Merge "Remove _light / _dark / from "fixed" sys resources" into udc-dev
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 c76a43f..954ec52 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -3088,7 +3088,9 @@
                         + " does not belong to the calling uid " + callingUid);
             }
             synchronized (mLock) {
-                removeLocked(callingPackage, REMOVE_REASON_ALARM_CANCELLED);
+                removeAlarmsInternalLocked(
+                        a -> (a.matches(callingPackage) && a.creatorUid == callingUid),
+                        REMOVE_REASON_ALARM_CANCELLED);
             }
         }
 
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 89eb1a9..4477e94 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1259,10 +1259,14 @@
 
                 final BackgroundStartPrivileges bsp =
                         activityManagerInternal.getBackgroundStartPrivileges(uid);
-                final boolean balAllowed = bsp.allowsBackgroundActivityStarts();
                 if (DEBUG) {
-                    Slog.d(TAG, "Job " + job.toShortString() + " bal state: " + bsp);
+                    Slog.d(TAG, "Job " + job.toShortString() + " bsp state: " + bsp);
                 }
+                // Intentionally use the background activity start BSP here instead of
+                // the full BAL check since the former is transient and better indicates that the
+                // user recently interacted with the app, while the latter includes
+                // permanent exceptions that don't warrant bypassing normal concurrency policy.
+                final boolean balAllowed = bsp.allowsBackgroundActivityStarts();
                 cachedPrivilegedState.put(uid,
                         balAllowed ? PRIVILEGED_STATE_BAL : PRIVILEGED_STATE_NONE);
                 return balAllowed;
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 08810b5..4cf9c8c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -31,7 +31,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
-import android.app.BackgroundStartPrivileges;
 import android.app.IUidObserver;
 import android.app.compat.CompatChanges;
 import android.app.job.IJobScheduler;
@@ -3782,7 +3781,8 @@
             return canPersist;
         }
 
-        private int validateJob(@NonNull JobInfo job, int callingUid, int sourceUserId,
+        private int validateJob(@NonNull JobInfo job, int callingUid, int callingPid,
+                int sourceUserId,
                 @Nullable String sourcePkgName, @Nullable JobWorkItem jobWorkItem) {
             final boolean rejectNegativeNetworkEstimates = CompatChanges.isChangeEnabled(
                             JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES, callingUid);
@@ -3815,6 +3815,8 @@
                 }
                 // We aim to check the permission of both the source and calling app so that apps
                 // don't attempt to bypass the permission by using other apps to do the work.
+                boolean isInStateToScheduleUiJobSource = false;
+                final String callingPkgName = job.getService().getPackageName();
                 if (sourceUid != -1) {
                     // Check the permission of the source app.
                     final int sourceResult =
@@ -3822,8 +3824,13 @@
                     if (sourceResult != JobScheduler.RESULT_SUCCESS) {
                         return sourceResult;
                     }
+                    final int sourcePid =
+                            callingUid == sourceUid && callingPkgName.equals(sourcePkgName)
+                                    ? callingPid : -1;
+                    isInStateToScheduleUiJobSource = isInStateToScheduleUserInitiatedJobs(
+                            sourceUid, sourcePid, sourcePkgName);
                 }
-                final String callingPkgName = job.getService().getPackageName();
+                boolean isInStateToScheduleUiJobCalling = false;
                 if (callingUid != sourceUid || !callingPkgName.equals(sourcePkgName)) {
                     // Source app is different from calling app. Make sure the calling app also has
                     // the permission.
@@ -3832,25 +3839,17 @@
                     if (callingResult != JobScheduler.RESULT_SUCCESS) {
                         return callingResult;
                     }
+                    // Avoid rechecking the state if the source app is able to schedule the job.
+                    if (!isInStateToScheduleUiJobSource) {
+                        isInStateToScheduleUiJobCalling = isInStateToScheduleUserInitiatedJobs(
+                                callingUid, callingPid, callingPkgName);
+                    }
                 }
 
-                final int uid = sourceUid != -1 ? sourceUid : callingUid;
-                final int procState = mActivityManagerInternal.getUidProcessState(uid);
-                if (DEBUG) {
-                    Slog.d(TAG, "Uid " + uid + " proc state="
-                            + ActivityManager.procStateToString(procState));
-                }
-                if (procState != ActivityManager.PROCESS_STATE_TOP) {
-                    final BackgroundStartPrivileges bsp =
-                            mActivityManagerInternal.getBackgroundStartPrivileges(uid);
-                    if (DEBUG) {
-                        Slog.d(TAG, "Uid " + uid + ": " + bsp);
-                    }
-                    if (!bsp.allowsBackgroundActivityStarts()) {
-                        Slog.e(TAG,
-                                "Uid " + uid + " not in a state to schedule user-initiated jobs");
-                        return JobScheduler.RESULT_FAILURE;
-                    }
+                if (!isInStateToScheduleUiJobSource && !isInStateToScheduleUiJobCalling) {
+                    Slog.e(TAG, "Uid(s) " + sourceUid + "/" + callingUid
+                            + " not in a state to schedule user-initiated jobs");
+                    return JobScheduler.RESULT_FAILURE;
                 }
             }
             if (jobWorkItem != null) {
@@ -3896,6 +3895,24 @@
             return JobScheduler.RESULT_SUCCESS;
         }
 
+        private boolean isInStateToScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
+            final int procState = mActivityManagerInternal.getUidProcessState(uid);
+            if (DEBUG) {
+                Slog.d(TAG, "Uid " + uid + " proc state="
+                        + ActivityManager.procStateToString(procState));
+            }
+            if (procState == ActivityManager.PROCESS_STATE_TOP) {
+                return true;
+            }
+            final boolean canScheduleUiJobsInBg =
+                    mActivityManagerInternal.canScheduleUserInitiatedJobs(uid, pid, pkgName);
+            if (DEBUG) {
+                Slog.d(TAG, "Uid " + uid
+                        + " AM.canScheduleUserInitiatedJobs= " + canScheduleUiJobsInBg);
+            }
+            return canScheduleUiJobsInBg;
+        }
+
         // IJobScheduler implementation
         @Override
         public int schedule(String namespace, JobInfo job) throws RemoteException {
@@ -3908,7 +3925,7 @@
 
             enforceValidJobRequest(uid, pid, job);
 
-            final int result = validateJob(job, uid, -1, null, null);
+            final int result = validateJob(job, uid, pid, -1, null, null);
             if (result != JobScheduler.RESULT_SUCCESS) {
                 return result;
             }
@@ -3941,7 +3958,7 @@
                 throw new NullPointerException("work is null");
             }
 
-            final int result = validateJob(job, uid, -1, null, work);
+            final int result = validateJob(job, uid, pid, -1, null, work);
             if (result != JobScheduler.RESULT_SUCCESS) {
                 return result;
             }
@@ -3963,6 +3980,7 @@
         public int scheduleAsPackage(String namespace, JobInfo job, String packageName, int userId,
                 String tag) throws RemoteException {
             final int callerUid = Binder.getCallingUid();
+            final int callerPid = Binder.getCallingPid();
             if (DEBUG) {
                 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
                         + " on behalf of " + packageName + "/");
@@ -3979,7 +3997,7 @@
                         + " not permitted to schedule jobs for other apps");
             }
 
-            int result = validateJob(job, callerUid, userId, packageName, null);
+            int result = validateJob(job, callerUid, callerPid, userId, packageName, null);
             if (result != JobScheduler.RESULT_SUCCESS) {
                 return result;
             }
diff --git a/api/Android.bp b/api/Android.bp
index 73dbd28..24b3004 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -41,23 +41,6 @@
 }
 
 python_binary_host {
-    name: "api_versions_trimmer",
-    srcs: ["api_versions_trimmer.py"],
-}
-
-python_test_host {
-    name: "api_versions_trimmer_unittests",
-    main: "api_versions_trimmer_unittests.py",
-    srcs: [
-        "api_versions_trimmer_unittests.py",
-        "api_versions_trimmer.py",
-    ],
-    test_options: {
-        unit_test: true,
-    },
-}
-
-python_binary_host {
     name: "merge_annotation_zips",
     srcs: ["merge_annotation_zips.py"],
 }
diff --git a/api/api.go b/api/api.go
index 9876abb..09c2383 100644
--- a/api/api.go
+++ b/api/api.go
@@ -194,55 +194,6 @@
 	}
 }
 
-func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) {
-	// For the filtered api versions, we prune all APIs except art module's APIs. because
-	// 1) ART apis are available by default to all modules, while other module-to-module deps are
-	//    explicit and probably receive more scrutiny anyway
-	// 2) The number of ART/libcore APIs is large, so not linting them would create a large gap
-	// 3) It's a compromise. Ideally we wouldn't be filtering out any module APIs, and have
-	//    per-module lint databases that excludes just that module's APIs. Alas, that's more
-	//    difficult to achieve.
-	modules = remove(modules, art)
-
-	for _, i := range []struct{
-		name string
-		out  string
-		in   string
-	}{
-		{
-			// We shouldn't need public-filtered or system-filtered.
-			// public-filtered is currently used to lint things that
-			// use the module sdk or the system server sdk, but those
-			// should be switched over to module-filtered and
-			// system-server-filtered, and then public-filtered can
-			// be removed.
-			name: "api-versions-xml-public-filtered",
-			out:  "api-versions-public-filtered.xml",
-			in:   ":api_versions_public{.api_versions.xml}",
-		}, {
-			name: "api-versions-xml-module-lib-filtered",
-			out:  "api-versions-module-lib-filtered.xml",
-			in:   ":api_versions_module_lib{.api_versions.xml}",
-		}, {
-			name: "api-versions-xml-system-server-filtered",
-			out:  "api-versions-system-server-filtered.xml",
-			in:   ":api_versions_system_server{.api_versions.xml}",
-		},
-	} {
-		props := genruleProps{}
-		props.Name = proptools.StringPtr(i.name)
-		props.Out = []string{i.out}
-		// Note: order matters: first parameter is the full api-versions.xml
-		// after that the stubs files in any order
-		// stubs files are all modules that export API surfaces EXCEPT ART
-		props.Srcs = append([]string{i.in}, createSrcs(modules, ".stubs{.jar}")...)
-		props.Tools = []string{"api_versions_trimmer"}
-		props.Cmd = proptools.StringPtr("$(location api_versions_trimmer) $(out) $(in)")
-		props.Dists = []android.Dist{{Targets: []string{"sdk"}}}
-		ctx.CreateModule(genrule.GenRuleFactory, &props, &bp2buildNotAvailable)
-	}
-}
-
 func createMergedPublicStubs(ctx android.LoadHookContext, modules []string) {
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("all-modules-public-stubs")
@@ -395,8 +346,6 @@
 
 	createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
 
-	createFilteredApiVersions(ctx, bootclasspath)
-
 	createPublicStubsSourceFilegroup(ctx, bootclasspath)
 }
 
diff --git a/api/api_versions_trimmer.py b/api/api_versions_trimmer.py
deleted file mode 100755
index 9afd95a..0000000
--- a/api/api_versions_trimmer.py
+++ /dev/null
@@ -1,136 +0,0 @@
-#!/usr/bin/env python3
-#
-# 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.
-
-"""Script to remove mainline APIs from the api-versions.xml."""
-
-import argparse
-import re
-import xml.etree.ElementTree as ET
-import zipfile
-
-
-def read_classes(stubs):
-  """Read classes from the stubs file.
-
-  Args:
-    stubs: argument can be a path to a file (a string), a file-like object or a
-    path-like object
-
-  Returns:
-    a set of the classes found in the file (set of strings)
-  """
-  classes = set()
-  with zipfile.ZipFile(stubs) as z:
-    for info in z.infolist():
-      if (not info.is_dir()
-          and info.filename.endswith(".class")
-          and not info.filename.startswith("META-INF")):
-        # drop ".class" extension
-        classes.add(info.filename[:-6])
-  return classes
-
-
-def filter_method_tag(method, classes_to_remove):
-  """Updates the signature of this method by calling filter_method_signature.
-
-  Updates the method passed into this function.
-
-  Args:
-    method: xml element that represents a method
-    classes_to_remove: set of classes you to remove
-  """
-  filtered = filter_method_signature(method.get("name"), classes_to_remove)
-  method.set("name", filtered)
-
-
-def filter_method_signature(signature, classes_to_remove):
-  """Removes mentions of certain classes from this method signature.
-
-  Replaces any existing classes that need to be removed, with java/lang/Object
-
-  Args:
-    signature: string that is a java representation of a method signature
-    classes_to_remove: set of classes you to remove
-  """
-  regex = re.compile("L.*?;")
-  start = signature.find("(")
-  matches = set(regex.findall(signature[start:]))
-  for m in matches:
-    # m[1:-1] to drop the leading `L` and `;` ending
-    if m[1:-1] in classes_to_remove:
-      signature = signature.replace(m, "Ljava/lang/Object;")
-  return signature
-
-
-def filter_lint_database(database, classes_to_remove, output):
-  """Reads a lint database and writes a filtered version without some classes.
-
-  Reads database from api-versions.xml and removes any references to classes
-  in the second argument. Writes the result (another xml with the same format
-  of the database) to output.
-
-  Args:
-    database: path to xml with lint database to read
-    classes_to_remove: iterable (ideally a set or similar for quick
-    lookups) that enumerates the classes that should be removed
-    output: path to write the filtered database
-  """
-  xml = ET.parse(database)
-  root = xml.getroot()
-  for c in xml.findall("class"):
-    cname = c.get("name")
-    if cname in classes_to_remove:
-      root.remove(c)
-    else:
-      # find the <extends /> tag inside this class to see if the parent
-      # has been removed from the known classes (attribute called name)
-      super_classes = c.findall("extends")
-      for super_class in super_classes:
-        super_class_name = super_class.get("name")
-        if super_class_name in classes_to_remove:
-          super_class.set("name", "java/lang/Object")
-      interfaces = c.findall("implements")
-      for interface in interfaces:
-        interface_name = interface.get("name")
-        if interface_name in classes_to_remove:
-          c.remove(interface)
-      for method in c.findall("method"):
-        filter_method_tag(method, classes_to_remove)
-  xml.write(output)
-
-
-def main():
-  """Run the program."""
-  parser = argparse.ArgumentParser(
-      description=
-      ("Read a lint database (api-versions.xml) and many stubs jar files. "
-       "Produce another database file that doesn't include the classes present "
-       "in the stubs file(s)."))
-  parser.add_argument("output", help="Destination of the result (xml file).")
-  parser.add_argument(
-      "api_versions",
-      help="The lint database (api-versions.xml file) to read data from"
-  )
-  parser.add_argument("stubs", nargs="+", help="The stubs jar file(s)")
-  parsed = parser.parse_args()
-  classes = set()
-  for stub in parsed.stubs:
-    classes.update(read_classes(stub))
-  filter_lint_database(parsed.api_versions, classes, parsed.output)
-
-
-if __name__ == "__main__":
-  main()
diff --git a/api/api_versions_trimmer_unittests.py b/api/api_versions_trimmer_unittests.py
deleted file mode 100644
index d2e5b7d..0000000
--- a/api/api_versions_trimmer_unittests.py
+++ /dev/null
@@ -1,307 +0,0 @@
-#!/usr/bin/env python3
-#
-# 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.
-
-import io
-import re
-import unittest
-import xml.etree.ElementTree as ET
-import zipfile
-
-import api_versions_trimmer
-
-
-def create_in_memory_zip_file(files):
-  f = io.BytesIO()
-  with zipfile.ZipFile(f, "w") as z:
-    for fname in files:
-      with z.open(fname, mode="w") as class_file:
-        class_file.write(b"")
-  return f
-
-
-def indent(elem, level=0):
-  i = "\n" + level * "  "
-  j = "\n" + (level - 1) * "  "
-  if len(elem):
-    if not elem.text or not elem.text.strip():
-      elem.text = i + "  "
-      if not elem.tail or not elem.tail.strip():
-        elem.tail = i
-        for subelem in elem:
-          indent(subelem, level + 1)
-        if not elem.tail or not elem.tail.strip():
-          elem.tail = j
-    else:
-      if level and (not elem.tail or not elem.tail.strip()):
-        elem.tail = j
-    return elem
-
-
-def pretty_print(s):
-  tree = ET.parse(io.StringIO(s))
-  el = indent(tree.getroot())
-  res = ET.tostring(el).decode("utf-8")
-  # remove empty lines inside the result because this still breaks some
-  # comparisons
-  return re.sub(r"\n\s*\n", "\n", res, re.MULTILINE)
-
-
-class ApiVersionsTrimmerUnittests(unittest.TestCase):
-
-  def setUp(self):
-    # so it prints diffs in long strings (xml files)
-    self.maxDiff = None
-
-  def test_read_classes(self):
-    f = create_in_memory_zip_file(
-        ["a/b/C.class",
-         "a/b/D.class",
-        ]
-    )
-    res = api_versions_trimmer.read_classes(f)
-    self.assertEqual({"a/b/C", "a/b/D"}, res)
-
-  def test_read_classes_ignore_dex(self):
-    f = create_in_memory_zip_file(
-        ["a/b/C.class",
-         "a/b/D.class",
-         "a/b/E.dex",
-         "f.dex",
-        ]
-    )
-    res = api_versions_trimmer.read_classes(f)
-    self.assertEqual({"a/b/C", "a/b/D"}, res)
-
-  def test_read_classes_ignore_manifest(self):
-    f = create_in_memory_zip_file(
-        ["a/b/C.class",
-         "a/b/D.class",
-         "META-INFO/G.class"
-        ]
-    )
-    res = api_versions_trimmer.read_classes(f)
-    self.assertEqual({"a/b/C", "a/b/D"}, res)
-
-  def test_filter_method_signature(self):
-    xml = """
-    <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/>
-    """
-    method = ET.fromstring(xml)
-    classes_to_remove = {"android/accessibilityservice/GestureDescription"}
-    expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z"
-    api_versions_trimmer.filter_method_tag(method, classes_to_remove)
-    self.assertEqual(expected, method.get("name"))
-
-  def test_filter_method_signature_with_L_in_method(self):
-    xml = """
-    <method name="dispatchLeftGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/>
-    """
-    method = ET.fromstring(xml)
-    classes_to_remove = {"android/accessibilityservice/GestureDescription"}
-    expected = "dispatchLeftGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z"
-    api_versions_trimmer.filter_method_tag(method, classes_to_remove)
-    self.assertEqual(expected, method.get("name"))
-
-  def test_filter_method_signature_with_L_in_class(self):
-    xml = """
-    <method name="dispatchGesture(Landroid/accessibilityservice/LeftGestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/>
-    """
-    method = ET.fromstring(xml)
-    classes_to_remove = {"android/accessibilityservice/LeftGestureDescription"}
-    expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z"
-    api_versions_trimmer.filter_method_tag(method, classes_to_remove)
-    self.assertEqual(expected, method.get("name"))
-
-  def test_filter_method_signature_with_inner_class(self):
-    xml = """
-    <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription$Inner;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/>
-    """
-    method = ET.fromstring(xml)
-    classes_to_remove = {"android/accessibilityservice/GestureDescription$Inner"}
-    expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z"
-    api_versions_trimmer.filter_method_tag(method, classes_to_remove)
-    self.assertEqual(expected, method.get("name"))
-
-  def _run_filter_db_test(self, database_str, expected):
-    """Performs the pattern of testing the filter_lint_database method.
-
-    Filters instances of the class "a/b/C" (hard-coded) from the database string
-    and compares the result with the expected result (performs formatting of
-    the xml of both inputs)
-
-    Args:
-      database_str: string, the contents of the lint database (api-versions.xml)
-      expected: string, the expected result after filtering the original
-    database
-    """
-    database = io.StringIO(database_str)
-    classes_to_remove = {"a/b/C"}
-    output = io.BytesIO()
-    api_versions_trimmer.filter_lint_database(
-        database,
-        classes_to_remove,
-        output
-    )
-    expected = pretty_print(expected)
-    res = pretty_print(output.getvalue().decode("utf-8"))
-    self.assertEqual(expected, res)
-
-  def test_filter_lint_database_updates_method_signature_params(self):
-    self._run_filter_db_test(
-        database_str="""
-    <api version="2">
-      <!-- will be removed -->
-      <class name="a/b/C" since="1">
-        <extends name="java/lang/Object"/>
-      </class>
-
-      <class name="a/b/E" since="1">
-        <!-- extends will be modified -->
-        <extends name="a/b/C"/>
-        <!-- first parameter will be modified -->
-        <method name="dispatchGesture(La/b/C;Landroid/os/Handler;)Z" since="24"/>
-        <!-- second should remain untouched -->
-        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
-      </class>
-    </api>
-    """,
-        expected="""
-    <api version="2">
-      <class name="a/b/E" since="1">
-        <extends name="java/lang/Object"/>
-        <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/>
-        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
-      </class>
-    </api>
-    """)
-
-  def test_filter_lint_database_updates_method_signature_return(self):
-    self._run_filter_db_test(
-        database_str="""
-    <api version="2">
-      <!-- will be removed -->
-      <class name="a/b/C" since="1">
-        <extends name="java/lang/Object"/>
-      </class>
-
-      <class name="a/b/E" since="1">
-        <!-- extends will be modified -->
-        <extends name="a/b/C"/>
-        <!-- return type should be changed -->
-        <method name="gestureIdToString(I)La/b/C;" since="24"/>
-      </class>
-    </api>
-    """,
-        expected="""
-    <api version="2">
-      <class name="a/b/E" since="1">
-
-        <extends name="java/lang/Object"/>
-
-        <method name="gestureIdToString(I)Ljava/lang/Object;" since="24"/>
-      </class>
-    </api>
-    """)
-
-  def test_filter_lint_database_removes_implements(self):
-    self._run_filter_db_test(
-        database_str="""
-    <api version="2">
-      <!-- will be removed -->
-      <class name="a/b/C" since="1">
-        <extends name="java/lang/Object"/>
-      </class>
-
-      <class name="a/b/D" since="1">
-        <extends name="java/lang/Object"/>
-        <implements name="a/b/C"/>
-        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
-      </class>
-    </api>
-    """,
-        expected="""
-    <api version="2">
-
-      <class name="a/b/D" since="1">
-        <extends name="java/lang/Object"/>
-        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
-      </class>
-    </api>
-    """)
-
-  def test_filter_lint_database_updates_extends(self):
-    self._run_filter_db_test(
-        database_str="""
-    <api version="2">
-      <!-- will be removed -->
-      <class name="a/b/C" since="1">
-        <extends name="java/lang/Object"/>
-      </class>
-
-      <class name="a/b/E" since="1">
-        <!-- extends will be modified -->
-        <extends name="a/b/C"/>
-        <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/>
-        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
-      </class>
-    </api>
-    """,
-        expected="""
-    <api version="2">
-      <class name="a/b/E" since="1">
-        <extends name="java/lang/Object"/>
-        <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/>
-        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
-      </class>
-    </api>
-    """)
-
-  def test_filter_lint_database_removes_class(self):
-    self._run_filter_db_test(
-        database_str="""
-    <api version="2">
-      <!-- will be removed -->
-      <class name="a/b/C" since="1">
-        <extends name="java/lang/Object"/>
-      </class>
-
-      <class name="a/b/D" since="1">
-        <extends name="java/lang/Object"/>
-        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
-      </class>
-    </api>
-    """,
-        expected="""
-    <api version="2">
-
-      <class name="a/b/D" since="1">
-        <extends name="java/lang/Object"/>
-        <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
-      </class>
-    </api>
-    """)
-
-
-if __name__ == "__main__":
-  unittest.main(verbosity=2)
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 00d9a4b..c4e8b0e 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -458,6 +458,7 @@
 
 EGLConfig BootAnimation::getEglConfig(const EGLDisplay& display) {
     const EGLint attribs[] = {
+        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
         EGL_RED_SIZE,   8,
         EGL_GREEN_SIZE, 8,
         EGL_BLUE_SIZE,  8,
diff --git a/cmds/svc/src/com/android/commands/svc/PowerCommand.java b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
index 957ebfb..a7560b2 100644
--- a/cmds/svc/src/com/android/commands/svc/PowerCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
@@ -40,7 +40,7 @@
     public String longHelp() {
         return shortHelp() + "\n"
                 + "\n"
-                + "usage: svc power stayon [true|false|usb|ac|wireless]\n"
+                + "usage: svc power stayon [true|false|usb|ac|wireless|dock]\n"
                 + "         Set the 'keep awake while plugged in' setting.\n"
                 + "       svc power reboot [reason]\n"
                 + "         Perform a runtime shutdown and reboot device with specified reason.\n"
@@ -66,9 +66,10 @@
                 if ("stayon".equals(args[1]) && args.length == 3) {
                     int val;
                     if ("true".equals(args[2])) {
-                        val = BatteryManager.BATTERY_PLUGGED_AC |
-                                BatteryManager.BATTERY_PLUGGED_USB |
-                                BatteryManager.BATTERY_PLUGGED_WIRELESS;
+                        val = BatteryManager.BATTERY_PLUGGED_AC
+                                | BatteryManager.BATTERY_PLUGGED_USB
+                                | BatteryManager.BATTERY_PLUGGED_WIRELESS
+                                | BatteryManager.BATTERY_PLUGGED_DOCK;
                     }
                     else if ("false".equals(args[2])) {
                         val = 0;
@@ -78,6 +79,8 @@
                         val = BatteryManager.BATTERY_PLUGGED_AC;
                     } else if ("wireless".equals(args[2])) {
                         val = BatteryManager.BATTERY_PLUGGED_WIRELESS;
+                    } else if ("dock".equals(args[2])) {
+                        val = BatteryManager.BATTERY_PLUGGED_DOCK;
                     } else {
                         break fail;
                     }
diff --git a/core/api/current.txt b/core/api/current.txt
index d415453..e53dc3f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -83,6 +83,7 @@
     field public static final String CLEAR_APP_CACHE = "android.permission.CLEAR_APP_CACHE";
     field public static final String CONFIGURE_WIFI_DISPLAY = "android.permission.CONFIGURE_WIFI_DISPLAY";
     field public static final String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
+    field public static final String CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS = "android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS";
     field public static final String CREDENTIAL_MANAGER_SET_ORIGIN = "android.permission.CREDENTIAL_MANAGER_SET_ORIGIN";
     field public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES";
     field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
@@ -13662,8 +13663,9 @@
   }
 
   public final class CredentialOption implements android.os.Parcelable {
-    ctor public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
+    ctor @Deprecated public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
     method public int describeContents();
+    method @NonNull public java.util.Set<android.content.ComponentName> getAllowedProviders();
     method @NonNull public android.os.Bundle getCandidateQueryData();
     method @NonNull public android.os.Bundle getCredentialRetrievalData();
     method @NonNull public String getType();
@@ -13673,6 +13675,14 @@
     field public static final String FLATTENED_REQUEST = "android.credentials.GetCredentialOption.FLATTENED_REQUEST_STRING";
   }
 
+  public static final class CredentialOption.Builder {
+    ctor public CredentialOption.Builder(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle);
+    method @NonNull public android.credentials.CredentialOption.Builder addAllowedProvider(@NonNull android.content.ComponentName);
+    method @NonNull public android.credentials.CredentialOption build();
+    method @NonNull public android.credentials.CredentialOption.Builder setAllowedProviders(@NonNull java.util.Set<android.content.ComponentName>);
+    method @NonNull public android.credentials.CredentialOption.Builder setIsSystemProviderRequired(boolean);
+  }
+
   public class GetCredentialException extends java.lang.Exception {
     ctor public GetCredentialException(@NonNull String, @Nullable String);
     ctor public GetCredentialException(@NonNull String, @Nullable String, @Nullable Throwable);
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index d0ce701..12026aa 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -72,6 +72,20 @@
     private static long sBackgroundPauseDelay = 1000;
 
     /**
+     * A cache of the values in a list. Used so that when calling the list, we have a copy
+     * of it in case the list is modified while iterating. The array can be reused to avoid
+     * allocation on every notification.
+     */
+    private Object[] mCachedList;
+
+    /**
+     * Tracks whether we've notified listeners of the onAnimationStart() event. This can be
+     * complex to keep track of since we notify listeners at different times depending on
+     * startDelay and whether start() was called before end().
+     */
+    boolean mStartListenersCalled = false;
+
+    /**
      * Sets the duration for delaying pausing animators when apps go into the background.
      * Used by AnimationHandler when requested to pause animators.
      *
@@ -158,16 +172,11 @@
      * @see AnimatorPauseListener
      */
     public void pause() {
-        if (isStarted() && !mPaused) {
+        // We only want to pause started Animators or animators that setCurrentPlayTime()
+        // have been called on. mStartListenerCalled will be true if seek has happened.
+        if ((isStarted() || mStartListenersCalled) && !mPaused) {
             mPaused = true;
-            if (mPauseListeners != null) {
-                ArrayList<AnimatorPauseListener> tmpListeners =
-                        (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
-                int numListeners = tmpListeners.size();
-                for (int i = 0; i < numListeners; ++i) {
-                    tmpListeners.get(i).onAnimationPause(this);
-                }
-            }
+            notifyPauseListeners(AnimatorCaller.ON_PAUSE);
         }
     }
 
@@ -184,14 +193,7 @@
     public void resume() {
         if (mPaused) {
             mPaused = false;
-            if (mPauseListeners != null) {
-                ArrayList<AnimatorPauseListener> tmpListeners =
-                        (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
-                int numListeners = tmpListeners.size();
-                for (int i = 0; i < numListeners; ++i) {
-                    tmpListeners.get(i).onAnimationResume(this);
-                }
-            }
+            notifyPauseListeners(AnimatorCaller.ON_RESUME);
         }
     }
 
@@ -450,6 +452,8 @@
             if (mPauseListeners != null) {
                 anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners);
             }
+            anim.mCachedList = null;
+            anim.mStartListenersCalled = false;
             return anim;
         } catch (CloneNotSupportedException e) {
            throw new AssertionError();
@@ -591,6 +595,86 @@
     }
 
     /**
+     * Calls notification for each AnimatorListener.
+     *
+     * @param notification The notification method to call on each listener.
+     * @param isReverse When this is used with start/end, this is the isReverse parameter. For
+     *                  other calls, this is ignored.
+     */
+    void notifyListeners(
+            AnimatorCaller<AnimatorListener, Animator> notification,
+            boolean isReverse
+    ) {
+        callOnList(mListeners, notification, this, isReverse);
+    }
+
+    /**
+     * Call pause/resume on each AnimatorPauseListener.
+     *
+     * @param notification Either ON_PAUSE or ON_RESUME to call onPause or onResume on each
+     *                     listener.
+     */
+    void notifyPauseListeners(AnimatorCaller<AnimatorPauseListener, Animator> notification) {
+        callOnList(mPauseListeners, notification, this, false);
+    }
+
+    void notifyStartListeners(boolean isReversing) {
+        boolean startListenersCalled = mStartListenersCalled;
+        mStartListenersCalled = true;
+        if (mListeners != null && !startListenersCalled) {
+            notifyListeners(AnimatorCaller.ON_START, isReversing);
+        }
+    }
+
+    void notifyEndListeners(boolean isReversing) {
+        boolean startListenersCalled = mStartListenersCalled;
+        mStartListenersCalled = false;
+        if (mListeners != null && startListenersCalled) {
+            notifyListeners(AnimatorCaller.ON_END, isReversing);
+        }
+    }
+
+    /**
+     * Calls <code>call</code> for every item in <code>list</code> with <code>animator</code> and
+     * <code>isReverse</code> as parameters.
+     *
+     * @param list The list of items to make calls on.
+     * @param call The method to call for each item in list.
+     * @param animator The animator parameter of call.
+     * @param isReverse The isReverse parameter of call.
+     * @param <T> The item type of list
+     * @param <A> The Animator type of animator.
+     */
+    <T, A> void callOnList(
+            ArrayList<T> list,
+            AnimatorCaller<T, A> call,
+            A animator,
+            boolean isReverse
+    ) {
+        int size = list == null ? 0 : list.size();
+        if (size > 0) {
+            // Try to reuse mCacheList to store the items of list.
+            Object[] array;
+            if (mCachedList == null || mCachedList.length < size) {
+                array = new Object[size];
+            } else {
+                array = mCachedList;
+                // Clear it in case there is some reentrancy
+                mCachedList = null;
+            }
+            list.toArray(array);
+            for (int i = 0; i < size; i++) {
+                //noinspection unchecked
+                T item = (T) array[i];
+                call.call(item, animator, isReverse);
+                array[i] = null;
+            }
+            // Store it for the next call so we can reuse this array, if needed.
+            mCachedList = array;
+        }
+    }
+
+    /**
      * <p>An animation listener receives notifications from an animation.
      * Notifications indicate animation related events, such as the end or the
      * repetition of the animation.</p>
@@ -748,4 +832,29 @@
             return clone;
         }
     }
+
+    /**
+     * Internally used by {@link #callOnList(ArrayList, AnimatorCaller, Object, boolean)} to
+     * make a call on all children of a list. This can be for start, stop, pause, cancel, update,
+     * etc notifications.
+     *
+     * @param <T> The type of listener to make the call on
+     * @param <A> The type of animator that is passed as a parameter
+     */
+    interface AnimatorCaller<T, A> {
+        void call(T listener, A animator, boolean isReverse);
+
+        AnimatorCaller<AnimatorListener, Animator> ON_START = AnimatorListener::onAnimationStart;
+        AnimatorCaller<AnimatorListener, Animator> ON_END = AnimatorListener::onAnimationEnd;
+        AnimatorCaller<AnimatorListener, Animator> ON_CANCEL =
+                (listener, animator, isReverse) -> listener.onAnimationCancel(animator);
+        AnimatorCaller<AnimatorListener, Animator> ON_REPEAT =
+                (listener, animator, isReverse) -> listener.onAnimationRepeat(animator);
+        AnimatorCaller<AnimatorPauseListener, Animator> ON_PAUSE =
+                (listener, animator, isReverse) -> listener.onAnimationPause(animator);
+        AnimatorCaller<AnimatorPauseListener, Animator> ON_RESUME =
+                (listener, animator, isReverse) -> listener.onAnimationResume(animator);
+        AnimatorCaller<ValueAnimator.AnimatorUpdateListener, ValueAnimator> ON_UPDATE =
+                (listener, animator, isReverse) -> listener.onAnimationUpdate(animator);
+    }
 }
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 257adfe..60659dc 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -32,6 +32,7 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * This class plays a set of {@link Animator} objects in the specified order. Animations
@@ -188,11 +189,6 @@
      */
     private long[] mChildStartAndStopTimes;
 
-    /**
-     * Tracks whether we've notified listeners of the onAnimationStart() event.
-     */
-    private boolean mStartListenersCalled;
-
     // This is to work around a bug in b/34736819. This needs to be removed once app team
     // fixes their side.
     private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() {
@@ -423,25 +419,29 @@
         if (Looper.myLooper() == null) {
             throw new AndroidRuntimeException("Animators may only be run on Looper threads");
         }
-        if (isStarted()) {
-            ArrayList<AnimatorListener> tmpListeners = null;
-            if (mListeners != null) {
-                tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
-                int size = tmpListeners.size();
-                for (int i = 0; i < size; i++) {
-                    tmpListeners.get(i).onAnimationCancel(this);
-                }
-            }
-            ArrayList<Node> playingSet = new ArrayList<>(mPlayingSet);
-            int setSize = playingSet.size();
-            for (int i = 0; i < setSize; i++) {
-                playingSet.get(i).mAnimation.cancel();
-            }
+        if (isStarted() || mStartListenersCalled) {
+            notifyListeners(AnimatorCaller.ON_CANCEL, false);
+            callOnPlayingSet(Animator::cancel);
             mPlayingSet.clear();
             endAnimation();
         }
     }
 
+    /**
+     * Calls consumer on every Animator of mPlayingSet.
+     *
+     * @param consumer The method to call on every Animator of mPlayingSet.
+     */
+    private void callOnPlayingSet(Consumer<Animator> consumer) {
+        final ArrayList<Node> list = mPlayingSet;
+        final int size = list.size();
+        //noinspection ForLoopReplaceableByForEach
+        for (int i = 0; i < size; i++) {
+            final Animator animator = list.get(i).mAnimation;
+            consumer.accept(animator);
+        }
+    }
+
     // Force all the animations to end when the duration scale is 0.
     private void forceToEnd() {
         if (mEndCanBeCalled) {
@@ -481,13 +481,13 @@
             return;
         }
         if (isStarted()) {
+            mStarted = false; // don't allow reentrancy
             // Iterate the animations that haven't finished or haven't started, and end them.
             if (mReversing) {
                 // Between start() and first frame, mLastEventId would be unset (i.e. -1)
                 mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId;
-                while (mLastEventId > 0) {
-                    mLastEventId = mLastEventId - 1;
-                    AnimationEvent event = mEvents.get(mLastEventId);
+                for (int eventId = mLastEventId - 1; eventId >= 0; eventId--) {
+                    AnimationEvent event = mEvents.get(eventId);
                     Animator anim = event.mNode.mAnimation;
                     if (mNodeMap.get(anim).mEnded) {
                         continue;
@@ -503,11 +503,10 @@
                     }
                 }
             } else {
-                while (mLastEventId < mEvents.size() - 1) {
+                for (int eventId = mLastEventId + 1; eventId < mEvents.size(); eventId++) {
                     // Avoid potential reentrant loop caused by child animators manipulating
                     // AnimatorSet's lifecycle (i.e. not a recommended approach).
-                    mLastEventId = mLastEventId + 1;
-                    AnimationEvent event = mEvents.get(mLastEventId);
+                    AnimationEvent event = mEvents.get(eventId);
                     Animator anim = event.mNode.mAnimation;
                     if (mNodeMap.get(anim).mEnded) {
                         continue;
@@ -522,7 +521,6 @@
                     }
                 }
             }
-            mPlayingSet.clear();
         }
         endAnimation();
     }
@@ -662,6 +660,7 @@
         super.pause();
         if (!previouslyPaused && mPaused) {
             mPauseTime = -1;
+            callOnPlayingSet(Animator::pause);
         }
     }
 
@@ -676,6 +675,7 @@
             if (mPauseTime >= 0) {
                 addAnimationCallback(0);
             }
+            callOnPlayingSet(Animator::resume);
         }
     }
 
@@ -716,6 +716,10 @@
         if (Looper.myLooper() == null) {
             throw new AndroidRuntimeException("Animators may only be run on Looper threads");
         }
+        if (inReverse == mReversing && selfPulse == mSelfPulse && mStarted) {
+            // It is already started
+            return;
+        }
         mStarted = true;
         mSelfPulse = selfPulse;
         mPaused = false;
@@ -749,32 +753,6 @@
         }
     }
 
-    private void notifyStartListeners(boolean inReverse) {
-        if (mListeners != null && !mStartListenersCalled) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                AnimatorListener listener = tmpListeners.get(i);
-                listener.onAnimationStart(this, inReverse);
-            }
-        }
-        mStartListenersCalled = true;
-    }
-
-    private void notifyEndListeners(boolean inReverse) {
-        if (mListeners != null && mStartListenersCalled) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                AnimatorListener listener = tmpListeners.get(i);
-                listener.onAnimationEnd(this, inReverse);
-            }
-        }
-        mStartListenersCalled = false;
-    }
-
     // Returns true if set is empty or contains nothing but animator sets with no start delay.
     private static boolean isEmptySet(AnimatorSet set) {
         if (set.getStartDelay() > 0) {
@@ -941,12 +919,18 @@
                                 lastPlayTime - node.mStartTime,
                                 notify
                         );
+                        if (notify) {
+                            mPlayingSet.remove(node);
+                        }
                     } else if (start <= currentPlayTime && currentPlayTime <= end) {
                         animator.animateSkipToEnds(
                                 currentPlayTime - node.mStartTime,
                                 lastPlayTime - node.mStartTime,
                                 notify
                         );
+                        if (notify && !mPlayingSet.contains(node)) {
+                            mPlayingSet.add(node);
+                        }
                     }
                 }
             }
@@ -974,12 +958,18 @@
                                 lastPlayTime - node.mStartTime,
                                 notify
                         );
+                        if (notify) {
+                            mPlayingSet.remove(node);
+                        }
                     } else if (start <= currentPlayTime && currentPlayTime <= end) {
                         animator.animateSkipToEnds(
                                 currentPlayTime - node.mStartTime,
                                 lastPlayTime - node.mStartTime,
                                 notify
                         );
+                        if (notify && !mPlayingSet.contains(node)) {
+                            mPlayingSet.add(node);
+                        }
                     }
                 }
             }
@@ -1120,8 +1110,8 @@
                 mSeekState.setPlayTime(0, mReversing);
             }
         }
-        animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
         mSeekState.setPlayTime(playTime, mReversing);
+        animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
     }
 
     /**
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 7009725..5d69f8b 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -199,13 +199,6 @@
     private boolean mStarted = false;
 
     /**
-     * Tracks whether we've notified listeners of the onAnimationStart() event. This can be
-     * complex to keep track of since we notify listeners at different times depending on
-     * startDelay and whether start() was called before end().
-     */
-    private boolean mStartListenersCalled = false;
-
-    /**
      * Flag that denotes whether the animation is set up and ready to go. Used to
      * set up animation that has not yet been started.
      */
@@ -1108,30 +1101,6 @@
         }
     }
 
-    private void notifyStartListeners(boolean isReversing) {
-        if (mListeners != null && !mStartListenersCalled) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationStart(this, isReversing);
-            }
-        }
-        mStartListenersCalled = true;
-    }
-
-    private void notifyEndListeners(boolean isReversing) {
-        if (mListeners != null && mStartListenersCalled) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationEnd(this, isReversing);
-            }
-        }
-        mStartListenersCalled = false;
-    }
-
     /**
      * Start the animation playing. This version of start() takes a boolean flag that indicates
      * whether the animation should play in reverse. The flag is usually false, but may be set
@@ -1149,6 +1118,10 @@
         if (Looper.myLooper() == null) {
             throw new AndroidRuntimeException("Animators may only be run on Looper threads");
         }
+        if (playBackwards == mResumed && mSelfPulse == !mSuppressSelfPulseRequested && mStarted) {
+            // already started
+            return;
+        }
         mReversing = playBackwards;
         mSelfPulse = !mSuppressSelfPulseRequested;
         // Special case: reversing from seek-to-0 should act as if not seeked at all.
@@ -1219,23 +1192,14 @@
         // Only cancel if the animation is actually running or has been started and is about
         // to run
         // Only notify listeners if the animator has actually started
-        if ((mStarted || mRunning) && mListeners != null) {
+        if ((mStarted || mRunning || mStartListenersCalled) && mListeners != null) {
             if (!mRunning) {
                 // If it's not yet running, then start listeners weren't called. Call them now.
                 notifyStartListeners(mReversing);
             }
-            int listenersSize = mListeners.size();
-            if (listenersSize > 0) {
-                ArrayList<AnimatorListener> tmpListeners =
-                        (ArrayList<AnimatorListener>) mListeners.clone();
-                for (int i = 0; i < listenersSize; i++) {
-                    AnimatorListener listener = tmpListeners.get(i);
-                    listener.onAnimationCancel(this);
-                }
-            }
+            notifyListeners(AnimatorCaller.ON_CANCEL, false);
         }
         endAnimation();
-
     }
 
     @Override
@@ -1338,11 +1302,11 @@
             // If it's not yet running, then start listeners weren't called. Call them now.
             notifyStartListeners(mReversing);
         }
-        mRunning = false;
-        mStarted = false;
         mLastFrameTime = -1;
         mFirstFrameTime = -1;
         mStartTime = -1;
+        mRunning = false;
+        mStarted = false;
         notifyEndListeners(mReversing);
         // mReversing needs to be reset *after* notifying the listeners for the end callbacks.
         mReversing = false;
@@ -1435,12 +1399,7 @@
                 done = true;
             } else if (newIteration && !lastIterationFinished) {
                 // Time to repeat
-                if (mListeners != null) {
-                    int numListeners = mListeners.size();
-                    for (int i = 0; i < numListeners; ++i) {
-                        mListeners.get(i).onAnimationRepeat(this);
-                    }
-                }
+                notifyListeners(AnimatorCaller.ON_REPEAT, false);
             } else if (lastIterationFinished) {
                 done = true;
             }
@@ -1493,13 +1452,8 @@
             iteration = Math.min(iteration, mRepeatCount);
             lastIteration = Math.min(lastIteration, mRepeatCount);
 
-            if (iteration != lastIteration) {
-                if (mListeners != null) {
-                    int numListeners = mListeners.size();
-                    for (int i = 0; i < numListeners; ++i) {
-                        mListeners.get(i).onAnimationRepeat(this);
-                    }
-                }
+            if (notify && iteration != lastIteration) {
+                notifyListeners(AnimatorCaller.ON_REPEAT, false);
             }
         }
 
@@ -1697,11 +1651,8 @@
         for (int i = 0; i < numValues; ++i) {
             mValues[i].calculateValue(fraction);
         }
-        if (mUpdateListeners != null) {
-            int numListeners = mUpdateListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                mUpdateListeners.get(i).onAnimationUpdate(this);
-            }
+        if (mSeekFraction >= 0 || mStartListenersCalled) {
+            callOnList(mUpdateListeners, AnimatorCaller.ON_UPDATE, this, false);
         }
     }
 
@@ -1718,7 +1669,6 @@
         anim.mRunning = false;
         anim.mPaused = false;
         anim.mResumed = false;
-        anim.mStartListenersCalled = false;
         anim.mStartTime = -1;
         anim.mStartTimeCommitted = false;
         anim.mAnimationEndRequested = false;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 981f140..929c07b 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -234,7 +234,7 @@
      * Map of callbacks that have registered for {@link UidFrozenStateChanged} events.
      * Will be called when a Uid has become frozen or unfrozen.
      */
-    final ArrayMap<UidFrozenStateChangedCallback, Executor> mFrozenStateChangedCallbacks =
+    private final ArrayMap<UidFrozenStateChangedCallback, Executor> mFrozenStateChangedCallbacks =
              new ArrayMap<>();
 
     private final IUidFrozenStateChangedCallback mFrozenStateChangedCallback =
@@ -284,6 +284,8 @@
         public @interface UidFrozenState {}
 
         /**
+         * Notify the client that the frozen states of an array of UIDs have changed.
+         *
          * @param uids The UIDs for which the frozen state has changed
          * @param frozenStates Frozen state for each UID index, Will be set to
          *               {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_FROZEN}
@@ -316,9 +318,11 @@
     public void registerUidFrozenStateChangedCallback(
             @NonNull Executor executor,
             @NonNull UidFrozenStateChangedCallback callback) {
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
         synchronized (mFrozenStateChangedCallbacks) {
             if (mFrozenStateChangedCallbacks.containsKey(callback)) {
-                throw new IllegalArgumentException("Callback already registered: " + callback);
+                throw new IllegalStateException("Callback already registered: " + callback);
             }
             mFrozenStateChangedCallbacks.put(callback, executor);
             if (mFrozenStateChangedCallbacks.size() > 1) {
@@ -344,6 +348,7 @@
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public void unregisterUidFrozenStateChangedCallback(
             @NonNull UidFrozenStateChangedCallback callback) {
+        Preconditions.checkNotNull(callback, "callback cannot be null");
         synchronized (mFrozenStateChangedCallbacks) {
             mFrozenStateChangedCallbacks.remove(callback);
             if (mFrozenStateChangedCallbacks.isEmpty()) {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index b96f8c9..0293bb5 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -474,6 +474,12 @@
     public abstract BackgroundStartPrivileges getBackgroundStartPrivileges(int uid);
     public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing);
 
+    /**
+     * Returns whether the app is in a state where it is allowed to schedule a
+     * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}.
+     */
+    public abstract boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName);
+
     /** @see com.android.server.am.ActivityManagerService#monitor */
     public abstract void monitor();
 
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 2879062..403acad 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -879,6 +879,8 @@
     /** Logs API state change to associate with an FGS, used for FGS Type Metrics */
     void logFgsApiStateChanged(int apiType, int state, int appUid, int appPid);
 
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
     void registerUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
     void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
 }
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 0bdc222..1df8602 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -42,10 +42,10 @@
         },
         {
             "file_patterns": ["(/|^)AppOpsManager.java"],
-            "name": "CtsPermission2TestCases",
+            "name": "CtsPermissionPolicyTestCases",
             "options": [
                 {
-                    "include-filter": "android.permission2.cts.RuntimePermissionProperties"
+                    "include-filter": "android.permissionpolicy.cts.RuntimePermissionProperties"
                 }
             ]
         },
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 303ada0..404f94a 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -188,6 +188,14 @@
     public int launchIntoPipHostTaskId;
 
     /**
+     * The task id of the parent Task of the launch-into-pip Activity, i.e., if task have more than
+     * one activity it will create new task for this activity, this id is the origin task id and
+     * the pip activity will be reparent to origin task when it exit pip mode.
+     * @hide
+     */
+    public int lastParentTaskIdBeforePip;
+
+    /**
      * The {@link Rect} copied from {@link DisplayCutout#getSafeInsets()} if the cutout is not of
      * (LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS),
      * {@code null} otherwise.
@@ -512,6 +520,7 @@
         pictureInPictureParams = source.readTypedObject(PictureInPictureParams.CREATOR);
         shouldDockBigOverlays = source.readBoolean();
         launchIntoPipHostTaskId = source.readInt();
+        lastParentTaskIdBeforePip = source.readInt();
         displayCutoutInsets = source.readTypedObject(Rect.CREATOR);
         topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
         isResizeable = source.readBoolean();
@@ -558,6 +567,7 @@
         dest.writeTypedObject(pictureInPictureParams, flags);
         dest.writeBoolean(shouldDockBigOverlays);
         dest.writeInt(launchIntoPipHostTaskId);
+        dest.writeInt(lastParentTaskIdBeforePip);
         dest.writeTypedObject(displayCutoutInsets, flags);
         dest.writeTypedObject(topActivityInfo, flags);
         dest.writeBoolean(isResizeable);
@@ -598,6 +608,7 @@
                 + " pictureInPictureParams=" + pictureInPictureParams
                 + " shouldDockBigOverlays=" + shouldDockBigOverlays
                 + " launchIntoPipHostTaskId=" + launchIntoPipHostTaskId
+                + " lastParentTaskIdBeforePip=" + lastParentTaskIdBeforePip
                 + " displayCutoutSafeInsets=" + displayCutoutInsets
                 + " topActivityInfo=" + topActivityInfo
                 + " launchCookies=" + launchCookies
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b9c671a..21e2a13 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -10134,25 +10134,9 @@
 
     /**
      * Register an application dex module with the package manager.
-     * The package manager will keep track of the given module for future optimizations.
      *
-     * Dex module optimizations will disable the classpath checking at runtime. The client bares
-     * the responsibility to ensure that the static assumptions on classes in the optimized code
-     * hold at runtime (e.g. there's no duplicate classes in the classpath).
-     *
-     * Note that the package manager already keeps track of dex modules loaded with
-     * {@link dalvik.system.DexClassLoader} and {@link dalvik.system.PathClassLoader}.
-     * This can be called for an eager registration.
-     *
-     * The call might take a while and the results will be posted on the main thread, using
-     * the given callback.
-     *
-     * If the module is intended to be shared with other apps, make sure that the file
-     * permissions allow for it.
-     * If at registration time the permissions allow for others to read it, the module would
-     * be marked as a shared module which might undergo a different optimization strategy.
-     * (usually shared modules will generated larger optimizations artifacts,
-     * taking more disk space).
+     * This call no longer does anything. If a callback is given it is called with a false success
+     * value.
      *
      * @param dexModulePath the absolute path of the dex module.
      * @param callback if not null, {@link DexModuleRegisterCallback#onDexModuleRegistered} will
diff --git a/core/java/android/credentials/CredentialOption.java b/core/java/android/credentials/CredentialOption.java
index 9a3b46d..da6656a 100644
--- a/core/java/android/credentials/CredentialOption.java
+++ b/core/java/android/credentials/CredentialOption.java
@@ -16,16 +16,25 @@
 
 package android.credentials;
 
+import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS;
+
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArraySet;
+
+import androidx.annotation.RequiresPermission;
 
 import com.android.internal.util.AnnotationValidations;
 import com.android.internal.util.Preconditions;
 
+import java.util.Set;
+
 /**
  * Information about a specific type of credential to be requested during a {@link
  * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor,
@@ -66,6 +75,14 @@
     private final boolean mIsSystemProviderRequired;
 
     /**
+     * A list of {@link ComponentName}s corresponding to the providers that this option must be
+     * queried against.
+     */
+    @NonNull
+    private final ArraySet<ComponentName> mAllowedProviders;
+
+
+    /**
      * Returns the requested credential type.
      */
     @NonNull
@@ -105,12 +122,22 @@
         return mIsSystemProviderRequired;
     }
 
+    /**
+     * Returns the set of {@link ComponentName} corresponding to providers that must receive
+     * this option.
+     */
+    @NonNull
+    public Set<ComponentName> getAllowedProviders() {
+        return mAllowedProviders;
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString8(mType);
         dest.writeBundle(mCredentialRetrievalData);
         dest.writeBundle(mCandidateQueryData);
         dest.writeBoolean(mIsSystemProviderRequired);
+        dest.writeArraySet(mAllowedProviders);
     }
 
     @Override
@@ -125,6 +152,7 @@
                 + ", requestData=" + mCredentialRetrievalData
                 + ", candidateQueryData=" + mCandidateQueryData
                 + ", isSystemProviderRequired=" + mIsSystemProviderRequired
+                + ", allowedProviders=" + mAllowedProviders
                 + "}";
     }
 
@@ -139,17 +167,50 @@
      *                                 provider
      * @throws IllegalArgumentException If type is empty.
      */
-    public CredentialOption(
+    private CredentialOption(
             @NonNull String type,
             @NonNull Bundle credentialRetrievalData,
             @NonNull Bundle candidateQueryData,
-            boolean isSystemProviderRequired) {
+            boolean isSystemProviderRequired,
+            @NonNull ArraySet<ComponentName> allowedProviders) {
         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
         mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
                 "requestData must not be null");
         mCandidateQueryData = requireNonNull(candidateQueryData,
                 "candidateQueryData must not be null");
         mIsSystemProviderRequired = isSystemProviderRequired;
+        mAllowedProviders = requireNonNull(allowedProviders, "providerFilterSer must"
+                + "not be empty");
+    }
+
+    /**
+     * Constructs a {@link CredentialOption}.
+     *
+     * @param type                     the requested credential type
+     * @param credentialRetrievalData  the request data
+     * @param candidateQueryData       the partial request data that will be sent to the provider
+     *                                 during the initial credential candidate query stage
+     * @param isSystemProviderRequired whether the request must only be fulfilled by a system
+     *                                 provider
+     * @throws IllegalArgumentException If type is empty, or null.
+     * @throws NullPointerException If {@code credentialRetrievalData}, or
+     * {@code candidateQueryData} is null.
+     *
+     * @deprecated replaced by Builder
+     */
+    @Deprecated
+    public CredentialOption(
+            @NonNull String type,
+            @NonNull Bundle credentialRetrievalData,
+            @NonNull Bundle candidateQueryData,
+            boolean isSystemProviderRequired) {
+        this(
+                type,
+                credentialRetrievalData,
+                candidateQueryData,
+                isSystemProviderRequired,
+                new ArraySet<>()
+        );
     }
 
     private CredentialOption(@NonNull Parcel in) {
@@ -165,6 +226,8 @@
         mCandidateQueryData = candidateQueryData;
         AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
         mIsSystemProviderRequired = isSystemProviderRequired;
+        mAllowedProviders = (ArraySet<ComponentName>) in.readArraySet(null);
+        AnnotationValidations.validate(NonNull.class, null, mAllowedProviders);
     }
 
     @NonNull
@@ -179,4 +242,108 @@
             return new CredentialOption(in);
         }
     };
+
+    /** A builder for {@link CredentialOption}. */
+    public static final class Builder {
+
+        @NonNull
+        private String mType;
+
+        @NonNull
+        private Bundle mCredentialRetrievalData;
+
+        @NonNull
+        private Bundle mCandidateQueryData;
+
+        private boolean mIsSystemProviderRequired = false;
+
+        @NonNull
+        private ArraySet<ComponentName> mAllowedProviders = new ArraySet<>();
+
+        /**
+         * @param type                    the type of the credential option
+         * @param credentialRetrievalData the full request data
+         * @param candidateQueryData      the partial request data that will be sent to the provider
+         *                                during the initial credential candidate query stage.
+         * @throws IllegalArgumentException If {@code type} is null, or empty
+         * @throws NullPointerException     If {@code credentialRetrievalData}, or
+         *                                  {@code candidateQueryData} is null
+         */
+        public Builder(@NonNull String type, @NonNull Bundle credentialRetrievalData,
+                @NonNull Bundle candidateQueryData) {
+            mType = Preconditions.checkStringNotEmpty(type, "type must not be "
+                    + "null, or empty");
+            mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
+                    "credentialRetrievalData must not be null");
+            mCandidateQueryData = requireNonNull(candidateQueryData,
+                    "candidateQueryData must not be null");
+        }
+
+        /**
+         * Sets a true/false value corresponding to whether this option must be serviced by
+         * system credentials providers only.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setIsSystemProviderRequired(boolean isSystemProviderRequired) {
+            mIsSystemProviderRequired = isSystemProviderRequired;
+            return this;
+        }
+
+        /**
+         * Adds a provider {@link ComponentName} to be queried while gathering credentials from
+         * credential providers on the device.
+         *
+         * If no candidate providers are specified, all user configured and system credential
+         * providers will be queried in the candidate query phase.
+         *
+         * If an invalid component name is provided, or a service corresponding to the
+         * component name does not exist on the device, that component name is ignored.
+         * If all component names are invalid, or not present on the device, no providers
+         * are queried and no credentials are retrieved.
+         *
+         * @throws NullPointerException If {@code allowedProvider} is null
+         */
+        @RequiresPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)
+        @NonNull
+        public Builder addAllowedProvider(@NonNull ComponentName allowedProvider) {
+            mAllowedProviders.add(requireNonNull(allowedProvider,
+                    "allowedProvider must not be null"));
+            return this;
+        }
+
+        /**
+         * Sets a set of provider {@link ComponentName} to be queried while gathering credentials
+         * from credential providers on the device.
+         *
+         * If no candidate providers are specified, all user configured and system credential
+         * providers will be queried in the candidate query phase.
+         *
+         * If an invalid component name is provided, or a service corresponding to the
+         * component name does not exist on the device, that component name is ignored.
+         * If all component names are invalid, or not present on the device, no providers
+         * are queried and no credentials are retrieved.
+         *
+         * @throws NullPointerException If {@code allowedProviders} is null, or any of its
+         * elements are null.
+         */
+        @RequiresPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)
+        @NonNull
+        public Builder setAllowedProviders(@NonNull Set<ComponentName> allowedProviders) {
+            Preconditions.checkCollectionElementsNotNull(
+                    allowedProviders,
+                    /*valueName=*/ "allowedProviders");
+            mAllowedProviders = new ArraySet<>(allowedProviders);
+            return this;
+        }
+
+        /**
+         * Builds a {@link CredentialOption}.
+         */
+        @NonNull
+        public CredentialOption build() {
+            return new CredentialOption(mType, mCredentialRetrievalData, mCandidateQueryData,
+                    mIsSystemProviderRequired, mAllowedProviders);
+        }
+    }
 }
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index e9df553..1bc6099 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -661,7 +661,7 @@
      *
      * <p>Repeating burst requests are a simple way for an application to
      * maintain a preview or other continuous stream of frames where each
-     * request is different in a predicatable way, without having to continually
+     * request is different in a predictable way, without having to continually
      * submit requests through {@link #captureBurst}.</p>
      *
      * <p>To stop the repeating capture, call {@link #stopRepeating}. Any
@@ -902,7 +902,7 @@
      * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING OFFLINE_PROCESSING}
      * capability in {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES}. When this method
      * is supported, applications can use it to improve the latency of closing camera or recreating
-     * capture session without losing the in progresss capture request outputs.</p>
+     * capture session without losing the in progress capture request outputs.</p>
      *
      * <p>Offline processing mode and the corresponding {@link CameraOfflineSession} differ from
      * a regular online camera capture session in several ways. Successful offline switches will
@@ -1001,7 +1001,7 @@
      *
      * <p>Note that for common usage scenarios like creating a new session or closing the camera
      * device, it is faster to call respective APIs directly (see below for more details) without
-     * calling into this method. This API is only useful when application wants to uncofigure the
+     * calling into this method. This API is only useful when application wants to unconfigure the
      * camera but keep the device open for later use.</p>
      *
      * <p>Creating a new capture session with {@link CameraDevice#createCaptureSession}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index dfb9cf6..0e4c3c0 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -651,7 +651,7 @@
      * @param metadataClass The subclass of CameraMetadata that you want to get the keys for.
      * @param keyClass The class of the metadata key, e.g. CaptureRequest.Key.class
      * @param filterTags An array of tags to be used for filtering
-     * @param includeSynthetic Include public syntethic tag by default.
+     * @param includeSynthetic Include public synthetic tag by default.
      *
      * @return List of keys supported by this CameraDevice for metadataClass.
      *
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 42aa608..5feda78 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -141,7 +141,7 @@
      * parameters. All automatic control is disabled (auto-exposure, auto-white
      * balance, auto-focus), and post-processing parameters are set to preview
      * quality. The manual capture parameters (exposure, sensitivity, and so on)
-     * are set to reasonable defaults, but should be overriden by the
+     * are set to reasonable defaults, but should be overridden by the
      * application depending on the intended use case.
      * This template is guaranteed to be supported on camera devices that support the
      * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR MANUAL_SENSOR}
@@ -680,7 +680,7 @@
      * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th> </tr>
      * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code MAXIMUM}</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution GPU processing with preview.</td> </tr>
      * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution in-app processing with preview.</td> </tr>
-     * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution two-input in-app processsing.</td> </tr>
+     * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution two-input in-app processing.</td> </tr>
      * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td> <td>Video recording with maximum-size video snapshot</td> </tr>
      * <tr> <td>{@code YUV }</td><td id="rb">{@code 640x480}</td> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Standard video recording plus maximum-resolution in-app processing.</td> </tr>
      * <tr> <td>{@code YUV }</td><td id="rb">{@code 640x480}</td> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Preview plus two-input maximum-resolution in-app processing.</td> </tr>
@@ -722,7 +722,7 @@
      * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th> </tr>
      * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution GPU processing with preview.</td> </tr>
      * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution in-app processing with preview.</td> </tr>
-     * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution two-input in-app processsing.</td> </tr>
+     * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution two-input in-app processing.</td> </tr>
      * </table><br>
      * </p>
      *
@@ -1137,7 +1137,7 @@
      * <tr><th colspan="13">Additional guaranteed combinations for ULTRA_HIGH_RESOLUTION sensors (YUV / PRIV inputs are guaranteed only if YUV / PRIVATE reprocessing are supported)</th></tr>
      * <tr> <th colspan="3" id="rb">Input</th> <th colspan="3" id="rb">Target 1</th> <th colspan="3" id="rb">Target 2</th>  <th colspan="3" id="rb">Target 3</th> <th rowspan="2">Sample use case(s)</th> </tr>
      * <tr> <th>Type</th><th id="rb"> SC Map</th><th id="rb">Max size</th><th>Type</th><th id="rb"> SC Map</th><th id="rb">Max size</th> <th>Type</th><th id="rb"> SC Map</th><th id="rb">Max size</th> <th>Type</th><th id="rb"> SC Map</th><th id="rb">Max size</th></tr>
-     * <tr> <td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td id="rb">{@code PRIV / YUV}</td><td id="rb">{@code DEFAULT}</td><td id="rb">{@code PREVIEW}</td><td colspan="3" id="rb"></td> <td>RAW remosaic reprocessing with seperate preview</td> </tr>
+     * <tr> <td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td id="rb">{@code PRIV / YUV}</td><td id="rb">{@code DEFAULT}</td><td id="rb">{@code PREVIEW}</td><td colspan="3" id="rb"></td> <td>RAW remosaic reprocessing with separate preview</td> </tr>
      * <tr> <td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td id="rb">{@code PRIV / YUV}</td><td id="rb">{@code DEFAULT}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code JPEG / YUV}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td> <td>Ultra high res RAW -> JPEG / YUV with seperate preview</td> </tr>
      * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td id="rb">{@code YUV / PRIV}</td><td id="rb">{@code DEFAULT}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code JPEG }</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td> <td> Ultra high res PRIV / YUV -> YUV / JPEG reprocessing with seperate preview</td> </tr>
      * </table><br>
@@ -1260,7 +1260,7 @@
      * settings by calling {@link CaptureRequest.Builder#setPhysicalCameraKey}.</p>
      *
      * <p>Individual physical camera settings will only be honored for camera session
-     * that was initialiazed with corresponding physical camera id output configuration
+     * that was initialized with corresponding physical camera id output configuration
      * {@link OutputConfiguration#setPhysicalCameraId} and the same output targets are
      * also attached in the request by {@link CaptureRequest.Builder#addTarget}.</p>
      *
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 696873f..144b1de 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -256,7 +256,7 @@
 
     /**
      * Similar to getCameraIdList(). However, getCamerIdListNoLazy() necessarily communicates with
-     * cameraserver in order to get the list of camera ids. This is to faciliate testing since some
+     * cameraserver in order to get the list of camera ids. This is to facilitate testing since some
      * camera ids may go 'offline' without callbacks from cameraserver because of changes in
      * SYSTEM_CAMERA permissions (though this is not a changeable permission, tests may call
      * adopt(drop)ShellPermissionIdentity() and effectively change their permissions). This call
@@ -560,7 +560,7 @@
         }
 
         // Query the characteristics of all physical sub-cameras, and combine the multi-resolution
-        // stream configurations. Alternatively, for ultra-high resolution camera, direclty use
+        // stream configurations. Alternatively, for ultra-high resolution camera, directly use
         // its multi-resolution stream configurations. Note that framework derived formats such as
         // HEIC and DEPTH_JPEG aren't supported as multi-resolution input or output formats.
         Set<String> physicalCameraIds = info.getPhysicalCameraIds();
@@ -835,7 +835,7 @@
      * Opening the same camera ID twice in the same application will similarly cause the
      * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback
      * being fired for the {@link CameraDevice} from the first open call and all ongoing tasks
-     * being droppped.</p>
+     * being dropped.</p>
      *
      * <p>Once the camera is successfully opened, {@link CameraDevice.StateCallback#onOpened} will
      * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up
@@ -1759,7 +1759,7 @@
         private final Set<Set<String>> mConcurrentCameraIdCombinations =
                 new ArraySet<Set<String>>();
 
-        // Registered availablility callbacks and their executors
+        // Registered availability callbacks and their executors
         private final ArrayMap<AvailabilityCallback, Executor> mCallbackMap =
             new ArrayMap<AvailabilityCallback, Executor>();
 
@@ -2846,7 +2846,7 @@
                 // Tell listeners that the cameras and torch modes are unavailable and schedule a
                 // reconnection to camera service. When camera service is reconnected, the camera
                 // and torch statuses will be updated.
-                // Iterate from the end to the beginning befcause onStatusChangedLocked removes
+                // Iterate from the end to the beginning because onStatusChangedLocked removes
                 // entries from the ArrayMap.
                 for (int i = mDeviceStatus.size() - 1; i >= 0; i--) {
                     String cameraId = mDeviceStatus.keyAt(i);
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index ea99847..a7e28e2 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -159,7 +159,7 @@
      * Optionally, if {@code filterTags} is not {@code null}, then filter out any keys
      * whose native {@code tag} is not in {@code filterTags}. The {@code filterTags} array will be
      * sorted as a side effect.
-     * {@code includeSynthetic} Includes public syntenthic fields by default.
+     * {@code includeSynthetic} Includes public synthetic fields by default.
      * </p>
      */
      /*package*/ @SuppressWarnings("unchecked")
@@ -2308,7 +2308,7 @@
     /**
      * <p>An external flash has been turned on.</p>
      * <p>It informs the camera device that an external flash has been turned on, and that
-     * metering (and continuous focus if active) should be quickly recaculated to account
+     * metering (and continuous focus if active) should be quickly recalculated to account
      * for the external flash. Otherwise, this mode acts like ON.</p>
      * <p>When the external flash is turned off, AE mode should be changed to one of the
      * other available AE modes.</p>
diff --git a/core/java/android/hardware/camera2/CameraOfflineSession.java b/core/java/android/hardware/camera2/CameraOfflineSession.java
index 312559c..c219886 100644
--- a/core/java/android/hardware/camera2/CameraOfflineSession.java
+++ b/core/java/android/hardware/camera2/CameraOfflineSession.java
@@ -152,7 +152,7 @@
      *
      * <p>Closing a session is idempotent; closing more than once has no effect.</p>
      *
-     * @throws IllegalStateException if the offline sesion is not ready.
+     * @throws IllegalStateException if the offline session is not ready.
      */
     @Override
     public abstract void close();
diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java
index cc484ea..1ae2fe1 100644
--- a/core/java/android/hardware/camera2/DngCreator.java
+++ b/core/java/android/hardware/camera2/DngCreator.java
@@ -482,7 +482,7 @@
     }
 
     private static final int DEFAULT_PIXEL_STRIDE = 2; // bytes per sample
-    private static final int BYTES_PER_RGB_PIX = 3; // byts per pixel
+    private static final int BYTES_PER_RGB_PIX = 3; // bytes per pixel
 
     // TIFF tag values needed to map between public API and TIFF spec
     private static final int TAG_ORIENTATION_UNKNOWN = 9;
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
index 34d016a..7c54a9b 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
@@ -36,4 +36,5 @@
     int surfaceGroupId;
     String physicalCameraId;
     List<CameraOutputConfig> sharedSurfaceConfigs;
+    boolean isMultiResolutionOutput;
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 2fa8b87..cfade55 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -258,6 +258,9 @@
             OutputConfiguration cameraOutput = new OutputConfiguration(output.surfaceGroupId,
                     outputSurface);
 
+            if (output.isMultiResolutionOutput) {
+                cameraOutput.setMultiResolutionOutput();
+            }
             if ((output.sharedSurfaceConfigs != null) && !output.sharedSurfaceConfigs.isEmpty()) {
                 cameraOutput.enableSurfaceSharing();
                 for (CameraOutputConfig sharedOutputConfig : output.sharedSurfaceConfigs) {
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 86c453b..0a4a1f0 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -607,7 +607,7 @@
         new StreamCombinationTemplate(new StreamTemplate [] {
                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM),
                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
-                "Maximum-resolution two-input in-app processsing"),
+                "Maximum-resolution two-input in-app processing"),
         new StreamCombinationTemplate(new StreamTemplate [] {
                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
@@ -891,7 +891,7 @@
         new StreamCombinationTemplate(new StreamTemplate [] {
                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s720p),
                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.s1440p)},
-                "Standard stil image capture"),
+                "Standard still image capture"),
         new StreamCombinationTemplate(new StreamTemplate [] {
                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s720p),
                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.s1440p)},
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 857f62d..21540bf 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -165,7 +165,7 @@
      * device runs in fixed frame rate. The timestamp is roughly in the same time base as
      * {@link android.os.SystemClock#uptimeMillis}.</li>
      * <li> For an output surface of MediaRecorder, MediaCodec, or ImageReader with {@link
-     * android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE} usge flag, the timestamp base is
+     * android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE} usage flag, the timestamp base is
      * {@link #TIMESTAMP_BASE_MONOTONIC}, which is roughly the same time base as
      * {@link android.os.SystemClock#uptimeMillis}.</li>
      * <li> For all other cases, the timestamp base is {@link #TIMESTAMP_BASE_SENSOR}, the same
@@ -418,7 +418,7 @@
      *         call, or no non-negative group ID has been set.
      * @hide
      */
-    void setMultiResolutionOutput() {
+    public void setMultiResolutionOutput() {
         if (mIsShared) {
             throw new IllegalStateException("Multi-resolution output flag must not be set for " +
                     "configuration with surface sharing");
@@ -654,7 +654,7 @@
             mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE;
         } else {
             mSurfaceType = SURFACE_TYPE_UNKNOWN;
-            throw new IllegalArgumentException("Unknow surface source class type");
+            throw new IllegalArgumentException("Unknown surface source class type");
         }
 
         if (surfaceSize.getWidth() == 0 || surfaceSize.getHeight() == 0) {
@@ -715,7 +715,7 @@
      * The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView,
      * MediaRecorder, MediaCodec, or implementation defined ImageReader.</p>
      *
-     * <p>This function must not be called from OuptutConfigurations created by {@link
+     * <p>This function must not be called from OutputConfigurations created by {@link
      * #createInstancesForMultiResolutionOutput}.</p>
      *
      * @throws IllegalStateException If this OutputConfiguration is created via {@link
@@ -934,7 +934,7 @@
      *
      * <p> Surfaces added via calls to {@link #addSurface} can also be removed from the
      *  OutputConfiguration. The only notable exception is the surface associated with
-     *  the OutputConfigration see {@link #getSurface} which was passed as part of the constructor
+     *  the OutputConfiguration see {@link #getSurface} which was passed as part of the constructor
      *  or was added first in the deferred case
      *  {@link OutputConfiguration#OutputConfiguration(Size, Class)}.</p>
      *
@@ -962,7 +962,7 @@
      * for scenarios where the immediate consumer target isn't sufficient to indicate the stream's
      * usage.</p>
      *
-     * <p>The main difference beteween stream use case and capture intent is that the former
+     * <p>The main difference between stream use case and capture intent is that the former
      * enables the camera device to optimize camera hardware and software pipelines based on user
      * scenarios for each stream, whereas the latter is mainly a hint to camera to decide
      * optimal 3A strategy that's applicable to the whole session. The camera device carries out
@@ -1123,7 +1123,7 @@
      * CameraCharacteristics#SENSOR_READOUT_TIMESTAMP} is
      * {@link CameraMetadata#SENSOR_READOUT_TIMESTAMP_HARDWARE}.</p>
      *
-     * <p>As long as readout timestamp is supported, if the timestamp base isi
+     * <p>As long as readout timestamp is supported, if the timestamp base is
      * {@link #TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED}, or if the timestamp base is DEFAULT for a
      * SurfaceView output, the image timestamps for the output are always readout time regardless
      * of whether this function is called.</p>
@@ -1420,9 +1420,9 @@
      */
     @Override
     public int hashCode() {
-        // Need ensure that the hashcode remains unchanged after adding a deferred surface. Otherwise
-        // the deferred output configuration will be lost in the camera streammap after the deferred
-        // surface is set.
+        // Need ensure that the hashcode remains unchanged after adding a deferred surface.
+        // Otherwise the deferred output configuration will be lost in the camera stream map
+        // after the deferred surface is set.
         if (mIsDeferredConfig) {
             return HashCodeHelpers.hashCode(
                     mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace,
@@ -1446,7 +1446,7 @@
     private static final String TAG = "OutputConfiguration";
 
     // A surfaceGroupId counter used for MultiResolutionImageReader. Its value is
-    // incremented everytime {@link createInstancesForMultiResolutionOutput} is called.
+    // incremented every time {@link createInstancesForMultiResolutionOutput} is called.
     private static int MULTI_RESOLUTION_GROUP_ID_COUNTER = 0;
 
     private ArrayList<Surface> mSurfaces;
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 5a48176..aabe149 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -1790,7 +1790,7 @@
      *
      * <p>{@code ValidOutputFormatsForInput([in:%s(%d), out:%s(%d), ... %s(%d)],
      * ... [in:%s(%d), out:%s(%d), ... %s(%d)])}, where {@code [in:%s(%d), out:%s(%d), ... %s(%d)]}
-     * represents an input fomat and its valid output formats.</p>
+     * represents an input format and its valid output formats.</p>
      *
      * <p>{@code HighSpeedVideoConfigurations([w:%d, h:%d, min_fps:%d, max_fps:%d],
      * ... [w:%d, h:%d, min_fps:%d, max_fps:%d])}, where
diff --git a/core/java/android/hardware/display/HdrConversionMode.java b/core/java/android/hardware/display/HdrConversionMode.java
index 49e5eff..5fccb5e 100644
--- a/core/java/android/hardware/display/HdrConversionMode.java
+++ b/core/java/android/hardware/display/HdrConversionMode.java
@@ -29,9 +29,6 @@
 /**
  * Describes the HDR conversion mode for a device.
  *
- * This class is used when user changes the HDR conversion mode of the device via
- * {@link DisplayManager#setHdrConversionMode(HdrConversionMode)}.
- * <p>
  * HDR conversion mode has a conversionMode and preferredHdrOutputType. </p><p>
  * The conversionMode can be one of:
  * {@link HdrConversionMode#HDR_CONVERSION_UNSUPPORTED} : HDR conversion is unsupported. In this
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 490e55b..03d6d91 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -161,9 +161,8 @@
     }
 
     /**
-     * Returns the recording session associated with this VirtualDisplay. Only used for
+     * Returns the recording session associated with this {@link VirtualDisplay}. Only used for
      * recording via {@link MediaProjection}.
-     *
      * @hide
      */
     @Nullable
@@ -438,7 +437,7 @@
          *
          * <p>For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
          * a 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded up
-         * down to a divisor of the physical display. If unset or zero, the virtual display will be
+         * to a divisor of the physical display. If unset or zero, the virtual display will be
          * refreshed at the physical display refresh rate.
          *
          * @see Display#getRefreshRate()
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index b478a379..af09a06 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -1295,19 +1295,31 @@
      * value, such as 256MB or 32GB. This avoids showing weird values like
      * "29.5GB" in UI.
      *
+     * Some storage devices are still using GiB (powers of 1024) over
+     * GB (powers of 1000) measurements and this method takes it into account.
+     *
+     * Round ranges:
+     * ...
+     * [256 GiB + 1; 512 GiB] -> 512 GB
+     * [512 GiB + 1; 1 TiB]   -> 1 TB
+     * [1 TiB + 1; 2 TiB]     -> 2 TB
+     * etc
+     *
      * @hide
      */
     public static long roundStorageSize(long size) {
         long val = 1;
-        long pow = 1;
-        while ((val * pow) < size) {
+        long kiloPow = 1;
+        long kibiPow = 1;
+        while ((val * kibiPow) < size) {
             val <<= 1;
             if (val > 512) {
                 val = 1;
-                pow *= 1000;
+                kibiPow *= 1024;
+                kiloPow *= 1000;
             }
         }
-        return val * pow;
+        return val * kiloPow;
     }
 
     private static long toBytes(long value, String unit) {
diff --git a/core/java/android/os/PermissionEnforcer.java b/core/java/android/os/PermissionEnforcer.java
index 221e89a..310ceb3 100644
--- a/core/java/android/os/PermissionEnforcer.java
+++ b/core/java/android/os/PermissionEnforcer.java
@@ -18,9 +18,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemService;
+import android.app.AppOpsManager;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.content.PermissionChecker;
+import android.content.pm.PackageManager;
 import android.permission.PermissionCheckerManager;
 
 /**
@@ -40,6 +42,7 @@
 public class PermissionEnforcer {
 
     private final Context mContext;
+    private static final String ACCESS_DENIED = "Access denied, requires: ";
 
     /** Protected constructor. Allows subclasses to instantiate an object
      *  without using a Context.
@@ -59,11 +62,42 @@
             mContext, permission, PermissionChecker.PID_UNKNOWN, source, "" /* message */);
     }
 
+    @SuppressWarnings("AndroidFrameworkClientSidePermissionCheck")
+    @PermissionCheckerManager.PermissionResult
+    protected int checkPermission(@NonNull String permission, int pid, int uid) {
+        if (mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
+            return PermissionCheckerManager.PERMISSION_GRANTED;
+        }
+        return PermissionCheckerManager.PERMISSION_HARD_DENIED;
+    }
+
+    private boolean anyAppOps(@NonNull String[] permissions) {
+        for (String permission : permissions) {
+            if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public void enforcePermission(@NonNull String permission, @NonNull
             AttributionSource source) throws SecurityException {
         int result = checkPermission(permission, source);
         if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Access denied, requires: " + permission);
+            throw new SecurityException(ACCESS_DENIED + permission);
+        }
+    }
+
+    public void enforcePermission(@NonNull String permission, int pid, int uid)
+            throws SecurityException {
+        if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+            AttributionSource source = new AttributionSource(uid, null, null);
+            enforcePermission(permission, source);
+            return;
+        }
+        int result = checkPermission(permission, pid, uid);
+        if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+            throw new SecurityException(ACCESS_DENIED + permission);
         }
     }
 
@@ -72,7 +106,23 @@
         for (String permission : permissions) {
             int result = checkPermission(permission, source);
             if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Access denied, requires: allOf={"
+                throw new SecurityException(ACCESS_DENIED + "allOf={"
+                        + String.join(", ", permissions) + "}");
+            }
+        }
+    }
+
+    public void enforcePermissionAllOf(@NonNull String[] permissions,
+            int pid, int uid) throws SecurityException {
+        if (anyAppOps(permissions)) {
+            AttributionSource source = new AttributionSource(uid, null, null);
+            enforcePermissionAllOf(permissions, source);
+            return;
+        }
+        for (String permission : permissions) {
+            int result = checkPermission(permission, pid, uid);
+            if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+                throw new SecurityException(ACCESS_DENIED + "allOf={"
                         + String.join(", ", permissions) + "}");
             }
         }
@@ -86,7 +136,24 @@
                 return;
             }
         }
-        throw new SecurityException("Access denied, requires: anyOf={"
+        throw new SecurityException(ACCESS_DENIED + "anyOf={"
+                + String.join(", ", permissions) + "}");
+    }
+
+    public void enforcePermissionAnyOf(@NonNull String[] permissions,
+            int pid, int uid) throws SecurityException {
+        if (anyAppOps(permissions)) {
+            AttributionSource source = new AttributionSource(uid, null, null);
+            enforcePermissionAnyOf(permissions, source);
+            return;
+        }
+        for (String permission : permissions) {
+            int result = checkPermission(permission, pid, uid);
+            if (result == PermissionCheckerManager.PERMISSION_GRANTED) {
+                return;
+            }
+        }
+        throw new SecurityException(ACCESS_DENIED + "anyOf={"
                 + String.join(", ", permissions) + "}");
     }
 
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index ea86fcc..8069414 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -357,16 +357,6 @@
             Log.e(TAG, "Failed to get capabilities: ", e);
         }
 
-        try {
-            String[] discovered =
-                    metadata.getStringArray(CredentialProviderService.CAPABILITY_META_DATA_KEY);
-            if (discovered != null) {
-                capabilities.addAll(Arrays.asList(discovered));
-            }
-        } catch (Resources.NotFoundException | NullPointerException e) {
-            Log.e(TAG, "Failed to get capabilities: ", e);
-        }
-
         if (capabilities.size() == 0) {
             Log.e(TAG, "No capabilities found for provider:" + serviceInfo);
             return output;
diff --git a/core/java/android/util/DataUnit.java b/core/java/android/util/DataUnit.java
index cf045b8..cc33af3 100644
--- a/core/java/android/util/DataUnit.java
+++ b/core/java/android/util/DataUnit.java
@@ -36,9 +36,11 @@
     KILOBYTES { @Override public long toBytes(long v) { return v * 1_000; } },
     MEGABYTES { @Override public long toBytes(long v) { return v * 1_000_000; } },
     GIGABYTES { @Override public long toBytes(long v) { return v * 1_000_000_000; } },
+    TERABYTES { @Override public long toBytes(long v) { return v * 1_000_000_000_000L; } },
     KIBIBYTES { @Override public long toBytes(long v) { return v * 1_024; } },
     MEBIBYTES { @Override public long toBytes(long v) { return v * 1_048_576; } },
-    GIBIBYTES { @Override public long toBytes(long v) { return v * 1_073_741_824; } };
+    GIBIBYTES { @Override public long toBytes(long v) { return v * 1_073_741_824; } },
+    TEBIBYTES { @Override public long toBytes(long v) { return v * 1_099_511_627_776L; } };
 
     public long toBytes(long v) {
         throw new AbstractMethodError();
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 6201b3a..4c6bd67 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -234,8 +234,8 @@
         DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
         DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true");
         DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "true");
-        DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "false");
-        DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "false");
+        DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true");
+        DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index cc83dec..cd03d83 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -71,7 +71,7 @@
      * Creates a new SparseArray containing no mappings.
      */
     public SparseArray() {
-        this(10);
+        this(0);
     }
 
     /**
diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java
index c145b20..12a9900 100644
--- a/core/java/android/util/SparseBooleanArray.java
+++ b/core/java/android/util/SparseBooleanArray.java
@@ -51,7 +51,7 @@
      * Creates a new SparseBooleanArray containing no mappings.
      */
     public SparseBooleanArray() {
-        this(10);
+        this(0);
     }
 
     /**
diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java
index ee2e3ce..4b0cbe4 100644
--- a/core/java/android/util/SparseDoubleArray.java
+++ b/core/java/android/util/SparseDoubleArray.java
@@ -50,7 +50,7 @@
 
     /** Creates a new SparseDoubleArray containing no mappings. */
     public SparseDoubleArray() {
-        this(10);
+        this(0);
     }
 
     /**
diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java
index d4f6685..0e98c28 100644
--- a/core/java/android/util/SparseIntArray.java
+++ b/core/java/android/util/SparseIntArray.java
@@ -58,7 +58,7 @@
      * Creates a new SparseIntArray containing no mappings.
      */
     public SparseIntArray() {
-        this(10);
+        this(0);
     }
 
     /**
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index b739e37..e86b647 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -51,7 +51,7 @@
      * Creates a new SparseLongArray containing no mappings.
      */
     public SparseLongArray() {
-        this(10);
+        this(0);
     }
 
     /**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 4895aed..0db52aa 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3896,10 +3896,12 @@
                 float currentBufferRatio, float desiredRatio) {
             checkPreconditions(sc);
             if (!Float.isFinite(currentBufferRatio) || currentBufferRatio < 1.0f) {
-                throw new IllegalArgumentException("currentBufferRatio must be finite && >= 1.0f");
+                throw new IllegalArgumentException(
+                        "currentBufferRatio must be finite && >= 1.0f; got " + currentBufferRatio);
             }
             if (!Float.isFinite(desiredRatio) || desiredRatio < 1.0f) {
-                throw new IllegalArgumentException("desiredRatio must be finite && >= 1.0f");
+                throw new IllegalArgumentException(
+                        "desiredRatio must be finite && >= 1.0f; got " + desiredRatio);
             }
             nativeSetExtendedRangeBrightness(mNativeObject, sc.mNativeObject, currentBufferRatio,
                     desiredRatio);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index d8b6b7b..b46a68c 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -851,14 +851,10 @@
             }
             mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
 
-            // Only control visibility if we're not hardware-accelerated. Otherwise we'll
-            // let renderthread drive since offscreen SurfaceControls should not be visible.
-            if (!isHardwareAccelerated()) {
-                if (mViewVisibility) {
-                    surfaceUpdateTransaction.show(mSurfaceControl);
-                } else {
-                    surfaceUpdateTransaction.hide(mSurfaceControl);
-                }
+            if (mViewVisibility) {
+                surfaceUpdateTransaction.show(mSurfaceControl);
+            } else {
+                surfaceUpdateTransaction.hide(mSurfaceControl);
             }
 
             updateBackgroundVisibility(surfaceUpdateTransaction);
@@ -1421,10 +1417,12 @@
     }
 
     private final Rect mRTLastReportedPosition = new Rect();
+    private final Point mRTLastReportedSurfaceSize = new Point();
 
     private class SurfaceViewPositionUpdateListener implements RenderNode.PositionUpdateListener {
         private final int mRtSurfaceWidth;
         private final int mRtSurfaceHeight;
+        private boolean mRtFirst = true;
         private final SurfaceControl.Transaction mPositionChangedTransaction =
                 new SurfaceControl.Transaction();
 
@@ -1435,6 +1433,15 @@
 
         @Override
         public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
+            if (!mRtFirst && (mRTLastReportedPosition.left == left
+                    && mRTLastReportedPosition.top == top
+                    && mRTLastReportedPosition.right == right
+                    && mRTLastReportedPosition.bottom == bottom
+                    && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth
+                    && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight)) {
+                return;
+            }
+            mRtFirst = false;
             try {
                 if (DEBUG_POSITION) {
                     Log.d(TAG, String.format(
@@ -1445,8 +1452,8 @@
                 }
                 synchronized (mSurfaceControlLock) {
                     if (mSurfaceControl == null) return;
-
                     mRTLastReportedPosition.set(left, top, right, bottom);
+                    mRTLastReportedSurfaceSize.set(mRtSurfaceWidth, mRtSurfaceHeight);
                     onSetSurfacePositionAndScale(mPositionChangedTransaction, mSurfaceControl,
                             mRTLastReportedPosition.left /*positionLeft*/,
                             mRTLastReportedPosition.top /*positionTop*/,
@@ -1454,8 +1461,10 @@
                                     / (float) mRtSurfaceWidth /*postScaleX*/,
                             mRTLastReportedPosition.height()
                                     / (float) mRtSurfaceHeight /*postScaleY*/);
-
-                    mPositionChangedTransaction.show(mSurfaceControl);
+                    if (mViewVisibility) {
+                        // b/131239825
+                        mPositionChangedTransaction.show(mSurfaceControl);
+                    }
                 }
                 applyOrMergeTransaction(mPositionChangedTransaction, frameNumber);
             } catch (Exception ex) {
@@ -1481,6 +1490,7 @@
                         System.identityHashCode(this), frameNumber));
             }
             mRTLastReportedPosition.setEmpty();
+            mRTLastReportedSurfaceSize.set(-1, -1);
 
             // positionLost can be called while UI thread is un-paused.
             synchronized (mSurfaceControlLock) {
diff --git a/core/java/android/view/TEST_MAPPING b/core/java/android/view/TEST_MAPPING
index ecb98f9..1e39716 100644
--- a/core/java/android/view/TEST_MAPPING
+++ b/core/java/android/view/TEST_MAPPING
@@ -42,6 +42,9 @@
   ],
   "imports": [
     {
+      "path": "cts/tests/surfacecontrol"
+    },
+    {
       "path": "cts/tests/tests/uirendering"
     }
   ]
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
index e7207fa..c4d43bc 100644
--- a/core/java/android/view/inputmethod/HandwritingGesture.java
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -22,6 +22,7 @@
 import android.annotation.TestApi;
 import android.graphics.RectF;
 import android.inputmethodservice.InputMethodService;
+import android.os.CancellationSignal;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.MotionEvent;
@@ -33,18 +34,20 @@
 import java.util.function.IntConsumer;
 
 /**
- * Base class for Stylus handwriting gesture.
- *
+ * Base class for stylus handwriting gestures.
+ * <p>
  * During a stylus handwriting session, user can perform a stylus gesture operation like
  * {@link SelectGesture}, {@link DeleteGesture}, {@link InsertGesture} on an
- * area of text. IME is responsible for listening to Stylus {@link MotionEvent} using
+ * area of text. IME is responsible for listening to stylus {@link MotionEvent}s using
  * {@link InputMethodService#onStylusHandwritingMotionEvent} and interpret if it can translate to a
  * gesture operation.
- * While creating Gesture operations {@link SelectGesture}, {@link DeleteGesture},
- * , {@code Granularity} helps pick the correct granular level of text like word level
+ * <p>
+ * While creating gesture operations {@link SelectGesture} and {@link DeleteGesture},
+ * {@code Granularity} helps pick the correct granular level of text like word level
  * {@link #GRANULARITY_WORD}, or character level {@link #GRANULARITY_CHARACTER}.
  *
  * @see InputConnection#performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)
+ * @see InputConnection#previewHandwritingGesture(PreviewableHandwritingGesture, CancellationSignal)
  * @see InputMethodService#onStartStylusHandwriting()
  */
 public abstract class HandwritingGesture {
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index 0d02683..a1d571f 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -1,4 +1,6 @@
 per-file *AppOp* = file:/core/java/android/permission/OWNERS
+per-file UnlaunchableAppActivity.java = file:/core/java/android/app/admin/WorkProfile_OWNERS
+per-file IntentForwarderActivity.java = file:/core/java/android/app/admin/WorkProfile_OWNERS
 per-file *Resolver* = file:/packages/SystemUI/OWNERS
 per-file *Chooser* = file:/packages/SystemUI/OWNERS
 per-file SimpleIconFactory.java = file:/packages/SystemUI/OWNERS
diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java
index afdbdc8..4a46d91 100644
--- a/core/java/com/android/internal/expresslog/Counter.java
+++ b/core/java/com/android/internal/expresslog/Counter.java
@@ -36,6 +36,16 @@
     }
 
     /**
+     * Increments Telemetry Express Counter metric by 1
+     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
+     * @param uid used as a dimension for the count metric
+     * @hide
+     */
+    public static void logIncrementWithUid(@NonNull String metricId, int uid) {
+        logIncrementWithUid(metricId, uid, 1);
+    }
+
+    /**
      * Increments Telemetry Express Counter metric by arbitrary value
      * @param metricId to log, no-op if metricId is not defined in the TeX catalog
      * @param amount to increment counter
@@ -45,4 +55,17 @@
         final long metricIdHash = Utils.hashString(metricId);
         FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount);
     }
+
+    /**
+     * Increments Telemetry Express Counter metric by arbitrary value
+     * @param metricId to log, no-op if metricId is not defined in the TeX catalog
+     * @param uid used as a dimension for the count metric
+     * @param amount to increment counter
+     * @hide
+     */
+    public static void logIncrementWithUid(@NonNull String metricId, int uid, long amount) {
+        final long metricIdHash = Utils.hashString(metricId);
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid);
+    }
 }
diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java
index 2f3b662..65fbb03 100644
--- a/core/java/com/android/internal/expresslog/Histogram.java
+++ b/core/java/com/android/internal/expresslog/Histogram.java
@@ -16,10 +16,14 @@
 
 package com.android.internal.expresslog;
 
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 
 import com.android.internal.util.FrameworkStatsLog;
 
+import java.util.Arrays;
+
 /** Histogram encapsulates StatsD write API calls */
 public final class Histogram {
 
@@ -28,7 +32,8 @@
 
     /**
      * Creates Histogram metric logging wrapper
-     * @param metricId to log, logging will be no-op if metricId is not defined in the TeX catalog
+     *
+     * @param metricId   to log, logging will be no-op if metricId is not defined in the TeX catalog
      * @param binOptions to calculate bin index for samples
      * @hide
      */
@@ -39,6 +44,7 @@
 
     /**
      * Logs increment sample count for automatically calculated bin
+     *
      * @param sample value
      * @hide
      */
@@ -52,6 +58,7 @@
     public interface BinOptions {
         /**
          * Returns bins count to be used by a histogram
+         *
          * @return bins count used to initialize Options, including overflow & underflow bins
          * @hide
          */
@@ -61,6 +68,7 @@
          * Returns bin index for the input sample value
          * index == 0 stands for underflow
          * index == getBinsCount() - 1 stands for overflow
+         *
          * @return zero based index
          * @hide
          */
@@ -76,17 +84,19 @@
         private final float mBinSize;
 
         /**
-         * Creates otpions for uniform (linear) sized bins
-         * @param binCount amount of histogram bins. 2 bin indexes will be calculated
-         *                 automatically to represent undeflow & overflow bins
-         * @param minValue is included in the first bin, values less than minValue
-         *                 go to underflow bin
+         * Creates options for uniform (linear) sized bins
+         *
+         * @param binCount          amount of histogram bins. 2 bin indexes will be calculated
+         *                          automatically to represent underflow & overflow bins
+         * @param minValue          is included in the first bin, values less than minValue
+         *                          go to underflow bin
          * @param exclusiveMaxValue is included in the overflow bucket. For accurate
-                                    measure up to kMax, then exclusiveMaxValue
+         *                          measure up to kMax, then exclusiveMaxValue
          *                          should be set to kMax + 1
          * @hide
          */
-        public UniformOptions(int binCount, float minValue, float exclusiveMaxValue) {
+        public UniformOptions(@IntRange(from = 1) int binCount, float minValue,
+                float exclusiveMaxValue) {
             if (binCount < 1) {
                 throw new IllegalArgumentException("Bin count should be positive number");
             }
@@ -99,7 +109,7 @@
             mExclusiveMaxValue = exclusiveMaxValue;
             mBinSize = (mExclusiveMaxValue - minValue) / binCount;
 
-            // Implicitly add 2 for the extra undeflow & overflow bins
+            // Implicitly add 2 for the extra underflow & overflow bins
             mBinCount = binCount + 2;
         }
 
@@ -120,4 +130,92 @@
             return (int) ((sample - mMinValue) / mBinSize + 1);
         }
     }
+
+    /** Used by Histogram to map data sample to corresponding bin for scaled bins */
+    public static final class ScaledRangeOptions implements BinOptions {
+        // store minimum value per bin
+        final long[] mBins;
+
+        /**
+         * Creates options for scaled range bins
+         *
+         * @param binCount      amount of histogram bins. 2 bin indexes will be calculated
+         *                      automatically to represent underflow & overflow bins
+         * @param minValue      is included in the first bin, values less than minValue
+         *                      go to underflow bin
+         * @param firstBinWidth used to represent first bin width and as a reference to calculate
+         *                      width for consecutive bins
+         * @param scaleFactor   used to calculate width for consecutive bins
+         * @hide
+         */
+        public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue,
+                @FloatRange(from = 1.f) float firstBinWidth,
+                @FloatRange(from = 1.f) float scaleFactor) {
+            if (binCount < 1) {
+                throw new IllegalArgumentException("Bin count should be positive number");
+            }
+
+            if (firstBinWidth < 1.f) {
+                throw new IllegalArgumentException(
+                        "First bin width invalid (should be 1.f at minimum)");
+            }
+
+            if (scaleFactor < 1.f) {
+                throw new IllegalArgumentException(
+                        "Scaled factor invalid (should be 1.f at minimum)");
+            }
+
+            // precalculating bins ranges (no need to create a bin for underflow reference value)
+            mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor);
+        }
+
+        @Override
+        public int getBinsCount() {
+            return mBins.length + 1;
+        }
+
+        @Override
+        public int getBinForSample(float sample) {
+            if (sample < mBins[0]) {
+                // goes to underflow
+                return 0;
+            } else if (sample >= mBins[mBins.length - 1]) {
+                // goes to overflow
+                return mBins.length;
+            }
+
+            return lower_bound(mBins, (long) sample) + 1;
+        }
+
+        // To find lower bound using binary search implementation of Arrays utility class
+        private static int lower_bound(long[] array, long sample) {
+            int index = Arrays.binarySearch(array, sample);
+            // If key is not present in the array
+            if (index < 0) {
+                // Index specify the position of the key when inserted in the sorted array
+                // so the element currently present at this position will be the lower bound
+                return Math.abs(index) - 2;
+            }
+            return index;
+        }
+
+        private static long[] initBins(int count, int minValue, float firstBinWidth,
+                float scaleFactor) {
+            long[] bins = new long[count];
+            bins[0] = minValue;
+            double lastWidth = firstBinWidth;
+            for (int i = 1; i < count; i++) {
+                // current bin minValue = previous bin width * scaleFactor
+                double currentBinMinValue = bins[i - 1] + lastWidth;
+                if (currentBinMinValue > Integer.MAX_VALUE) {
+                    throw new IllegalArgumentException(
+                        "Attempted to create a bucket larger than maxint");
+                }
+
+                bins[i] = (long) currentBinMinValue;
+                lastWidth *= scaleFactor;
+            }
+            return bins;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS b/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
new file mode 100644
index 0000000..7755000
--- /dev/null
+++ b/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
@@ -0,0 +1,3 @@
+# TODO(b/274465475): Migrate LatencyTracker testing to its own module
+marcinoc@google.com
+ilkos@google.com
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 43a9f5f..3c06755 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -48,6 +48,7 @@
 import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX;
 
 import android.Manifest;
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -55,7 +56,6 @@
 import android.app.ActivityThread;
 import android.content.Context;
 import android.os.Build;
-import android.os.ConditionVariable;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.provider.DeviceConfig;
@@ -79,7 +79,7 @@
  * Class to track various latencies in SystemUI. It then writes the latency to statsd and also
  * outputs it to logcat so these latencies can be captured by tests and then used for dashboards.
  * <p>
- * This is currently only in Keyguard so it can be shared between SystemUI and Keyguard, but
+ * This is currently only in Keyguard. It can be shared between SystemUI and Keyguard, but
  * eventually we'd want to merge these two packages together so Keyguard can use common classes
  * that are shared with SystemUI.
  */
@@ -285,8 +285,6 @@
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN,
     };
 
-    private static LatencyTracker sLatencyTracker;
-
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private final SparseArray<Session> mSessions = new SparseArray<>();
@@ -294,20 +292,21 @@
     private final SparseArray<ActionProperties> mActionPropertiesMap = new SparseArray<>();
     @GuardedBy("mLock")
     private boolean mEnabled;
-    @VisibleForTesting
-    public final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable();
 
-    public static LatencyTracker getInstance(Context context) {
-        if (sLatencyTracker == null) {
-            synchronized (LatencyTracker.class) {
-                if (sLatencyTracker == null) {
-                    sLatencyTracker = new LatencyTracker();
-                }
-            }
-        }
-        return sLatencyTracker;
+    // Wrapping this in a holder class achieves lazy loading behavior
+    private static final class SLatencyTrackerHolder {
+        private static final LatencyTracker sLatencyTracker = new LatencyTracker();
     }
 
+    public static LatencyTracker getInstance(Context context) {
+        return SLatencyTrackerHolder.sLatencyTracker;
+    }
+
+    /**
+     * Constructor for LatencyTracker
+     *
+     * <p>This constructor is only visible for test classes to inject their own consumer callbacks
+     */
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     @VisibleForTesting
     public LatencyTracker() {
@@ -349,11 +348,8 @@
                         properties.getInt(actionName + TRACE_THRESHOLD_SUFFIX,
                                 legacyActionTraceThreshold)));
             }
-            if (DEBUG) {
-                Log.d(TAG, "updated action properties: " + mActionPropertiesMap);
-            }
+            onDeviceConfigPropertiesUpdated(mActionPropertiesMap);
         }
-        mDeviceConfigPropertiesUpdated.open();
     }
 
     /**
@@ -477,7 +473,7 @@
      */
     public void onActionStart(@Action int action, String tag) {
         synchronized (mLock) {
-            if (!isEnabled()) {
+            if (!isEnabled(action)) {
                 return;
             }
             // skip if the action is already instrumenting.
@@ -501,7 +497,7 @@
      */
     public void onActionEnd(@Action int action) {
         synchronized (mLock) {
-            if (!isEnabled()) {
+            if (!isEnabled(action)) {
                 return;
             }
             Session session = mSessions.get(action);
@@ -540,6 +536,24 @@
     }
 
     /**
+     * Testing API to get the time when a given action was started.
+     *
+     * @param action Action which to retrieve start time from
+     * @return Elapsed realtime timestamp when the action started. -1 if the action is not active.
+     * @hide
+     */
+    @VisibleForTesting
+    @ElapsedRealtimeLong
+    public long getActiveActionStartTime(@Action int action) {
+        synchronized (mLock) {
+            if (mSessions.contains(action)) {
+                return mSessions.get(action).mStartRtc;
+            }
+            return -1;
+        }
+    }
+
+    /**
      * 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.
@@ -549,6 +563,9 @@
         boolean shouldSample;
         int traceThreshold;
         synchronized (mLock) {
+            if (!isEnabled(action)) {
+                return;
+            }
             ActionProperties actionProperties = mActionPropertiesMap.get(action);
             if (actionProperties == null) {
                 return;
@@ -559,28 +576,24 @@
             traceThreshold = actionProperties.getTraceThreshold();
         }
 
-        if (traceThreshold > 0 && duration >= traceThreshold) {
-            PerfettoTrigger.trigger(getTraceTriggerNameForAction(action));
+        boolean shouldTriggerPerfettoTrace = traceThreshold > 0 && duration >= traceThreshold;
+
+        if (DEBUG) {
+            Log.i(TAG, "logAction: " + getNameOfAction(STATSD_ACTION[action])
+                    + " duration=" + duration
+                    + " shouldSample=" + shouldSample
+                    + " shouldTriggerPerfettoTrace=" + shouldTriggerPerfettoTrace);
         }
 
-        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 writeToStatsLog Whether to write the measured latency to FrameworkStatsLog.
-     */
-    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) {
-            FrameworkStatsLog.write(
-                    FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED, STATSD_ACTION[action], duration);
+        if (shouldTriggerPerfettoTrace) {
+            onTriggerPerfetto(getTraceTriggerNameForAction(action));
+        }
+        if (shouldSample) {
+            onLogToFrameworkStats(
+                    new FrameworkStatsLogEvent(action, FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED,
+                            STATSD_ACTION[action], duration)
+            );
         }
     }
 
@@ -642,10 +655,10 @@
     }
 
     @VisibleForTesting
-    static class ActionProperties {
+    public static class ActionProperties {
         static final String ENABLE_SUFFIX = "_enable";
         static final String SAMPLE_INTERVAL_SUFFIX = "_sample_interval";
-        // TODO: migrate all usages of the legacy trace theshold property
+        // TODO: migrate all usages of the legacy trace threshold property
         static final String LEGACY_TRACE_THRESHOLD_SUFFIX = "";
         static final String TRACE_THRESHOLD_SUFFIX = "_trace_threshold";
 
@@ -655,7 +668,8 @@
         private final int mSamplingInterval;
         private final int mTraceThreshold;
 
-        ActionProperties(
+        @VisibleForTesting
+        public ActionProperties(
                 @Action int action,
                 boolean enabled,
                 int samplingInterval,
@@ -668,20 +682,24 @@
             this.mTraceThreshold = traceThreshold;
         }
 
+        @VisibleForTesting
         @Action
-        int getAction() {
+        public int getAction() {
             return mAction;
         }
 
-        boolean isEnabled() {
+        @VisibleForTesting
+        public boolean isEnabled() {
             return mEnabled;
         }
 
-        int getSamplingInterval() {
+        @VisibleForTesting
+        public int getSamplingInterval() {
             return mSamplingInterval;
         }
 
-        int getTraceThreshold() {
+        @VisibleForTesting
+        public int getTraceThreshold() {
             return mTraceThreshold;
         }
 
@@ -694,5 +712,103 @@
                     + ", mTraceThreshold=" + mTraceThreshold
                     + "}";
         }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null) {
+                return false;
+            }
+            if (!(o instanceof ActionProperties)) {
+                return false;
+            }
+            ActionProperties that = (ActionProperties) o;
+            return mAction == that.mAction
+                    && mEnabled == that.mEnabled
+                    && mSamplingInterval == that.mSamplingInterval
+                    && mTraceThreshold == that.mTraceThreshold;
+        }
+
+        @Override
+        public int hashCode() {
+            int _hash = 1;
+            _hash = 31 * _hash + mAction;
+            _hash = 31 * _hash + Boolean.hashCode(mEnabled);
+            _hash = 31 * _hash + mSamplingInterval;
+            _hash = 31 * _hash + mTraceThreshold;
+            return _hash;
+        }
+    }
+
+    /**
+     * Testing method intended to be overridden to determine when the LatencyTracker's device
+     * properties are updated.
+     */
+    @VisibleForTesting
+    public void onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties) {
+        if (DEBUG) {
+            Log.d(TAG, "onDeviceConfigPropertiesUpdated: " + actionProperties);
+        }
+    }
+
+    /**
+     * Testing class intended to be overridden to determine when LatencyTracker triggers perfetto.
+     */
+    @VisibleForTesting
+    public void onTriggerPerfetto(String triggerName) {
+        if (DEBUG) {
+            Log.i(TAG, "onTriggerPerfetto: triggerName=" + triggerName);
+        }
+        PerfettoTrigger.trigger(triggerName);
+    }
+
+    /**
+     * Testing method intended to be overridden to determine when LatencyTracker writes to
+     * FrameworkStatsLog.
+     */
+    @VisibleForTesting
+    public void onLogToFrameworkStats(FrameworkStatsLogEvent event) {
+        if (DEBUG) {
+            Log.i(TAG, "onLogToFrameworkStats: event=" + event);
+        }
+        FrameworkStatsLog.write(event.logCode, event.statsdAction, event.durationMillis);
+    }
+
+    /**
+     * Testing class intended to reject what should be written to the {@link FrameworkStatsLog}
+     *
+     * <p>This class is used in {@link #onLogToFrameworkStats(FrameworkStatsLogEvent)} for test code
+     * to observer when and what information is being logged by {@link LatencyTracker}
+     */
+    @VisibleForTesting
+    public static class FrameworkStatsLogEvent {
+
+        @VisibleForTesting
+        public final int action;
+        @VisibleForTesting
+        public final int logCode;
+        @VisibleForTesting
+        public final int statsdAction;
+        @VisibleForTesting
+        public final int durationMillis;
+
+        private FrameworkStatsLogEvent(int action, int logCode, int statsdAction,
+                int durationMillis) {
+            this.action = action;
+            this.logCode = logCode;
+            this.statsdAction = statsdAction;
+            this.durationMillis = durationMillis;
+        }
+
+        @Override
+        public String toString() {
+            return "FrameworkStatsLogEvent{"
+                    + " logCode=" + logCode
+                    + ", statsdAction=" + statsdAction
+                    + ", durationMillis=" + durationMillis
+                    + "}";
+        }
     }
 }
diff --git a/core/java/com/android/internal/util/OWNERS b/core/java/com/android/internal/util/OWNERS
index 1808bd5..9be8ea7 100644
--- a/core/java/com/android/internal/util/OWNERS
+++ b/core/java/com/android/internal/util/OWNERS
@@ -6,3 +6,4 @@
 per-file State* = jchalard@google.com, lorenzo@google.com, satk@google.com
 per-file *Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
 per-file *Screenshot* = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
+per-file *LatencyTracker* = file:/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index d62f1cf..ce806a0 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -2296,7 +2296,9 @@
     // region shared with the child process we reduce the number of pages that
     // transition to the private-dirty state when malloc adjusts the meta-data
     // on each of the pages it is managing after the fork.
-    mallopt(M_PURGE, 0);
+    if (mallopt(M_PURGE_ALL, 0) != 1) {
+      mallopt(M_PURGE, 0);
+    }
   }
 
   pid_t pid = fork();
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 092f6e5..0f1e558 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4482,6 +4482,12 @@
     <permission android:name="android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- Allows specifying candidate credential providers to be queried in Credential Manager
+    get flows, or to be preferred as a default in the Credential Manager create flows.
+     <p>Protection level: normal -->
+    <permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS"
+                android:protectionLevel="normal" />
+
     <!-- Allows a browser to invoke credential manager APIs on behalf of another RP.
         <p>Protection level: normal -->
     <permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ORIGIN"
diff --git a/core/res/TEST_MAPPING b/core/res/TEST_MAPPING
index 9185bae..4d09076 100644
--- a/core/res/TEST_MAPPING
+++ b/core/res/TEST_MAPPING
@@ -1,13 +1,13 @@
 {
     "presubmit": [
         {
-            "name": "CtsPermission2TestCases",
+            "name": "CtsPermissionPolicyTestCases",
             "options": [
                 {
-                    "include-filter": "android.permission2.cts.PermissionPolicyTest#platformPermissionPolicyIsUnaltered"
+                    "include-filter": "android.permissionpolicy.cts.PermissionPolicyTest#platformPermissionPolicyIsUnaltered"
                 },
                 {
-                    "include-filter": "android.permission2.cts.RuntimePermissionProperties"
+                    "include-filter": "android.permissionpolicy.cts.RuntimePermissionProperties"
                 }
             ]
         }
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 16a8bb7..710a70a 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -116,7 +116,8 @@
 
                 <com.android.internal.widget.NotificationVanishingFrameLayout
                     android:layout_width="match_parent"
-                    android:layout_height="@dimen/notification_headerless_line_height"
+                    android:layout_height="wrap_content"
+                    android:minHeight="@dimen/notification_headerless_line_height"
                     >
                     <!-- This is the simplest way to keep this text vertically centered without
                      gravity="center_vertical" which causes jumpiness in expansion animations. -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 80bf795..5bb86dc 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -356,7 +356,7 @@
     <dimen name="notification_headerless_margin_twoline">20dp</dimen>
 
     <!-- The height of each of the 1 or 2 lines in the headerless notification template -->
-    <dimen name="notification_headerless_line_height">24sp</dimen>
+    <dimen name="notification_headerless_line_height">24dp</dimen>
 
     <!-- vertical margin for the headerless notification content -->
     <dimen name="notification_headerless_min_height">56dp</dimen>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index e811bb6..3ea1592 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -20,6 +20,7 @@
         "BinderProxyCountingTestService/src/**/*.java",
         "BinderDeathRecipientHelperApp/src/**/*.java",
         "aidl/**/I*.aidl",
+        ":FrameworksCoreTestDoubles-sources",
     ],
 
     aidl: {
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
index 7a1de0c..a753870 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
@@ -435,9 +435,11 @@
         mActivityRule.runOnUiThread(s::start);
 
         while (!listener.endIsCalled) {
-            boolean passedStartDelay = a1.isStarted() || a2.isStarted() || a3.isStarted() ||
-                    a4.isStarted() || a5.isStarted();
-            assertEquals(passedStartDelay, s.isRunning());
+            mActivityRule.runOnUiThread(() -> {
+                boolean passedStartDelay = a1.isStarted() || a2.isStarted() || a3.isStarted()
+                        || a4.isStarted() || a5.isStarted();
+                assertEquals(passedStartDelay, s.isRunning());
+            });
             Thread.sleep(50);
         }
         assertFalse(s.isRunning());
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
new file mode 100644
index 0000000..43266a5
--- /dev/null
+++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.animation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.PollingCheck;
+import android.view.View;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.filters.MediumTest;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+public class AnimatorSetCallsTest {
+    @Rule
+    public final ActivityScenarioRule<AnimatorSetActivity> mRule =
+            new ActivityScenarioRule<>(AnimatorSetActivity.class);
+
+    private AnimatorSetActivity mActivity;
+    private AnimatorSet mSet1;
+    private AnimatorSet mSet2;
+    private ObjectAnimator mAnimator;
+    private CountListener mListener1;
+    private CountListener mListener2;
+    private CountListener mListener3;
+
+    @Before
+    public void setUp() throws Exception {
+        mRule.getScenario().onActivity((activity) -> {
+            mActivity = activity;
+            View square = mActivity.findViewById(R.id.square1);
+
+            mSet1 = new AnimatorSet();
+            mListener1 = new CountListener();
+            mSet1.addListener(mListener1);
+            mSet1.addPauseListener(mListener1);
+
+            mSet2 = new AnimatorSet();
+            mListener2 = new CountListener();
+            mSet2.addListener(mListener2);
+            mSet2.addPauseListener(mListener2);
+
+            mAnimator = ObjectAnimator.ofFloat(square, "translationX", 0f, 100f);
+            mListener3 = new CountListener();
+            mAnimator.addListener(mListener3);
+            mAnimator.addPauseListener(mListener3);
+            mAnimator.setDuration(1);
+
+            mSet2.play(mAnimator);
+            mSet1.play(mSet2);
+        });
+    }
+
+    @Test
+    public void startEndCalledOnChildren() {
+        mRule.getScenario().onActivity((a) -> mSet1.start());
+        waitForOnUiThread(() -> mListener1.endForward > 0);
+
+        // only startForward and endForward should have been called once
+        mListener1.assertValues(
+                1, 0, 1, 0, 0, 0, 0, 0
+        );
+        mListener2.assertValues(
+                1, 0, 1, 0, 0, 0, 0, 0
+        );
+        mListener3.assertValues(
+                1, 0, 1, 0, 0, 0, 0, 0
+        );
+    }
+
+    @Test
+    public void cancelCalledOnChildren() {
+        mRule.getScenario().onActivity((a) -> {
+            mSet1.start();
+            mSet1.cancel();
+        });
+        waitForOnUiThread(() -> mListener1.endForward > 0);
+
+        // only startForward and endForward should have been called once
+        mListener1.assertValues(
+                1, 0, 1, 0, 1, 0, 0, 0
+        );
+        mListener2.assertValues(
+                1, 0, 1, 0, 1, 0, 0, 0
+        );
+        mListener3.assertValues(
+                1, 0, 1, 0, 1, 0, 0, 0
+        );
+    }
+
+    @Test
+    public void startEndReversedCalledOnChildren() {
+        mRule.getScenario().onActivity((a) -> mSet1.reverse());
+        waitForOnUiThread(() -> mListener1.endReverse > 0);
+
+        // only startForward and endForward should have been called once
+        mListener1.assertValues(
+                0, 1, 0, 1, 0, 0, 0, 0
+        );
+        mListener2.assertValues(
+                0, 1, 0, 1, 0, 0, 0, 0
+        );
+        mListener3.assertValues(
+                0, 1, 0, 1, 0, 0, 0, 0
+        );
+    }
+
+    @Test
+    public void pauseResumeCalledOnChildren() {
+        mRule.getScenario().onActivity((a) -> {
+            mSet1.start();
+            mSet1.pause();
+        });
+        waitForOnUiThread(() -> mListener1.pause > 0);
+
+        // only startForward and pause should have been called once
+        mListener1.assertValues(
+                1, 0, 0, 0, 0, 0, 1, 0
+        );
+        mListener2.assertValues(
+                1, 0, 0, 0, 0, 0, 1, 0
+        );
+        mListener3.assertValues(
+                1, 0, 0, 0, 0, 0, 1, 0
+        );
+
+        mRule.getScenario().onActivity((a) -> mSet1.resume());
+        waitForOnUiThread(() -> mListener1.endForward > 0);
+
+        // resume and endForward should have been called once
+        mListener1.assertValues(
+                1, 0, 1, 0, 0, 0, 1, 1
+        );
+        mListener2.assertValues(
+                1, 0, 1, 0, 0, 0, 1, 1
+        );
+        mListener3.assertValues(
+                1, 0, 1, 0, 0, 0, 1, 1
+        );
+    }
+
+    @Test
+    public void updateOnlyWhileChangingValues() {
+        ArrayList<Float> updateValues = new ArrayList<>();
+        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                updateValues.add((Float) animation.getAnimatedValue());
+            }
+        });
+
+        mSet1.setCurrentPlayTime(0);
+
+        assertEquals(1, updateValues.size());
+        assertEquals(0f, updateValues.get(0), 0f);
+    }
+
+    @Test
+    public void updateOnlyWhileRunning() {
+        ArrayList<Float> updateValues = new ArrayList<>();
+        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                updateValues.add((Float) animation.getAnimatedValue());
+            }
+        });
+
+        mRule.getScenario().onActivity((a) -> {
+            mSet1.start();
+        });
+
+        waitForOnUiThread(() -> mListener1.endForward > 0);
+
+        // the duration is only 1ms, so there should only be two values, 0 and 100.
+        assertEquals(0f, updateValues.get(0), 0f);
+        assertEquals(100f, updateValues.get(updateValues.size() - 1), 0f);
+
+        // now check all the values in the middle, which can never go from 100->0.
+        boolean isAtEnd = false;
+        for (int i = 1; i < updateValues.size() - 1; i++) {
+            float actual = updateValues.get(i);
+            if (actual == 100f) {
+                isAtEnd = true;
+            }
+            float expected = isAtEnd ? 100f : 0f;
+            assertEquals(expected, actual, 0f);
+        }
+    }
+
+    @Test
+    public void pauseResumeSeekingAnimators() {
+        ValueAnimator animator2 = ValueAnimator.ofFloat(0f, 1f);
+        mSet2.play(animator2).after(mAnimator);
+        mSet2.setStartDelay(100);
+        mSet1.setStartDelay(100);
+        mAnimator.setDuration(100);
+
+        mActivity.runOnUiThread(() -> {
+            mSet1.setCurrentPlayTime(0);
+            mSet1.pause();
+
+            // only startForward and pause should have been called once
+            mListener1.assertValues(
+                    1, 0, 0, 0, 0, 0, 1, 0
+            );
+            mListener2.assertValues(
+                    0, 0, 0, 0, 0, 0, 0, 0
+            );
+            mListener3.assertValues(
+                    0, 0, 0, 0, 0, 0, 0, 0
+            );
+
+            mSet1.resume();
+            mListener1.assertValues(
+                    1, 0, 0, 0, 0, 0, 1, 1
+            );
+            mListener2.assertValues(
+                    0, 0, 0, 0, 0, 0, 0, 0
+            );
+            mListener3.assertValues(
+                    0, 0, 0, 0, 0, 0, 0, 0
+            );
+
+            mSet1.setCurrentPlayTime(200);
+
+            // resume and endForward should have been called once
+            mListener1.assertValues(
+                    1, 0, 0, 0, 0, 0, 1, 1
+            );
+            mListener2.assertValues(
+                    1, 0, 0, 0, 0, 0, 0, 0
+            );
+            mListener3.assertValues(
+                    1, 0, 0, 0, 0, 0, 0, 0
+            );
+
+            mSet1.pause();
+            mListener1.assertValues(
+                    1, 0, 0, 0, 0, 0, 2, 1
+            );
+            mListener2.assertValues(
+                    1, 0, 0, 0, 0, 0, 1, 0
+            );
+            mListener3.assertValues(
+                    1, 0, 0, 0, 0, 0, 1, 0
+            );
+            mSet1.resume();
+            mListener1.assertValues(
+                    1, 0, 0, 0, 0, 0, 2, 2
+            );
+            mListener2.assertValues(
+                    1, 0, 0, 0, 0, 0, 1, 1
+            );
+            mListener3.assertValues(
+                    1, 0, 0, 0, 0, 0, 1, 1
+            );
+
+            // now go to animator2
+            mSet1.setCurrentPlayTime(400);
+            mSet1.pause();
+            mSet1.resume();
+            mListener1.assertValues(
+                    1, 0, 0, 0, 0, 0, 3, 3
+            );
+            mListener2.assertValues(
+                    1, 0, 0, 0, 0, 0, 2, 2
+            );
+            mListener3.assertValues(
+                    1, 0, 1, 0, 0, 0, 1, 1
+            );
+
+            // now go back to mAnimator
+            mSet1.setCurrentPlayTime(250);
+            mSet1.pause();
+            mSet1.resume();
+            mListener1.assertValues(
+                    1, 0, 0, 0, 0, 0, 4, 4
+            );
+            mListener2.assertValues(
+                    1, 0, 0, 0, 0, 0, 3, 3
+            );
+            mListener3.assertValues(
+                    1, 1, 1, 0, 0, 0, 2, 2
+            );
+
+            // now go back to before mSet2 was being run
+            mSet1.setCurrentPlayTime(1);
+            mSet1.pause();
+            mSet1.resume();
+            mListener1.assertValues(
+                    1, 0, 0, 0, 0, 0, 5, 5
+            );
+            mListener2.assertValues(
+                    1, 0, 0, 1, 0, 0, 3, 3
+            );
+            mListener3.assertValues(
+                    1, 1, 1, 1, 0, 0, 2, 2
+            );
+        });
+    }
+
+    @Test
+    public void endInCancel() throws Throwable {
+        AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mSet1.end();
+            }
+        };
+        mSet1.addListener(listener);
+        mActivity.runOnUiThread(() -> {
+            mSet1.start();
+            mSet1.cancel();
+            // Should go to the end value
+            View square = mActivity.findViewById(R.id.square1);
+            assertEquals(100f, square.getTranslationX(), 0.001f);
+        });
+    }
+
+    @Test
+    public void reentrantStart() throws Throwable {
+        CountDownLatch latch = new CountDownLatch(3);
+        AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation, boolean isReverse) {
+                mSet1.start();
+                latch.countDown();
+            }
+        };
+        mSet1.addListener(listener);
+        mSet2.addListener(listener);
+        mAnimator.addListener(listener);
+        mActivity.runOnUiThread(() -> mSet1.start());
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+        // Make sure that the UI thread hasn't been destroyed by a stack overflow...
+        mActivity.runOnUiThread(() -> {});
+    }
+
+    @Test
+    public void reentrantEnd() throws Throwable {
+        CountDownLatch latch = new CountDownLatch(3);
+        AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation, boolean isReverse) {
+                mSet1.end();
+                latch.countDown();
+            }
+        };
+        mSet1.addListener(listener);
+        mSet2.addListener(listener);
+        mAnimator.addListener(listener);
+        mActivity.runOnUiThread(() -> {
+            mSet1.start();
+            mSet1.end();
+        });
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+        // Make sure that the UI thread hasn't been destroyed by a stack overflow...
+        mActivity.runOnUiThread(() -> {});
+    }
+
+    @Test
+    public void reentrantPause() throws Throwable {
+        CountDownLatch latch = new CountDownLatch(3);
+        AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationPause(Animator animation) {
+                mSet1.pause();
+                latch.countDown();
+            }
+        };
+        mSet1.addPauseListener(listener);
+        mSet2.addPauseListener(listener);
+        mAnimator.addPauseListener(listener);
+        mActivity.runOnUiThread(() -> {
+            mSet1.start();
+            mSet1.pause();
+        });
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+        // Make sure that the UI thread hasn't been destroyed by a stack overflow...
+        mActivity.runOnUiThread(() -> {});
+    }
+
+    @Test
+    public void reentrantResume() throws Throwable {
+        CountDownLatch latch = new CountDownLatch(3);
+        AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationResume(Animator animation) {
+                mSet1.resume();
+                latch.countDown();
+            }
+        };
+        mSet1.addPauseListener(listener);
+        mSet2.addPauseListener(listener);
+        mAnimator.addPauseListener(listener);
+        mActivity.runOnUiThread(() -> {
+            mSet1.start();
+            mSet1.pause();
+            mSet1.resume();
+        });
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+        // Make sure that the UI thread hasn't been destroyed by a stack overflow...
+        mActivity.runOnUiThread(() -> {});
+    }
+
+    private void waitForOnUiThread(PollingCheck.PollingCheckCondition condition) {
+        final boolean[] value = new boolean[1];
+        PollingCheck.waitFor(() -> {
+            mActivity.runOnUiThread(() -> value[0] = condition.canProceed());
+            return value[0];
+        });
+    }
+
+    private static class CountListener implements Animator.AnimatorListener,
+            Animator.AnimatorPauseListener {
+        public int startNoParam;
+        public int endNoParam;
+        public int startReverse;
+        public int startForward;
+        public int endForward;
+        public int endReverse;
+        public int cancel;
+        public int repeat;
+        public int pause;
+        public int resume;
+
+        public void assertValues(
+                int startForward,
+                int startReverse,
+                int endForward,
+                int endReverse,
+                int cancel,
+                int repeat,
+                int pause,
+                int resume
+        ) {
+            assertEquals("onAnimationStart() without direction", 0, startNoParam);
+            assertEquals("onAnimationEnd() without direction", 0, endNoParam);
+            assertEquals("onAnimationStart(forward)", startForward, this.startForward);
+            assertEquals("onAnimationStart(reverse)", startReverse, this.startReverse);
+            assertEquals("onAnimationEnd(forward)", endForward, this.endForward);
+            assertEquals("onAnimationEnd(reverse)", endReverse, this.endReverse);
+            assertEquals("onAnimationCancel()", cancel, this.cancel);
+            assertEquals("onAnimationRepeat()", repeat, this.repeat);
+            assertEquals("onAnimationPause()", pause, this.pause);
+            assertEquals("onAnimationResume()", resume, this.resume);
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation, boolean isReverse) {
+            if (isReverse) {
+                startReverse++;
+            } else {
+                startForward++;
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation, boolean isReverse) {
+            if (isReverse) {
+                endReverse++;
+            } else {
+                endForward++;
+            }
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            startNoParam++;
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            endNoParam++;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            cancel++;
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+            repeat++;
+        }
+
+        @Override
+        public void onAnimationPause(Animator animation) {
+            pause++;
+        }
+
+        @Override
+        public void onAnimationResume(Animator animation) {
+            resume++;
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
index dee0a3e..a53d57f 100644
--- a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
+++ b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
@@ -40,6 +40,8 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 @MediumTest
@@ -1067,6 +1069,64 @@
         });
     }
 
+    @Test
+    public void reentrantStart() throws Throwable {
+        CountDownLatch latch = new CountDownLatch(1);
+        a1.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation, boolean isReverse) {
+                a1.start();
+                latch.countDown();
+            }
+        });
+        mActivityRule.runOnUiThread(() -> a1.start());
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+        // Make sure that the UI thread isn't blocked by an infinite loop:
+        mActivityRule.runOnUiThread(() -> {});
+    }
+
+    @Test
+    public void reentrantPause() throws Throwable {
+        CountDownLatch latch = new CountDownLatch(1);
+        a1.addPauseListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationPause(Animator animation) {
+                a1.pause();
+                latch.countDown();
+            }
+        });
+        mActivityRule.runOnUiThread(() -> {
+            a1.start();
+            a1.pause();
+        });
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+        // Make sure that the UI thread isn't blocked by an infinite loop:
+        mActivityRule.runOnUiThread(() -> {});
+    }
+
+    @Test
+    public void reentrantResume() throws Throwable {
+        CountDownLatch latch = new CountDownLatch(1);
+        a1.addPauseListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationResume(Animator animation) {
+                a1.resume();
+                latch.countDown();
+            }
+        });
+        mActivityRule.runOnUiThread(() -> {
+            a1.start();
+            a1.pause();
+            a1.resume();
+        });
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+        // Make sure that the UI thread isn't blocked by an infinite loop:
+        mActivityRule.runOnUiThread(() -> {});
+    }
+
     class MyUpdateListener implements ValueAnimator.AnimatorUpdateListener {
         boolean wasRunning = false;
         long firstRunningFrameTime = -1;
diff --git a/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java b/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java
index 81cd4da..8cc88ea 100644
--- a/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java
+++ b/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java
@@ -135,11 +135,15 @@
      * @throws Exception
      */
     @Before
-    public void setUp() throws Exception {
+    public void setUp() throws Throwable {
         final BasicAnimatorActivity activity = mActivityRule.getActivity();
         Button button = activity.findViewById(R.id.animatingButton);
 
         mAnimator = button.animate().x(100).y(100);
+        mActivityRule.runOnUiThread(() -> {
+            mAnimator.start();
+            mAnimator.cancel();
+        });
 
         // mListener is the main testing mechanism of this file. The asserts of each test
         // are embedded in the listener callbacks that it implements.
diff --git a/core/tests/coretests/src/android/hardware/display/VirtualDisplayConfigTest.java b/core/tests/coretests/src/android/hardware/display/VirtualDisplayConfigTest.java
new file mode 100644
index 0000000..4e59064
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/display/VirtualDisplayConfigTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.SurfaceTexture;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.util.DisplayMetrics;
+import android.view.ContentRecordingSession;
+import android.view.Surface;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * Tests for non-public APIs in {@link VirtualDisplayConfig}.
+ * See also related CTS tests.
+ *
+ * Run with:
+ * atest FrameworksCoreTests:VirtualDisplayConfigTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VirtualDisplayConfigTest {
+
+    private static final String NAME = "VirtualDisplayConfigTest";
+    private static final int WIDTH = 720;
+    private static final int HEIGHT = 480;
+    private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM;
+    private static final float REQUESTED_REFRESH_RATE = 30.0f;
+    private static final int FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
+            | DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
+
+    // Values for hidden APIs.
+    private static final int DISPLAY_ID_TO_MIRROR = 10;
+    private static final IBinder WINDOW_TOKEN = new Binder("DisplayContentWindowToken");
+    private static final ContentRecordingSession CONTENT_RECORDING_SESSION =
+            ContentRecordingSession.createTaskSession(WINDOW_TOKEN);
+
+    private final Surface mSurface = new Surface(new SurfaceTexture(/*texName=*/1));
+
+    @Test
+    public void testParcelAndUnparcel_matches() {
+        final VirtualDisplayConfig originalConfig = buildGenericVirtualDisplay(NAME);
+
+        validateConstantFields(originalConfig);
+        assertThat(originalConfig.getName()).isEqualTo(NAME);
+
+
+        final Parcel parcel = Parcel.obtain();
+        originalConfig.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+        final VirtualDisplayConfig recreatedConfig =
+                VirtualDisplayConfig.CREATOR.createFromParcel(parcel);
+
+        validateConstantFields(recreatedConfig);
+        assertThat(recreatedConfig.getName()).isEqualTo(NAME);
+    }
+
+    @Test
+    public void testEquals_matches() {
+        assertThat(buildGenericVirtualDisplay(NAME)).isEqualTo(buildGenericVirtualDisplay(NAME));
+    }
+
+    @Test
+    public void testEquals_different() {
+        assertThat(buildGenericVirtualDisplay(NAME + "2")).isNotEqualTo(
+                buildGenericVirtualDisplay(NAME));
+    }
+
+    private VirtualDisplayConfig buildGenericVirtualDisplay(String name) {
+        return new VirtualDisplayConfig.Builder(name, WIDTH, HEIGHT, DENSITY)
+                .setFlags(FLAGS)
+                .setSurface(mSurface)
+                .setDisplayCategories(Set.of("C1", "C2"))
+                .addDisplayCategory("C3")
+                .setRequestedRefreshRate(REQUESTED_REFRESH_RATE)
+                .setDisplayIdToMirror(DISPLAY_ID_TO_MIRROR)
+                .setWindowManagerMirroringEnabled(true)
+                .setContentRecordingSession(CONTENT_RECORDING_SESSION)
+                .build();
+    }
+
+    private void validateConstantFields(VirtualDisplayConfig config) {
+        assertThat(config.getWidth()).isEqualTo(WIDTH);
+        assertThat(config.getHeight()).isEqualTo(HEIGHT);
+        assertThat(config.getDensityDpi()).isEqualTo(DENSITY);
+        assertThat(config.getFlags()).isEqualTo(FLAGS);
+        assertThat(config.getSurface()).isNotNull();
+        assertThat(config.getDisplayCategories()).containsExactly("C1", "C2", "C3");
+        assertThat(config.getRequestedRefreshRate()).isEqualTo(REQUESTED_REFRESH_RATE);
+        assertThat(config.getDisplayIdToMirror()).isEqualTo(DISPLAY_ID_TO_MIRROR);
+        assertThat(config.isWindowManagerMirroringEnabled()).isTrue();
+        assertThat(config.getContentRecordingSession()).isEqualTo(CONTENT_RECORDING_SESSION);
+    }
+}
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 91fbe00..394ff0a 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -54,6 +54,7 @@
 import android.content.Context;
 import android.os.FileUtils.MemoryPipe;
 import android.provider.DocumentsContract.Document;
+import android.util.DataUnit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -504,31 +505,45 @@
 
     @Test
     public void testRoundStorageSize() throws Exception {
-        final long M128 = 128000000L;
-        final long M256 = 256000000L;
-        final long M512 = 512000000L;
-        final long G1 = 1000000000L;
-        final long G2 = 2000000000L;
-        final long G16 = 16000000000L;
-        final long G32 = 32000000000L;
-        final long G64 = 64000000000L;
+        final long GB1 = DataUnit.GIGABYTES.toBytes(1);
+        final long GiB1 = DataUnit.GIBIBYTES.toBytes(1);
+        final long GB2 = DataUnit.GIGABYTES.toBytes(2);
+        final long GiB2 = DataUnit.GIBIBYTES.toBytes(2);
+        final long GiB128 = DataUnit.GIBIBYTES.toBytes(128);
+        final long GB256 = DataUnit.GIGABYTES.toBytes(256);
+        final long GiB256 = DataUnit.GIBIBYTES.toBytes(256);
+        final long GB512 = DataUnit.GIGABYTES.toBytes(512);
+        final long GiB512 = DataUnit.GIBIBYTES.toBytes(512);
+        final long TB1 = DataUnit.TERABYTES.toBytes(1);
+        final long TiB1 = DataUnit.TEBIBYTES.toBytes(1);
+        final long TB2 = DataUnit.TERABYTES.toBytes(2);
+        final long TiB2 = DataUnit.TEBIBYTES.toBytes(2);
+        final long TB4 = DataUnit.TERABYTES.toBytes(4);
+        final long TiB4 = DataUnit.TEBIBYTES.toBytes(4);
+        final long TB8 = DataUnit.TERABYTES.toBytes(8);
+        final long TiB8 = DataUnit.TEBIBYTES.toBytes(8);
 
-        assertEquals(M128, roundStorageSize(M128));
-        assertEquals(M256, roundStorageSize(M128 + 1));
-        assertEquals(M256, roundStorageSize(M256 - 1));
-        assertEquals(M256, roundStorageSize(M256));
-        assertEquals(M512, roundStorageSize(M256 + 1));
-        assertEquals(M512, roundStorageSize(M512 - 1));
-        assertEquals(M512, roundStorageSize(M512));
-        assertEquals(G1, roundStorageSize(M512 + 1));
-        assertEquals(G1, roundStorageSize(G1));
-        assertEquals(G2, roundStorageSize(G1 + 1));
+        assertEquals(GB1, roundStorageSize(GB1 - 1));
+        assertEquals(GB1, roundStorageSize(GB1));
+        assertEquals(GB1, roundStorageSize(GB1 + 1));
+        assertEquals(GB1, roundStorageSize(GiB1 - 1));
+        assertEquals(GB1, roundStorageSize(GiB1));
+        assertEquals(GB2, roundStorageSize(GiB1 + 1));
+        assertEquals(GB2, roundStorageSize(GiB2));
 
-        assertEquals(G16, roundStorageSize(G16));
-        assertEquals(G32, roundStorageSize(G16 + 1));
-        assertEquals(G32, roundStorageSize(G32 - 1));
-        assertEquals(G32, roundStorageSize(G32));
-        assertEquals(G64, roundStorageSize(G32 + 1));
+        assertEquals(GB256, roundStorageSize(GiB128 + 1));
+        assertEquals(GB256, roundStorageSize(GiB256));
+        assertEquals(GB512, roundStorageSize(GiB256 + 1));
+        assertEquals(GB512, roundStorageSize(GiB512));
+        assertEquals(TB1, roundStorageSize(GiB512 + 1));
+        assertEquals(TB1, roundStorageSize(TiB1));
+        assertEquals(TB2, roundStorageSize(TiB1 + 1));
+        assertEquals(TB2, roundStorageSize(TiB2));
+        assertEquals(TB4, roundStorageSize(TiB2 + 1));
+        assertEquals(TB4, roundStorageSize(TiB4));
+        assertEquals(TB8, roundStorageSize(TiB4 + 1));
+        assertEquals(TB8, roundStorageSize(TiB8));
+        assertEquals(TB1, roundStorageSize(1013077688320L)); // b/268571529
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/util/DataUnitTest.java b/core/tests/coretests/src/android/util/DataUnitTest.java
index ec296b7..034cbdd 100644
--- a/core/tests/coretests/src/android/util/DataUnitTest.java
+++ b/core/tests/coretests/src/android/util/DataUnitTest.java
@@ -26,11 +26,13 @@
         assertEquals(12_000L, DataUnit.KILOBYTES.toBytes(12));
         assertEquals(12_000_000L, DataUnit.MEGABYTES.toBytes(12));
         assertEquals(12_000_000_000L, DataUnit.GIGABYTES.toBytes(12));
+        assertEquals(12_000_000_000_000L, DataUnit.TERABYTES.toBytes(12));
     }
 
     public void testIec() throws Exception {
         assertEquals(12_288L, DataUnit.KIBIBYTES.toBytes(12));
         assertEquals(12_582_912L, DataUnit.MEBIBYTES.toBytes(12));
         assertEquals(12_884_901_888L, DataUnit.GIBIBYTES.toBytes(12));
+        assertEquals(13_194_139_533_312L, DataUnit.TEBIBYTES.toBytes(12));
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
new file mode 100644
index 0000000..e6f10ad
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION;
+import static com.android.internal.util.FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED;
+import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * This test class verifies the additional methods which {@link FakeLatencyTracker} exposes.
+ *
+ * <p>The typical {@link LatencyTracker} behavior test coverage is present in
+ * {@link LatencyTrackerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+public class FakeLatencyTrackerTest {
+
+    private FakeLatencyTracker mFakeLatencyTracker;
+
+    @Before
+    public void setUp() throws Exception {
+        mFakeLatencyTracker = FakeLatencyTracker.create();
+    }
+
+    @Test
+    public void testForceEnabled() throws Exception {
+        mFakeLatencyTracker.logAction(ACTION_SHOW_VOICE_INTERACTION, 1234);
+
+        assertThat(mFakeLatencyTracker.getEventsWrittenToFrameworkStats(
+                ACTION_SHOW_VOICE_INTERACTION)).isEmpty();
+
+        mFakeLatencyTracker.forceEnabled(ACTION_SHOW_VOICE_INTERACTION, 1000);
+        mFakeLatencyTracker.logAction(ACTION_SHOW_VOICE_INTERACTION, 1234);
+        List<LatencyTracker.FrameworkStatsLogEvent> events =
+                mFakeLatencyTracker.getEventsWrittenToFrameworkStats(
+                        ACTION_SHOW_VOICE_INTERACTION);
+        assertThat(events).hasSize(1);
+        assertThat(events.get(0).logCode).isEqualTo(UI_ACTION_LATENCY_REPORTED);
+        assertThat(events.get(0).statsdAction).isEqualTo(
+                UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION);
+        assertThat(events.get(0).durationMillis).isEqualTo(1234);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
index d1f0b5e..645324d 100644
--- a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
@@ -16,19 +16,22 @@
 
 package com.android.internal.util;
 
+import static android.provider.DeviceConfig.NAMESPACE_LATENCY_TRACKER;
 import static android.text.TextUtils.formatSimple;
 
+import static com.android.internal.util.FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED;
 import static com.android.internal.util.LatencyTracker.STATSD_ACTION;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.provider.DeviceConfig;
-import android.util.Log;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.util.LatencyTracker.ActionProperties;
+
 import com.google.common.truth.Expect;
 
 import org.junit.Before;
@@ -38,7 +41,6 @@
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
-import java.time.Duration;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -49,27 +51,23 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class LatencyTrackerTest {
-    private static final String TAG = LatencyTrackerTest.class.getSimpleName();
     private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__";
-    private static final String ACTION_ENABLE_SUFFIX = "_enable";
-    private static final Duration TEST_TIMEOUT = Duration.ofMillis(500);
 
     @Rule
     public final Expect mExpect = Expect.create();
 
+    // Fake is used because it tests the real logic of LatencyTracker, and it only fakes the
+    // outcomes (PerfettoTrigger and FrameworkStatsLog).
+    private FakeLatencyTracker mLatencyTracker;
+
     @Before
-    public void setUp() {
-        DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
-                LatencyTracker.SETTINGS_ENABLED_KEY);
-        getAllActions().forEach(action -> {
-            DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
-                    action.getName().toLowerCase() + ACTION_ENABLE_SUFFIX);
-        });
+    public void setUp() throws Exception {
+        mLatencyTracker = FakeLatencyTracker.create();
     }
 
     @Test
     public void testCujsMapToEnumsCorrectly() {
-        List<Field> actions = getAllActions();
+        List<Field> actions = getAllActionFields();
         Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
                 .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
                         && Modifier.isStatic(f.getModifiers())
@@ -101,7 +99,7 @@
 
     @Test
     public void testCujTypeEnumCorrectlyDefined() throws Exception {
-        List<Field> cujEnumFields = getAllActions();
+        List<Field> cujEnumFields = getAllActionFields();
         HashSet<Integer> allValues = new HashSet<>();
         for (Field field : cujEnumFields) {
             int fieldValue = field.getInt(null);
@@ -118,92 +116,242 @@
     }
 
     @Test
-    public void testIsEnabled_globalEnabled() {
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
+    public void testIsEnabled_trueWhenGlobalEnabled() throws Exception {
+        DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER,
                 LatencyTracker.SETTINGS_ENABLED_KEY, "true", false);
-        LatencyTracker latencyTracker = new LatencyTracker();
-        waitForLatencyTrackerToUpdateProperties(latencyTracker);
-        assertThat(latencyTracker.isEnabled()).isTrue();
+        mLatencyTracker.waitForGlobalEnabledState(true);
+        mLatencyTracker.waitForAllPropertiesEnableState(true);
+
+        //noinspection deprecation
+        assertThat(mLatencyTracker.isEnabled()).isTrue();
     }
 
     @Test
-    public void testIsEnabled_globalDisabled() {
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
+    public void testIsEnabled_falseWhenGlobalDisabled() throws Exception {
+        DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER,
                 LatencyTracker.SETTINGS_ENABLED_KEY, "false", false);
-        LatencyTracker latencyTracker = new LatencyTracker();
-        waitForLatencyTrackerToUpdateProperties(latencyTracker);
-        assertThat(latencyTracker.isEnabled()).isFalse();
+        mLatencyTracker.waitForGlobalEnabledState(false);
+        mLatencyTracker.waitForAllPropertiesEnableState(false);
+
+        //noinspection deprecation
+        assertThat(mLatencyTracker.isEnabled()).isFalse();
     }
 
     @Test
-    public void testIsEnabledAction_useGlobalValueWhenActionEnableIsNotSet() {
-        LatencyTracker latencyTracker = new LatencyTracker();
+    public void testIsEnabledAction_useGlobalValueWhenActionEnableIsNotSet()
+            throws Exception {
         // using a single test action, but this applies to all actions
         int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
-        Log.i(TAG, "setting property=" + LatencyTracker.SETTINGS_ENABLED_KEY + ", value=true");
-        latencyTracker.mDeviceConfigPropertiesUpdated.close();
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
+        DeviceConfig.deleteProperty(NAMESPACE_LATENCY_TRACKER,
+                "action_show_voice_interaction_enable");
+        mLatencyTracker.waitForAllPropertiesEnableState(false);
+        DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER,
                 LatencyTracker.SETTINGS_ENABLED_KEY, "true", false);
-        waitForLatencyTrackerToUpdateProperties(latencyTracker);
-        assertThat(
-                latencyTracker.isEnabled(action)).isTrue();
+        mLatencyTracker.waitForGlobalEnabledState(true);
+        mLatencyTracker.waitForAllPropertiesEnableState(true);
 
-        Log.i(TAG, "setting property=" + LatencyTracker.SETTINGS_ENABLED_KEY
-                + ", value=false");
-        latencyTracker.mDeviceConfigPropertiesUpdated.close();
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
-                LatencyTracker.SETTINGS_ENABLED_KEY, "false", false);
-        waitForLatencyTrackerToUpdateProperties(latencyTracker);
-        assertThat(latencyTracker.isEnabled(action)).isFalse();
+        assertThat(mLatencyTracker.isEnabled(action)).isTrue();
     }
 
     @Test
     public void testIsEnabledAction_actionPropertyOverridesGlobalProperty()
-            throws DeviceConfig.BadConfigException {
-        LatencyTracker latencyTracker = new LatencyTracker();
+            throws Exception {
         // using a single test action, but this applies to all actions
         int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
-        String actionEnableProperty = "action_show_voice_interaction" + ACTION_ENABLE_SUFFIX;
-        Log.i(TAG, "setting property=" + actionEnableProperty + ", value=true");
+        DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER,
+                LatencyTracker.SETTINGS_ENABLED_KEY, "false", false);
+        mLatencyTracker.waitForGlobalEnabledState(false);
 
-        latencyTracker.mDeviceConfigPropertiesUpdated.close();
-        Map<String, String> properties = new HashMap<String, String>() {{
-            put(LatencyTracker.SETTINGS_ENABLED_KEY, "false");
-            put(actionEnableProperty, "true");
-        }};
+        Map<String, String> deviceConfigProperties = new HashMap<>();
+        deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
+        deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
+        deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "-1");
         DeviceConfig.setProperties(
-                new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
-                        properties));
-        waitForLatencyTrackerToUpdateProperties(latencyTracker);
-        assertThat(latencyTracker.isEnabled(action)).isTrue();
+                new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
+                        deviceConfigProperties));
 
-        latencyTracker.mDeviceConfigPropertiesUpdated.close();
-        Log.i(TAG, "setting property=" + actionEnableProperty + ", value=false");
-        properties.put(LatencyTracker.SETTINGS_ENABLED_KEY, "true");
-        properties.put(actionEnableProperty, "false");
-        DeviceConfig.setProperties(
-                    new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
-                            properties));
-        waitForLatencyTrackerToUpdateProperties(latencyTracker);
-        assertThat(latencyTracker.isEnabled(action)).isFalse();
+        mLatencyTracker.waitForMatchingActionProperties(
+                new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
+                        -1 /* traceThreshold */));
+
+        assertThat(mLatencyTracker.isEnabled(action)).isTrue();
     }
 
-    private void waitForLatencyTrackerToUpdateProperties(LatencyTracker latencyTracker) {
-        try {
-            Thread.sleep(TEST_TIMEOUT.toMillis());
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-        assertThat(latencyTracker.mDeviceConfigPropertiesUpdated.block(
-                TEST_TIMEOUT.toMillis())).isTrue();
+    @Test
+    public void testLogsWhenEnabled() throws Exception {
+        // using a single test action, but this applies to all actions
+        int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+        Map<String, String> deviceConfigProperties = new HashMap<>();
+        deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
+        deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
+        deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "-1");
+        DeviceConfig.setProperties(
+                new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
+                        deviceConfigProperties));
+        mLatencyTracker.waitForMatchingActionProperties(
+                new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
+                        -1 /* traceThreshold */));
+
+        mLatencyTracker.logAction(action, 1234);
+        assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).hasSize(1);
+        LatencyTracker.FrameworkStatsLogEvent frameworkStatsLog =
+                mLatencyTracker.getEventsWrittenToFrameworkStats(action).get(0);
+        assertThat(frameworkStatsLog.logCode).isEqualTo(UI_ACTION_LATENCY_REPORTED);
+        assertThat(frameworkStatsLog.statsdAction).isEqualTo(STATSD_ACTION[action]);
+        assertThat(frameworkStatsLog.durationMillis).isEqualTo(1234);
+
+        mLatencyTracker.clearEvents();
+
+        mLatencyTracker.onActionStart(action);
+        mLatencyTracker.onActionEnd(action);
+        // assert that action was logged, but we cannot confirm duration logged
+        assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).hasSize(1);
+        frameworkStatsLog = mLatencyTracker.getEventsWrittenToFrameworkStats(action).get(0);
+        assertThat(frameworkStatsLog.logCode).isEqualTo(UI_ACTION_LATENCY_REPORTED);
+        assertThat(frameworkStatsLog.statsdAction).isEqualTo(STATSD_ACTION[action]);
     }
 
-    private List<Field> getAllActions() {
-        return Arrays.stream(LatencyTracker.class.getDeclaredFields())
-                .filter(field -> field.getName().startsWith("ACTION_")
-                        && Modifier.isStatic(field.getModifiers())
-                        && field.getType() == int.class)
-                .collect(Collectors.toList());
+    @Test
+    public void testDoesNotLogWhenDisabled() throws Exception {
+        // using a single test action, but this applies to all actions
+        int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+        DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, "action_show_voice_interaction_enable",
+                "false", false);
+        mLatencyTracker.waitForActionEnabledState(action, false);
+        assertThat(mLatencyTracker.isEnabled(action)).isFalse();
+
+        mLatencyTracker.logAction(action, 1234);
+        assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty();
+
+        mLatencyTracker.onActionStart(action);
+        mLatencyTracker.onActionEnd(action);
+        assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty();
+    }
+
+    @Test
+    public void testOnActionEndDoesNotLogWithoutOnActionStart()
+            throws Exception {
+        // using a single test action, but this applies to all actions
+        int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+        DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, "action_show_voice_interaction_enable",
+                "true", false);
+        mLatencyTracker.waitForActionEnabledState(action, true);
+        assertThat(mLatencyTracker.isEnabled(action)).isTrue();
+
+        mLatencyTracker.onActionEnd(action);
+        assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty();
+    }
+
+    @Test
+    public void testOnActionEndDoesNotLogWhenCanceled()
+            throws Exception {
+        // using a single test action, but this applies to all actions
+        int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+        DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, "action_show_voice_interaction_enable",
+                "true", false);
+        mLatencyTracker.waitForActionEnabledState(action, true);
+        assertThat(mLatencyTracker.isEnabled(action)).isTrue();
+
+        mLatencyTracker.onActionStart(action);
+        mLatencyTracker.onActionCancel(action);
+        mLatencyTracker.onActionEnd(action);
+        assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty();
+    }
+
+    @Test
+    public void testNeverTriggersPerfettoWhenThresholdNegative()
+            throws Exception {
+        // using a single test action, but this applies to all actions
+        int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+        Map<String, String> deviceConfigProperties = new HashMap<>();
+        deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
+        deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
+        deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "-1");
+        DeviceConfig.setProperties(
+                new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
+                        deviceConfigProperties));
+        mLatencyTracker.waitForMatchingActionProperties(
+                new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
+                        -1 /* traceThreshold */));
+
+        mLatencyTracker.onActionStart(action);
+        mLatencyTracker.onActionEnd(action);
+        assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).isEmpty();
+    }
+
+    @Test
+    public void testNeverTriggersPerfettoWhenDisabled()
+            throws Exception {
+        // using a single test action, but this applies to all actions
+        int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+        Map<String, String> deviceConfigProperties = new HashMap<>();
+        deviceConfigProperties.put("action_show_voice_interaction_enable", "false");
+        deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
+        deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "1");
+        DeviceConfig.setProperties(
+                new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
+                        deviceConfigProperties));
+        mLatencyTracker.waitForMatchingActionProperties(
+                new ActionProperties(action, false /* enabled */, 1 /* samplingInterval */,
+                        1 /* traceThreshold */));
+
+        mLatencyTracker.onActionStart(action);
+        mLatencyTracker.onActionEnd(action);
+        assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).isEmpty();
+    }
+
+    @Test
+    public void testTriggersPerfettoWhenAboveThreshold()
+            throws Exception {
+        // using a single test action, but this applies to all actions
+        int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+        Map<String, String> deviceConfigProperties = new HashMap<>();
+        deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
+        deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
+        deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "1");
+        DeviceConfig.setProperties(
+                new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
+                        deviceConfigProperties));
+        mLatencyTracker.waitForMatchingActionProperties(
+                new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
+                        1 /* traceThreshold */));
+
+        mLatencyTracker.onActionStart(action);
+        // We need to sleep here to ensure that the end call is past the set trace threshold (1ms)
+        Thread.sleep(5 /* millis */);
+        mLatencyTracker.onActionEnd(action);
+        assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).hasSize(1);
+        assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames().get(0)).isEqualTo(
+                "com.android.telemetry.latency-tracker-ACTION_SHOW_VOICE_INTERACTION");
+    }
+
+    @Test
+    public void testNeverTriggersPerfettoWhenBelowThreshold()
+            throws Exception {
+        // using a single test action, but this applies to all actions
+        int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+        Map<String, String> deviceConfigProperties = new HashMap<>();
+        deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
+        deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
+        deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "1000");
+        DeviceConfig.setProperties(
+                new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
+                        deviceConfigProperties));
+        mLatencyTracker.waitForMatchingActionProperties(
+                new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
+                        1000 /* traceThreshold */));
+
+        mLatencyTracker.onActionStart(action);
+        // No sleep here to ensure that end call comes before 1000ms threshold
+        mLatencyTracker.onActionEnd(action);
+        assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).isEmpty();
+    }
+
+    private List<Field> getAllActionFields() {
+        return Arrays.stream(LatencyTracker.class.getDeclaredFields()).filter(
+                field -> field.getName().startsWith("ACTION_") && Modifier.isStatic(
+                        field.getModifiers()) && field.getType() == int.class).collect(
+                Collectors.toList());
     }
 
     private int getIntFieldChecked(Field field) {
diff --git a/core/tests/coretests/src/com/android/internal/util/OWNERS b/core/tests/coretests/src/com/android/internal/util/OWNERS
index d832745..dda11fb 100644
--- a/core/tests/coretests/src/com/android/internal/util/OWNERS
+++ b/core/tests/coretests/src/com/android/internal/util/OWNERS
@@ -1,2 +1,3 @@
 per-file *Notification* = file:/services/core/java/com/android/server/notification/OWNERS
-per-file *ContrastColor* = file:/services/core/java/com/android/server/notification/OWNERS
\ No newline at end of file
+per-file *ContrastColor* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file *LatencyTracker* = file:/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
diff --git a/core/tests/coretests/testdoubles/Android.bp b/core/tests/coretests/testdoubles/Android.bp
new file mode 100644
index 0000000..35f6911
--- /dev/null
+++ b/core/tests/coretests/testdoubles/Android.bp
@@ -0,0 +1,19 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    //   SPDX-license-identifier-BSD
+    //   legacy_unencumbered
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "FrameworksCoreTestDoubles-sources",
+    srcs: ["src/**/*.java"],
+    visibility: [
+        "//frameworks/base/core/tests/coretests",
+        "//frameworks/base/services/tests/voiceinteractiontests",
+    ],
+}
diff --git a/core/tests/coretests/testdoubles/OWNERS b/core/tests/coretests/testdoubles/OWNERS
new file mode 100644
index 0000000..baf92ec
--- /dev/null
+++ b/core/tests/coretests/testdoubles/OWNERS
@@ -0,0 +1 @@
+per-file *LatencyTracker* = file:/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
diff --git a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
new file mode 100644
index 0000000..306ecde
--- /dev/null
+++ b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static com.android.internal.util.LatencyTracker.ActionProperties.ENABLE_SUFFIX;
+import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_INTERVAL_SUFFIX;
+import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.ConditionVariable;
+import android.provider.DeviceConfig;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.GuardedBy;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
+
+public final class FakeLatencyTracker extends LatencyTracker {
+
+    private static final String TAG = "FakeLatencyTracker";
+    private static final Duration FORCE_UPDATE_TIMEOUT = Duration.ofSeconds(1);
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final Map<Integer, List<FrameworkStatsLogEvent>> mLatenciesLogged;
+    @GuardedBy("mLock")
+    private final List<String> mPerfettoTraceNamesTriggered;
+    private final AtomicReference<SparseArray<ActionProperties>> mLastPropertiesUpdate =
+            new AtomicReference<>();
+    @Nullable
+    @GuardedBy("mLock")
+    private Callable<Boolean> mShouldClosePropertiesUpdatedCallable = null;
+    private final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable();
+
+    public static FakeLatencyTracker create() throws Exception {
+        Log.i(TAG, "create");
+        disableForAllActions();
+        FakeLatencyTracker fakeLatencyTracker = new FakeLatencyTracker();
+        // always return the fake in the disabled state and let the client control the desired state
+        fakeLatencyTracker.waitForGlobalEnabledState(false);
+        fakeLatencyTracker.waitForAllPropertiesEnableState(false);
+        return fakeLatencyTracker;
+    }
+
+    FakeLatencyTracker() {
+        super();
+        mLatenciesLogged = new HashMap<>();
+        mPerfettoTraceNamesTriggered = new ArrayList<>();
+    }
+
+    private static void disableForAllActions() throws DeviceConfig.BadConfigException {
+        Map<String, String> properties = new HashMap<>();
+        properties.put(LatencyTracker.SETTINGS_ENABLED_KEY, "false");
+        for (int action : STATSD_ACTION) {
+            Log.d(TAG, "disabling action=" + action + ", property=" + getNameOfAction(
+                    action).toLowerCase(Locale.ROOT) + ENABLE_SUFFIX);
+            properties.put(getNameOfAction(action).toLowerCase(Locale.ROOT) + ENABLE_SUFFIX,
+                    "false");
+        }
+
+        DeviceConfig.setProperties(
+                new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER, properties));
+    }
+
+    public void forceEnabled(int action, int traceThresholdMillis)
+            throws Exception {
+        String actionName = getNameOfAction(STATSD_ACTION[action]).toLowerCase(Locale.ROOT);
+        String actionEnableProperty = actionName + ENABLE_SUFFIX;
+        String actionSampleProperty = actionName + SAMPLE_INTERVAL_SUFFIX;
+        String actionTraceProperty = actionName + TRACE_THRESHOLD_SUFFIX;
+        Log.i(TAG, "setting property=" + actionTraceProperty + ", value=" + traceThresholdMillis);
+        Log.i(TAG, "setting property=" + actionEnableProperty + ", value=true");
+
+        Map<String, String> properties = new HashMap<>(ImmutableMap.of(
+                actionEnableProperty, "true",
+                // Fake forces to sample every event
+                actionSampleProperty, String.valueOf(1),
+                actionTraceProperty, String.valueOf(traceThresholdMillis)
+        ));
+        DeviceConfig.setProperties(
+                new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER, properties));
+        waitForMatchingActionProperties(
+                new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
+                        traceThresholdMillis));
+    }
+
+    public List<FrameworkStatsLogEvent> getEventsWrittenToFrameworkStats(@Action int action) {
+        synchronized (mLock) {
+            Log.i(TAG, "getEventsWrittenToFrameworkStats: mLatenciesLogged=" + mLatenciesLogged);
+            return mLatenciesLogged.getOrDefault(action, Collections.emptyList());
+        }
+    }
+
+    public List<String> getTriggeredPerfettoTraceNames() {
+        synchronized (mLock) {
+            return mPerfettoTraceNamesTriggered;
+        }
+    }
+
+    public void clearEvents() {
+        synchronized (mLock) {
+            mLatenciesLogged.clear();
+            mPerfettoTraceNamesTriggered.clear();
+        }
+    }
+
+    @Override
+    public void onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties) {
+        Log.d(TAG, "onDeviceConfigPropertiesUpdated: " + actionProperties);
+        mLastPropertiesUpdate.set(actionProperties);
+        synchronized (mLock) {
+            if (mShouldClosePropertiesUpdatedCallable != null) {
+                try {
+                    boolean shouldClosePropertiesUpdated =
+                            mShouldClosePropertiesUpdatedCallable.call();
+                    Log.i(TAG, "shouldClosePropertiesUpdatedCallable callable result="
+                            + shouldClosePropertiesUpdated);
+                    if (shouldClosePropertiesUpdated) {
+                        Log.i(TAG, "shouldClosePropertiesUpdatedCallable=true, opening condition");
+                        mShouldClosePropertiesUpdatedCallable = null;
+                        mDeviceConfigPropertiesUpdated.open();
+                    }
+                } catch (Exception e) {
+                    Log.e(TAG, "exception when calling callable", e);
+                    throw new RuntimeException(e);
+                }
+            } else {
+                Log.i(TAG, "no conditional callable set, opening condition");
+                mDeviceConfigPropertiesUpdated.open();
+            }
+        }
+    }
+
+    @Override
+    public void onTriggerPerfetto(String triggerName) {
+        synchronized (mLock) {
+            mPerfettoTraceNamesTriggered.add(triggerName);
+        }
+    }
+
+    @Override
+    public void onLogToFrameworkStats(FrameworkStatsLogEvent event) {
+        synchronized (mLock) {
+            Log.i(TAG, "onLogToFrameworkStats: event=" + event);
+            List<FrameworkStatsLogEvent> eventList = mLatenciesLogged.getOrDefault(event.action,
+                    new ArrayList<>());
+            eventList.add(event);
+            mLatenciesLogged.put(event.action, eventList);
+        }
+    }
+
+    public void waitForAllPropertiesEnableState(boolean enabledState) throws Exception {
+        Log.i(TAG, "waitForAllPropertiesEnableState: enabledState=" + enabledState);
+        synchronized (mLock) {
+            Log.i(TAG, "closing condition");
+            mDeviceConfigPropertiesUpdated.close();
+            // Update the callable to only close the properties updated condition when all the
+            // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+            // times so testing the resulting updates is required.
+            mShouldClosePropertiesUpdatedCallable = () -> {
+                Log.i(TAG, "verifying if last properties update has all properties enable="
+                        + enabledState);
+                SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
+                if (newProperties != null) {
+                    for (int i = 0; i < newProperties.size(); i++) {
+                        if (newProperties.get(i).isEnabled() != enabledState) {
+                            return false;
+                        }
+                    }
+                }
+                return true;
+            };
+            if (mShouldClosePropertiesUpdatedCallable.call()) {
+                return;
+            }
+        }
+        Log.i(TAG, "waiting for condition");
+        assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue();
+    }
+
+    public void waitForMatchingActionProperties(ActionProperties actionProperties)
+            throws Exception {
+        Log.i(TAG, "waitForMatchingActionProperties: actionProperties=" + actionProperties);
+        synchronized (mLock) {
+            Log.i(TAG, "closing condition");
+            mDeviceConfigPropertiesUpdated.close();
+            // Update the callable to only close the properties updated condition when all the
+            // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+            // times so testing the resulting updates is required.
+            mShouldClosePropertiesUpdatedCallable = () -> {
+                Log.i(TAG, "verifying if last properties update contains matching property ="
+                        + actionProperties);
+                SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
+                if (newProperties != null) {
+                    if (newProperties.size() > 0) {
+                        return newProperties.get(actionProperties.getAction()).equals(
+                                actionProperties);
+                    }
+                }
+                return false;
+            };
+            if (mShouldClosePropertiesUpdatedCallable.call()) {
+                return;
+            }
+        }
+        Log.i(TAG, "waiting for condition");
+        assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue();
+    }
+
+    public void waitForActionEnabledState(int action, boolean enabledState) throws Exception {
+        Log.i(TAG, "waitForActionEnabledState:"
+                + " action=" + action + ", enabledState=" + enabledState);
+        synchronized (mLock) {
+            Log.i(TAG, "closing condition");
+            mDeviceConfigPropertiesUpdated.close();
+            // Update the callable to only close the properties updated condition when all the
+            // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+            // times so testing the resulting updates is required.
+            mShouldClosePropertiesUpdatedCallable = () -> {
+                Log.i(TAG, "verifying if last properties update contains action=" + action
+                        + ", enabledState=" + enabledState);
+                SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
+                if (newProperties != null) {
+                    if (newProperties.size() > 0) {
+                        return newProperties.get(action).isEnabled() == enabledState;
+                    }
+                }
+                return false;
+            };
+            if (mShouldClosePropertiesUpdatedCallable.call()) {
+                return;
+            }
+        }
+        Log.i(TAG, "waiting for condition");
+        assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue();
+    }
+
+    public void waitForGlobalEnabledState(boolean enabledState) throws Exception {
+        Log.i(TAG, "waitForGlobalEnabledState: enabledState=" + enabledState);
+        synchronized (mLock) {
+            Log.i(TAG, "closing condition");
+            mDeviceConfigPropertiesUpdated.close();
+            // Update the callable to only close the properties updated condition when all the
+            // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+            // times so testing the resulting updates is required.
+            mShouldClosePropertiesUpdatedCallable = () -> {
+                //noinspection deprecation
+                return isEnabled() == enabledState;
+            };
+            if (mShouldClosePropertiesUpdatedCallable.call()) {
+                return;
+            }
+        }
+        Log.i(TAG, "waiting for condition");
+        assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue();
+    }
+}
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
new file mode 100644
index 0000000..ee62d75
--- /dev/null
+++ b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.expresslog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class ScaledRangeOptionsTest {
+    private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName();
+
+    @Test
+    public void testGetBinsCount() {
+        Histogram.ScaledRangeOptions options1 = new Histogram.ScaledRangeOptions(1, 100, 100, 2);
+        assertEquals(3, options1.getBinsCount());
+
+        Histogram.ScaledRangeOptions options10 = new Histogram.ScaledRangeOptions(10, 100, 100, 2);
+        assertEquals(12, options10.getBinsCount());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructZeroBinsCount() {
+        new Histogram.ScaledRangeOptions(0, 100, 100, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructNegativeBinsCount() {
+        new Histogram.ScaledRangeOptions(-1, 100, 100, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructNegativeFirstBinWidth() {
+        new Histogram.ScaledRangeOptions(10, 100, -100, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructTooSmallFirstBinWidth() {
+        new Histogram.ScaledRangeOptions(10, 100, 0.5f, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructNegativeScaleFactor() {
+        new Histogram.ScaledRangeOptions(10, 100, 100, -2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructTooSmallScaleFactor() {
+        new Histogram.ScaledRangeOptions(10, 100, 100, 0.5f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructTooBigScaleFactor() {
+        new Histogram.ScaledRangeOptions(10, 100, 100, 500.f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructTooBigBinRange() {
+        new Histogram.ScaledRangeOptions(100, 100, 100, 10.f);
+    }
+
+    @Test
+    public void testBinIndexForRangeEqual1() {
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 1, 1);
+        assertEquals(12, options.getBinsCount());
+
+        assertEquals(11, options.getBinForSample(11));
+
+        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+            assertEquals(i, options.getBinForSample(i));
+        }
+    }
+
+    @Test
+    public void testBinIndexForRangeEqual2() {
+        // this should produce bin otpions similar to linear histogram with bin width 2
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 2, 1);
+        assertEquals(12, options.getBinsCount());
+
+        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+            assertEquals(i, options.getBinForSample(i * 2));
+            assertEquals(i, options.getBinForSample(i * 2 - 1));
+        }
+    }
+
+    @Test
+    public void testBinIndexForRangeEqual5() {
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(2, 0, 5, 1);
+        assertEquals(4, options.getBinsCount());
+        for (int i = 0; i < 2; i++) {
+            for (int sample = 0; sample < 5; sample++) {
+                assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
+            }
+        }
+    }
+
+    @Test
+    public void testBinIndexForRangeEqual10() {
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 10, 1);
+        assertEquals(0, options.getBinForSample(0));
+        assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
+        assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
+
+        final float binSize = (101 - 1) / 10f;
+        for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
+            assertEquals(i, options.getBinForSample(i * binSize));
+        }
+    }
+
+    @Test
+    public void testBinIndexForScaleFactor2() {
+        final int binsCount = 10;
+        final int minValue = 10;
+        final int firstBinWidth = 5;
+        final int scaledFactor = 2;
+
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(
+                binsCount, minValue, firstBinWidth, scaledFactor);
+        assertEquals(binsCount + 2, options.getBinsCount());
+        long[] binCounts = new long[10];
+
+        // precalculate max valid value - start value for the overflow bin
+        int lastBinStartValue = minValue; //firstBinMin value
+        int lastBinWidth = firstBinWidth;
+        for (int binIdx = 2; binIdx <= binsCount + 1; binIdx++) {
+            lastBinStartValue = lastBinStartValue + lastBinWidth;
+            lastBinWidth *= scaledFactor;
+        }
+
+        // underflow bin
+        for (int i = 1; i < minValue; i++) {
+            assertEquals(0, options.getBinForSample(i));
+        }
+
+        for (int i = 10; i < lastBinStartValue; i++) {
+            assertTrue(options.getBinForSample(i) > 0);
+            assertTrue(options.getBinForSample(i) <= binsCount);
+            binCounts[options.getBinForSample(i) - 1]++;
+        }
+
+        // overflow bin
+        assertEquals(binsCount + 1, options.getBinForSample(lastBinStartValue));
+
+        for (int i = 1; i < binsCount; i++) {
+            assertEquals(binCounts[i], binCounts[i - 1] * 2L);
+        }
+    }
+}
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
index 9fa6d06..037dbb3 100644
--- a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
+++ b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
@@ -24,11 +24,11 @@
 import org.junit.runners.JUnit4;
 
 @RunWith(JUnit4.class)
+@SmallTest
 public class UniformOptionsTest {
     private static final String TAG = UniformOptionsTest.class.getSimpleName();
 
     @Test
-    @SmallTest
     public void testGetBinsCount() {
         Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000);
         assertEquals(3, options1.getBinsCount());
@@ -38,25 +38,21 @@
     }
 
     @Test(expected = IllegalArgumentException.class)
-    @SmallTest
     public void testConstructZeroBinsCount() {
         new Histogram.UniformOptions(0, 100, 1000);
     }
 
     @Test(expected = IllegalArgumentException.class)
-    @SmallTest
     public void testConstructNegativeBinsCount() {
         new Histogram.UniformOptions(-1, 100, 1000);
     }
 
     @Test(expected = IllegalArgumentException.class)
-    @SmallTest
     public void testConstructMaxValueLessThanMinValue() {
         new Histogram.UniformOptions(10, 1000, 100);
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual1() {
         Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11);
         for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
@@ -65,7 +61,6 @@
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual2() {
         Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21);
         for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
@@ -75,7 +70,6 @@
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual5() {
         Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10);
         assertEquals(4, options.getBinsCount());
@@ -87,7 +81,6 @@
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual10() {
         Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101);
         assertEquals(0, options.getBinForSample(0));
@@ -101,7 +94,6 @@
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual90() {
         final int binCount = 10;
         final int minValue = 100;
diff --git a/data/etc/TEST_MAPPING b/data/etc/TEST_MAPPING
index 1a5db2f..5927720 100644
--- a/data/etc/TEST_MAPPING
+++ b/data/etc/TEST_MAPPING
@@ -2,10 +2,10 @@
     "presubmit": [
         {
             "file_patterns": ["(/|^)platform.xml"],
-            "name": "CtsPermission2TestCases",
+            "name": "CtsPermissionPolicyTestCases",
             "options": [
                 {
-                    "include-filter": "android.permission2.cts.RuntimePermissionProperties"
+                    "include-filter": "android.permissionpolicy.cts.RuntimePermissionProperties"
                 }
             ]
         }
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index 27e0b18..5d77713 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -14,13 +14,12 @@
   ~ limitations under the License.
   -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24"
-        android:tint="@color/decor_button_dark_color">
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
     <group android:translateY="8.0">
         <path
-            android:fillColor="@android:color/white" android:pathData="M3,5V3H21V5Z"/>
+            android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
     </group>
 </vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml
new file mode 100644
index 0000000..0225949
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape android:shape="rectangle"
+       xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#bf309fb5" />
+    <corners android:radius="20dp" />
+    <stroke android:width="1dp" color="#A00080FF"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml b/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml
new file mode 100644
index 0000000..3e0297a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector android:height="24dp" android:tint="#000000"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/black" android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
new file mode 100644
index 0000000..35562b6
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/desktop_mode_caption"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal"
+    android:orientation="horizontal"
+    android:background="@drawable/desktop_mode_decor_title">
+
+    <LinearLayout
+        android:id="@+id/open_menu_button"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        android:clickable="true"
+        android:focusable="true"
+        android:paddingStart="8dp"
+        android:background="?android:selectableItemBackgroundBorderless">
+
+        <ImageView
+            android:id="@+id/application_icon"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_margin="4dp"
+            android:layout_gravity="center_vertical"
+            android:contentDescription="@string/app_icon_text" />
+
+        <TextView
+            android:id="@+id/application_name"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:minWidth="80dp"
+            android:textColor="@color/desktop_mode_caption_app_name_dark"
+            android:textSize="14sp"
+            android:textFontWeight="500"
+            android:gravity="center_vertical"
+            android:layout_weight="1"
+            android:paddingStart="4dp"
+            android:paddingEnd="4dp"
+            tools:text="Gmail"/>
+
+        <ImageButton
+            android:id="@+id/expand_menu_button"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:padding="4dp"
+            android:contentDescription="@string/collapse_menu_text"
+            android:src="@drawable/ic_baseline_expand_more_24"
+            android:tint="@color/desktop_mode_caption_expand_button_dark"
+            android:background="@null"
+            android:scaleType="fitCenter"
+            android:clickable="false"
+            android:focusable="false"
+            android:layout_gravity="center_vertical"/>
+
+    </LinearLayout>
+
+    <View
+        android:id="@+id/caption_handle"
+        android:layout_width="wrap_content"
+        android:layout_height="40dp"
+        android:layout_weight="1"/>
+
+    <ImageButton
+        android:id="@+id/close_window"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:padding="4dp"
+        android:layout_marginEnd="8dp"
+        android:contentDescription="@string/close_button_text"
+        android:src="@drawable/decor_close_button_dark"
+        android:scaleType="fitCenter"
+        android:gravity="end"
+        android:background="?android:selectableItemBackgroundBorderless"
+        android:tint="@color/desktop_mode_caption_close_button_dark"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
index f9aeb6a..ac13eae 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
@@ -50,7 +50,7 @@
             android:layout_marginEnd="10dp"
             android:contentDescription="@string/collapse_menu_text"
             android:layout_alignParentEnd="true"
-            android:background="@drawable/caption_collapse_menu_button"
+            android:background="@drawable/ic_baseline_expand_more_24"
             android:layout_centerVertical="true"/>
     </RelativeLayout>
     <LinearLayout
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
similarity index 67%
rename from libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
rename to libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index 29cf151..5ab159c 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -16,26 +16,21 @@
   -->
 <com.android.wm.shell.windowdecor.WindowDecorLinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/desktop_mode_caption"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:gravity="center_horizontal"
     android:background="@drawable/desktop_mode_decor_title">
-    <Button
-        style="@style/CaptionButtonStyle"
-        android:id="@+id/back_button"
-        android:contentDescription="@string/back_button_text"
-        android:background="@drawable/decor_back_button_dark"/>
-    <Button
+
+    <ImageButton
         android:id="@+id/caption_handle"
         android:layout_width="128dp"
-        android:layout_height="32dp"
-        android:layout_margin="5dp"
+        android:layout_height="42dp"
         android:contentDescription="@string/handle_text"
-        android:background="@drawable/decor_handle_dark"/>
-    <Button
-        style="@style/CaptionButtonStyle"
-        android:id="@+id/close_window"
-        android:contentDescription="@string/close_button_text"
-        android:background="@drawable/decor_close_button_dark"/>
+        android:src="@drawable/decor_handle_dark"
+        tools:tint="@color/desktop_mode_caption_handle_bar_dark"
+        android:scaleType="fitXY"
+        android:background="?android:selectableItemBackground"/>
+
 </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 6fb70006..2a03b03 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -54,4 +54,14 @@
     <color name="splash_screen_bg_light">#FFFFFF</color>
     <color name="splash_screen_bg_dark">#000000</color>
     <color name="splash_window_background_default">@color/splash_screen_bg_light</color>
+
+    <!-- Desktop Mode -->
+    <color name="desktop_mode_caption_handle_bar_light">#EFF1F2</color>
+    <color name="desktop_mode_caption_handle_bar_dark">#1C1C17</color>
+    <color name="desktop_mode_caption_expand_button_light">#EFF1F2</color>
+    <color name="desktop_mode_caption_expand_button_dark">#48473A</color>
+    <color name="desktop_mode_caption_close_button_light">#EFF1F2</color>
+    <color name="desktop_mode_caption_close_button_dark">#1C1C17</color>
+    <color name="desktop_mode_caption_app_name_light">#EFF1F2</color>
+    <color name="desktop_mode_caption_app_name_dark">#1C1C17</color>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 3d5230d..57b5b8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -82,6 +82,7 @@
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.startingsurface.StartingSurface;
@@ -520,6 +521,9 @@
                         desktopModeTaskRepository, mainExecutor));
     }
 
+    @BindsOptionalOf
+    abstract RecentsTransitionHandler optionalRecentsTransitionHandler();
+
     //
     // Shell transitions
     //
@@ -803,6 +807,7 @@
             Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
             Optional<FreeformComponents> freeformComponents,
             Optional<RecentTasksController> recentTasksOptional,
+            Optional<RecentsTransitionHandler> recentsTransitionHandlerOptional,
             Optional<OneHandedController> oneHandedControllerOptional,
             Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional,
             Optional<ActivityEmbeddingController> activityEmbeddingOptional,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 7a83d10..eb7c32f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -83,6 +83,7 @@
 import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
@@ -528,9 +529,20 @@
             ShellInit shellInit,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<PipTouchHandler> pipTouchHandlerOptional,
+            Optional<RecentsTransitionHandler> recentsTransitionHandler,
             Transitions transitions) {
         return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
-                pipTouchHandlerOptional);
+                pipTouchHandlerOptional, recentsTransitionHandler);
+    }
+
+    @WMSingleton
+    @Provides
+    static RecentsTransitionHandler provideRecentsTransitionHandler(
+            ShellInit shellInit,
+            Transitions transitions,
+            Optional<RecentTasksController> recentTasksController) {
+        return new RecentsTransitionHandler(shellInit, transitions,
+                recentTasksController.orElse(null));
     }
 
     //
@@ -659,13 +671,17 @@
             Context context,
             ShellInit shellInit,
             ShellController shellController,
+            DisplayController displayController,
             ShellTaskOrganizer shellTaskOrganizer,
+            SyncTransactionQueue syncQueue,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             Transitions transitions,
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
-        return new DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer,
-                transitions, desktopModeTaskRepository, mainExecutor);
+        return new DesktopTasksController(context, shellInit, shellController, displayController,
+                shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions,
+                desktopModeTaskRepository, mainExecutor);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
new file mode 100644
index 0000000..015d5c1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Animated visual indicator for Desktop Mode windowing transitions.
+ */
+public class DesktopModeVisualIndicator {
+
+    private final Context mContext;
+    private final DisplayController mDisplayController;
+    private final ShellTaskOrganizer mTaskOrganizer;
+    private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
+    private final ActivityManager.RunningTaskInfo mTaskInfo;
+    private final SurfaceControl mTaskSurface;
+    private SurfaceControl mLeash;
+
+    private final SyncTransactionQueue mSyncQueue;
+    private SurfaceControlViewHost mViewHost;
+
+    public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
+            ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
+            Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
+            RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
+        mSyncQueue = syncQueue;
+        mTaskInfo = taskInfo;
+        mDisplayController = displayController;
+        mContext = context;
+        mTaskSurface = taskSurface;
+        mTaskOrganizer = taskOrganizer;
+        mRootTdaOrganizer = taskDisplayAreaOrganizer;
+    }
+
+    /**
+     * Create and animate the indicator for the exit desktop mode transition.
+     */
+    public void createFullscreenIndicator() {
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        final Resources resources = mContext.getResources();
+        final DisplayMetrics metrics = resources.getDisplayMetrics();
+        final int screenWidth = metrics.widthPixels;
+        final int screenHeight = metrics.heightPixels;
+        final int padding = mDisplayController
+                .getDisplayLayout(mTaskInfo.displayId).stableInsets().top;
+        final ImageView v = new ImageView(mContext);
+        v.setImageResource(R.drawable.desktop_windowing_transition_background);
+        final SurfaceControl.Builder builder = new SurfaceControl.Builder();
+        mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
+        mLeash = builder
+                .setName("Fullscreen Indicator")
+                .setContainerLayer()
+                .build();
+        t.show(mLeash);
+        final WindowManager.LayoutParams lp =
+                new WindowManager.LayoutParams(screenWidth, screenHeight,
+                        WindowManager.LayoutParams.TYPE_APPLICATION,
+                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+        lp.setTitle("Fullscreen indicator for Task=" + mTaskInfo.taskId);
+        lp.setTrustedOverlay();
+        final WindowlessWindowManager windowManager = new WindowlessWindowManager(
+                mTaskInfo.configuration, mLeash,
+                null /* hostInputToken */);
+        mViewHost = new SurfaceControlViewHost(mContext,
+                mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
+                "FullscreenVisualIndicator");
+        mViewHost.setView(v, lp);
+        // We want this indicator to be behind the dragged task, but in front of all others.
+        t.setRelativeLayer(mLeash, mTaskSurface, -1);
+
+        mSyncQueue.runInSync(transaction -> {
+            transaction.merge(t);
+            t.close();
+        });
+        final Rect startBounds = new Rect(padding, padding,
+                screenWidth - padding, screenHeight - padding);
+        final VisualIndicatorAnimator animator = VisualIndicatorAnimator.fullscreenIndicator(v,
+                startBounds);
+        animator.start();
+    }
+
+    /**
+     * Release the indicator and its components when it is no longer needed.
+     */
+    public void releaseFullscreenIndicator() {
+        if (mViewHost == null) return;
+        if (mViewHost != null) {
+            mViewHost.release();
+            mViewHost = null;
+        }
+
+        if (mLeash != null) {
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            t.remove(mLeash);
+            mLeash = null;
+            mSyncQueue.runInSync(transaction -> {
+                transaction.merge(t);
+                t.close();
+            });
+        }
+    }
+    /**
+     * Animator for Desktop Mode transitions which supports bounds and alpha animation.
+     */
+    private static class VisualIndicatorAnimator extends ValueAnimator {
+        private static final int FULLSCREEN_INDICATOR_DURATION = 200;
+        private static final float SCALE_ADJUSTMENT_PERCENT = 0.015f;
+        private static final float INDICATOR_FINAL_OPACITY = 0.7f;
+
+        private final ImageView mView;
+        private final Rect mStartBounds;
+        private final Rect mEndBounds;
+        private final RectEvaluator mRectEvaluator;
+
+        private VisualIndicatorAnimator(ImageView view, Rect startBounds,
+                Rect endBounds) {
+            mView = view;
+            mStartBounds = new Rect(startBounds);
+            mEndBounds = endBounds;
+            setFloatValues(0, 1);
+            mRectEvaluator = new RectEvaluator(new Rect());
+        }
+
+        /**
+         * Create animator for visual indicator of fullscreen transition
+         *
+         * @param view        the view for this indicator
+         * @param startBounds the starting bounds of the fullscreen indicator
+         */
+        public static VisualIndicatorAnimator fullscreenIndicator(ImageView view,
+                Rect startBounds) {
+            view.getDrawable().setBounds(startBounds);
+            int width = startBounds.width();
+            int height = startBounds.height();
+            Rect endBounds = new Rect((int) (startBounds.left - (SCALE_ADJUSTMENT_PERCENT * width)),
+                    (int) (startBounds.top - (SCALE_ADJUSTMENT_PERCENT * height)),
+                    (int) (startBounds.right + (SCALE_ADJUSTMENT_PERCENT * width)),
+                    (int) (startBounds.bottom + (SCALE_ADJUSTMENT_PERCENT * height)));
+            VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+                    view, startBounds, endBounds);
+            animator.setInterpolator(new DecelerateInterpolator());
+            setupFullscreenIndicatorAnimation(animator);
+            return animator;
+        }
+
+        /**
+         * Add necessary listener for animation of fullscreen indicator
+         */
+        private static void setupFullscreenIndicatorAnimation(
+                VisualIndicatorAnimator animator) {
+            animator.addUpdateListener(a -> {
+                if (animator.mView != null) {
+                    animator.updateBounds(a.getAnimatedFraction(), animator.mView);
+                    animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
+                } else {
+                    animator.cancel();
+                }
+            });
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    animator.mView.getDrawable().setBounds(animator.mEndBounds);
+                }
+            });
+            animator.setDuration(FULLSCREEN_INDICATOR_DURATION);
+        }
+
+        /**
+         * Update bounds of view based on current animation fraction.
+         * Use of delta is to animate bounds independently, in case we need to
+         * run multiple animations simultaneously.
+         *
+         * @param fraction fraction to use, compared against previous fraction
+         * @param view     the view to update
+         */
+        private void updateBounds(float fraction, ImageView view) {
+            Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
+            view.getDrawable().setBounds(currentBounds);
+        }
+
+        /**
+         * Fade in the fullscreen indicator
+         *
+         * @param fraction current animation fraction
+         */
+        private void updateIndicatorAlpha(float fraction, View view) {
+            view.setAlpha(fraction * INDICATOR_FINAL_OPACITY);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 31c5e33..5696dfc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -17,6 +17,7 @@
 package com.android.wm.shell.desktopmode
 
 import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
 import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -37,11 +38,14 @@
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
 import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.ExecutorUtils
 import com.android.wm.shell.common.ExternalInterfaceBinder
 import com.android.wm.shell.common.RemoteCallable
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.common.annotations.ExternalThread
 import com.android.wm.shell.common.annotations.ShellMainThread
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
@@ -55,16 +59,20 @@
 
 /** Handles moving tasks in and out of desktop */
 class DesktopTasksController(
-    private val context: Context,
-    shellInit: ShellInit,
-    private val shellController: ShellController,
-    private val shellTaskOrganizer: ShellTaskOrganizer,
-    private val transitions: Transitions,
-    private val desktopModeTaskRepository: DesktopModeTaskRepository,
-    @ShellMainThread private val mainExecutor: ShellExecutor
+        private val context: Context,
+        shellInit: ShellInit,
+        private val shellController: ShellController,
+        private val displayController: DisplayController,
+        private val shellTaskOrganizer: ShellTaskOrganizer,
+        private val syncQueue: SyncTransactionQueue,
+        private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+        private val transitions: Transitions,
+        private val desktopModeTaskRepository: DesktopModeTaskRepository,
+        @ShellMainThread private val mainExecutor: ShellExecutor
 ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
 
     private val desktopMode: DesktopModeImpl
+    private var visualIndicator: DesktopModeVisualIndicator? = null
 
     init {
         desktopMode = DesktopModeImpl()
@@ -298,6 +306,52 @@
     }
 
     /**
+     * Perform checks required on drag move. Create/release fullscreen indicator as needed.
+     *
+     * @param taskInfo the task being dragged.
+     * @param taskSurface SurfaceControl of dragged task.
+     * @param y coordinate of dragged task. Used for checks against status bar height.
+     */
+    fun onDragPositioningMove(
+            taskInfo: RunningTaskInfo,
+            taskSurface: SurfaceControl,
+            y: Float
+    ) {
+        val statusBarHeight = displayController
+                .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+        if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+            if (y <= statusBarHeight && visualIndicator == null) {
+                visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
+                        displayController, context, taskSurface, shellTaskOrganizer,
+                        rootTaskDisplayAreaOrganizer)
+                visualIndicator?.createFullscreenIndicator()
+            } else if (y > statusBarHeight && visualIndicator != null) {
+                visualIndicator?.releaseFullscreenIndicator()
+                visualIndicator = null
+            }
+        }
+    }
+
+    /**
+     * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
+     *
+     * @param taskInfo the task being dragged.
+     * @param y height of drag, to be checked against status bar height.
+     */
+    fun onDragPositioningEnd(
+            taskInfo: RunningTaskInfo,
+            y: Float
+    ) {
+        val statusBarHeight = displayController
+                .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+        if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+            moveToFullscreen(taskInfo.taskId)
+            visualIndicator?.releaseFullscreenIndicator()
+            visualIndicator = null
+        }
+    }
+
+    /**
      * Adds a listener to find out about changes in the visibility of freeform tasks.
      *
      * @param listener the listener to add.
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 52f5a8c..c19d543 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
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.pip;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -536,6 +537,15 @@
             mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
             return;
         }
+        if (mSplitScreenOptional.isPresent()) {
+            // If pip activity will reparent to origin task case and if the origin task still under
+            // split root, just exit split screen here to ensure it could expand to fullscreen.
+            SplitScreenController split = mSplitScreenOptional.get();
+            if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
+                split.exitSplitScreen(INVALID_TASK_ID,
+                        SplitScreenController.EXIT_REASON_APP_FINISHED);
+            }
+        }
         mSyncTransactionQueue.queue(wct);
         mSyncTransactionQueue.runInSync(t -> {
             // Make sure to grab the latest source hint rect as it could have been
@@ -1479,9 +1489,13 @@
                 applyFinishBoundsResize(wct, direction, false);
             }
         } else {
-            final boolean isPipTopLeft =
-                    direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && isPipToTopLeft();
-            applyFinishBoundsResize(wct, direction, isPipTopLeft);
+            applyFinishBoundsResize(wct, direction, isPipToTopLeft());
+            // Use sync transaction to apply finish transaction for enter split case.
+            if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+                mSyncTransactionQueue.runInSync(t -> {
+                    t.merge(tx);
+                });
+            }
         }
 
         finishResizeForMenu(destinationBounds);
@@ -1518,7 +1532,10 @@
         mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
 
         wct.setBounds(mToken, taskBounds);
-        wct.setBoundsChangeTransaction(mToken, tx);
+        // Pip to split should use sync transaction to sync split bounds change.
+        if (direction != TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+            wct.setBoundsChangeTransaction(mToken, tx);
+        }
     }
 
     /**
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 979b7c7..167c032 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
@@ -282,7 +282,8 @@
 
     public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
         final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent()
-                && mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId);
+                && mSplitScreenControllerOptional.get().isTaskInSplitScreenForeground(
+                taskInfo.taskId);
         mFocusedTaskAllowSplitScreen = isSplitScreen
                 || (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
                 && taskInfo.supportsMultiWindow
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 1a6c1d6..4048c5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -17,6 +17,11 @@
 package com.android.wm.shell.recents;
 
 import android.app.ActivityManager.RunningTaskInfo;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.IRecentsAnimationRunner;
 
 import com.android.wm.shell.recents.IRecentTasksListener;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -45,4 +50,10 @@
      * Gets the set of running tasks.
      */
     RunningTaskInfo[] getRunningTasks(int maxNum) = 4;
+
+    /**
+     * Starts a recents transition.
+     */
+    oneway void startRecentsTransition(in PendingIntent intent, in Intent fillIn, in Bundle options,
+                    IApplicationThread appThread, IRecentsAnimationRunner listener) = 5;
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 0d9faa3..c5bfd87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -24,13 +24,18 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
 import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
+import android.view.IRecentsAnimationRunner;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
@@ -79,6 +84,7 @@
     private final TaskStackListenerImpl mTaskStackListener;
     private final RecentTasksImpl mImpl = new RecentTasksImpl();
     private final ActivityTaskManager mActivityTaskManager;
+    private RecentsTransitionHandler mTransitionHandler = null;
     private IRecentTasksListener mListener;
     private final boolean mIsDesktopMode;
 
@@ -150,6 +156,10 @@
         mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
     }
 
+    void setTransitionHandler(RecentsTransitionHandler handler) {
+        mTransitionHandler = handler;
+    }
+
     /**
      * Adds a split pair. This call does not validate the taskIds, only that they are not the same.
      */
@@ -492,5 +502,18 @@
                     true /* blocking */);
             return tasks[0];
         }
+
+        @Override
+        public void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+                IApplicationThread appThread, IRecentsAnimationRunner listener) {
+            if (mController.mTransitionHandler == null) {
+                Slog.e(TAG, "Used shell-transitions startRecentsTransition without"
+                        + " shell-transitions");
+                return;
+            }
+            executeRemoteCallWithTaskPermission(mController, "startRecentsTransition",
+                    (controller) -> controller.mTransitionHandler.startRecentsTransition(
+                            intent, fillIn, options, appThread, listener));
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
new file mode 100644
index 0000000..2ec64b0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -0,0 +1,760 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.recents;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_SLEEP;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.IRecentsAnimationController;
+import android.view.IRecentsAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.window.PictureInPictureSurfaceTransaction;
+import android.window.TaskSnapshot;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Handles the Recents (overview) animation. Only one of these can run at a time. A recents
+ * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored.
+ */
+public class RecentsTransitionHandler implements Transitions.TransitionHandler {
+    private static final String TAG = "RecentsTransitionHandler";
+
+    private final Transitions mTransitions;
+    private final ShellExecutor mExecutor;
+    private IApplicationThread mAnimApp = null;
+    private final ArrayList<RecentsController> mControllers = new ArrayList<>();
+
+    /**
+     * List of other handlers which might need to mix recents with other things. These are checked
+     * in the order they are added. Ideally there should only be one.
+     */
+    private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
+
+    public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
+            @Nullable RecentTasksController recentTasksController) {
+        mTransitions = transitions;
+        mExecutor = transitions.getMainExecutor();
+        if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
+        if (recentTasksController == null) return;
+        shellInit.addInitCallback(() -> {
+            recentTasksController.setTransitionHandler(this);
+            transitions.addHandler(this);
+        }, this);
+    }
+
+    /** Register a mixer handler. {@see RecentsMixedHandler}*/
+    public void addMixer(RecentsMixedHandler mixer) {
+        mMixers.add(mixer);
+    }
+
+    /** Unregister a Mixed Handler */
+    public void removeMixer(RecentsMixedHandler mixer) {
+        mMixers.remove(mixer);
+    }
+
+    void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+            IApplicationThread appThread, IRecentsAnimationRunner listener) {
+        // only care about latest one.
+        mAnimApp = appThread;
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.sendPendingIntent(intent, fillIn, options);
+        final RecentsController controller = new RecentsController(listener);
+        RecentsMixedHandler mixer = null;
+        Transitions.TransitionHandler mixedHandler = null;
+        for (int i = 0; i < mMixers.size(); ++i) {
+            mixedHandler = mMixers.get(i).handleRecentsRequest(wct);
+            if (mixedHandler != null) {
+                mixer = mMixers.get(i);
+                break;
+            }
+        }
+        final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
+                mixedHandler == null ? this : mixedHandler);
+        if (mixer != null) {
+            mixer.setRecentsTransition(transition);
+        }
+        if (transition == null) {
+            controller.cancel();
+            return;
+        }
+        controller.setTransition(transition);
+        mControllers.add(controller);
+    }
+
+    @Override
+    public WindowContainerTransaction handleRequest(IBinder transition,
+            TransitionRequestInfo request) {
+        // do not directly handle requests. Only entry point should be via startRecentsTransition
+        return null;
+    }
+
+    private int findController(IBinder transition) {
+        for (int i = mControllers.size() - 1; i >= 0; --i) {
+            if (mControllers.get(i).mTransition == transition) return i;
+        }
+        return -1;
+    }
+
+    @Override
+    public boolean startAnimation(IBinder transition, TransitionInfo info,
+            SurfaceControl.Transaction startTransaction,
+            SurfaceControl.Transaction finishTransaction,
+            Transitions.TransitionFinishCallback finishCallback) {
+        final int controllerIdx = findController(transition);
+        if (controllerIdx < 0) return false;
+        final RecentsController controller = mControllers.get(controllerIdx);
+        Transitions.setRunningRemoteTransitionDelegate(mAnimApp);
+        mAnimApp = null;
+        if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void mergeAnimation(IBinder transition, TransitionInfo info,
+            SurfaceControl.Transaction t, IBinder mergeTarget,
+            Transitions.TransitionFinishCallback finishCallback) {
+        final int targetIdx = findController(mergeTarget);
+        if (targetIdx < 0) return;
+        final RecentsController controller = mControllers.get(targetIdx);
+        controller.merge(info, t, finishCallback);
+    }
+
+    @Override
+    public void onTransitionConsumed(IBinder transition, boolean aborted,
+            SurfaceControl.Transaction finishTransaction) {
+        final int idx = findController(transition);
+        if (idx < 0) return;
+        mControllers.get(idx).cancel();
+    }
+
+    /** There is only one of these and it gets reset on finish. */
+    private class RecentsController extends IRecentsAnimationController.Stub {
+        private IRecentsAnimationRunner mListener;
+        private IBinder.DeathRecipient mDeathHandler;
+        private Transitions.TransitionFinishCallback mFinishCB = null;
+        private SurfaceControl.Transaction mFinishTransaction = null;
+
+        /**
+         * List of tasks that we are switching away from via this transition. Upon finish, these
+         * pausing tasks will become invisible.
+         * These need to be ordered since the order must be restored if there is no task-switch.
+         */
+        private ArrayList<TaskState> mPausingTasks = null;
+
+        /**
+         * List of tasks that we are switching to. Upon finish, these will remain visible and
+         * on top.
+         */
+        private ArrayList<TaskState> mOpeningTasks = null;
+
+        private WindowContainerToken mPipTask = null;
+        private WindowContainerToken mRecentsTask = null;
+        private int mRecentsTaskId = -1;
+        private TransitionInfo mInfo = null;
+        private boolean mOpeningSeparateHome = false;
+        private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
+        private PictureInPictureSurfaceTransaction mPipTransaction = null;
+        private IBinder mTransition = null;
+        private boolean mKeyguardLocked = false;
+        private boolean mWillFinishToHome = false;
+
+        /** The animation is idle, waiting for the user to choose a task to switch to. */
+        private static final int STATE_NORMAL = 0;
+
+        /** The user chose a new task to switch to and the animation is animating to it. */
+        private static final int STATE_NEW_TASK = 1;
+
+        /** The latest state that the recents animation is operating in. */
+        private int mState = STATE_NORMAL;
+
+        RecentsController(IRecentsAnimationRunner listener) {
+            mListener = listener;
+            mDeathHandler = () -> mExecutor.execute(() -> {
+                if (mListener == null) return;
+                if (mFinishCB != null) {
+                    finish(mWillFinishToHome, false /* leaveHint */);
+                }
+            });
+            try {
+                mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
+            } catch (RemoteException e) {
+                mListener = null;
+            }
+        }
+
+        void setTransition(IBinder transition) {
+            mTransition = transition;
+        }
+
+        void cancel() {
+            // restoring (to-home = false) involves submitting more WM changes, so by default, use
+            // toHome = true when canceling.
+            cancel(true /* toHome */);
+        }
+
+        void cancel(boolean toHome) {
+            if (mListener != null) {
+                try {
+                    mListener.onAnimationCanceled(null, null);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error canceling recents animation", e);
+                }
+            }
+            if (mFinishCB != null) {
+                finish(toHome, false /* userLeave */);
+            } else {
+                cleanUp();
+            }
+        }
+
+        /**
+         * Sends a cancel message to the recents animation with snapshots. Used to trigger a
+         * "replace-with-screenshot" like behavior.
+         */
+        private boolean sendCancelWithSnapshots() {
+            int[] taskIds = null;
+            TaskSnapshot[] snapshots = null;
+            if (mPausingTasks.size() > 0) {
+                taskIds = new int[mPausingTasks.size()];
+                snapshots = new TaskSnapshot[mPausingTasks.size()];
+                try {
+                    for (int i = 0; i < mPausingTasks.size(); ++i) {
+                        snapshots[i] = ActivityTaskManager.getService().takeTaskSnapshot(
+                                mPausingTasks.get(0).mTaskInfo.taskId);
+                    }
+                } catch (RemoteException e) {
+                    taskIds = null;
+                    snapshots = null;
+                }
+            }
+            try {
+                mListener.onAnimationCanceled(taskIds, snapshots);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error canceling recents animation", e);
+                return false;
+            }
+            return true;
+        }
+
+        void cleanUp() {
+            if (mListener != null && mDeathHandler != null) {
+                mListener.asBinder().unlinkToDeath(mDeathHandler, 0 /* flags */);
+                mDeathHandler = null;
+            }
+            mListener = null;
+            mFinishCB = null;
+            // clean-up leash surfacecontrols and anything that might reference them.
+            if (mLeashMap != null) {
+                for (int i = 0; i < mLeashMap.size(); ++i) {
+                    mLeashMap.valueAt(i).release();
+                }
+                mLeashMap = null;
+            }
+            mFinishTransaction = null;
+            mPausingTasks = null;
+            mOpeningTasks = null;
+            mInfo = null;
+            mTransition = null;
+            mControllers.remove(this);
+        }
+
+        boolean start(TransitionInfo info, SurfaceControl.Transaction t,
+                SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
+            if (mListener == null || mTransition == null) {
+                cleanUp();
+                return false;
+            }
+            // First see if this is a valid recents transition.
+            boolean hasPausingTasks = false;
+            for (int i = 0; i < info.getChanges().size(); ++i) {
+                final TransitionInfo.Change change = info.getChanges().get(i);
+                if (TransitionUtil.isWallpaper(change)) continue;
+                if (TransitionUtil.isClosingType(change.getMode())) {
+                    hasPausingTasks = true;
+                    continue;
+                }
+                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+                if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
+                    mRecentsTask = taskInfo.token;
+                    mRecentsTaskId = taskInfo.taskId;
+                } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+                    mRecentsTask = taskInfo.token;
+                    mRecentsTaskId = taskInfo.taskId;
+                }
+            }
+            if (mRecentsTask == null && !hasPausingTasks) {
+                // Recents is already running apparently, so this is a no-op.
+                Slog.e(TAG, "Tried to start recents while it is already running.");
+                cleanUp();
+                return false;
+            }
+
+            mInfo = info;
+            mFinishCB = finishCB;
+            mFinishTransaction = finishT;
+            mPausingTasks = new ArrayList<>();
+            mOpeningTasks = new ArrayList<>();
+            mLeashMap = new ArrayMap<>();
+            mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
+
+            final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
+            final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
+            TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter();
+            // About layering: we divide up the "layer space" into 3 regions (each the size of
+            // the change count). This lets us categorize things into above/below/between
+            // while maintaining their relative ordering.
+            for (int i = 0; i < info.getChanges().size(); ++i) {
+                final TransitionInfo.Change change = info.getChanges().get(i);
+                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+                if (TransitionUtil.isWallpaper(change)) {
+                    final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
+                            // wallpapers go into the "below" layer space
+                            info.getChanges().size() - i, info, t, mLeashMap);
+                    wallpapers.add(target);
+                    // Make all the wallpapers opaque since we want them visible from the start
+                    t.setAlpha(target.leash, 1);
+                } else if (leafTaskFilter.test(change)) {
+                    // start by putting everything into the "below" layer space.
+                    final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
+                            info.getChanges().size() - i, info, t, mLeashMap);
+                    apps.add(target);
+                    if (TransitionUtil.isClosingType(change.getMode())) {
+                        // raise closing (pausing) task to "above" layer so it isn't covered
+                        t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+                        mPausingTasks.add(new TaskState(change, target.leash));
+                        if (taskInfo.pictureInPictureParams != null
+                                && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
+                            mPipTask = taskInfo.token;
+                        }
+                    } else if (taskInfo != null
+                            && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
+                        // There's a 3p launcher, so make sure recents goes above that.
+                        t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+                    } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+                        // do nothing
+                    } else if (TransitionUtil.isOpeningType(change.getMode())) {
+                        mOpeningTasks.add(new TaskState(change, target.leash));
+                    }
+                }
+            }
+            t.apply();
+            try {
+                mListener.onAnimationStart(this,
+                        apps.toArray(new RemoteAnimationTarget[apps.size()]),
+                        wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
+                        new Rect(0, 0, 0, 0), new Rect());
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error starting recents animation", e);
+                cancel();
+            }
+            return true;
+        }
+
+        @SuppressLint("NewApi")
+        void merge(TransitionInfo info, SurfaceControl.Transaction t,
+                Transitions.TransitionFinishCallback finishCallback) {
+            if (mFinishCB == null) {
+                // This was no-op'd (likely a repeated start) and we've already sent finish.
+                return;
+            }
+            if (info.getType() == TRANSIT_SLEEP) {
+                // A sleep event means we need to stop animations immediately, so cancel here.
+                cancel();
+                return;
+            }
+            ArrayList<TransitionInfo.Change> openingTasks = null;
+            ArrayList<TransitionInfo.Change> closingTasks = null;
+            mOpeningSeparateHome = false;
+            TransitionInfo.Change recentsOpening = null;
+            boolean foundRecentsClosing = false;
+            boolean hasChangingApp = false;
+            final TransitionUtil.LeafTaskFilter leafTaskFilter =
+                    new TransitionUtil.LeafTaskFilter();
+            for (int i = 0; i < info.getChanges().size(); ++i) {
+                final TransitionInfo.Change change = info.getChanges().get(i);
+                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+                final boolean isLeafTask = leafTaskFilter.test(change);
+                if (TransitionUtil.isOpeningType(change.getMode())) {
+                    if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+                        recentsOpening = change;
+                    } else if (isLeafTask) {
+                        if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+                            // This is usually a 3p launcher
+                            mOpeningSeparateHome = true;
+                        }
+                        if (openingTasks == null) {
+                            openingTasks = new ArrayList<>();
+                        }
+                        openingTasks.add(change);
+                    }
+                } else if (TransitionUtil.isClosingType(change.getMode())) {
+                    if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+                        foundRecentsClosing = true;
+                    } else if (isLeafTask) {
+                        if (closingTasks == null) {
+                            closingTasks = new ArrayList<>();
+                        }
+                        closingTasks.add(change);
+                    }
+                } else if (change.getMode() == TRANSIT_CHANGE) {
+                    // Finish recents animation if the display is changed, so the default
+                    // transition handler can play the animation such as rotation effect.
+                    if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) {
+                        cancel(mWillFinishToHome);
+                        return;
+                    }
+                    hasChangingApp = true;
+                }
+            }
+            if (hasChangingApp && foundRecentsClosing) {
+                // This happens when a visible app is expanding (usually PiP). In this case,
+                // that transition probably has a special-purpose animation, so finish recents
+                // now and let it do its animation (since recents is going to be occluded).
+                sendCancelWithSnapshots();
+                mExecutor.executeDelayed(
+                        () -> finishInner(true /* toHome */, false /* userLeaveHint */), 0);
+                return;
+            }
+            if (recentsOpening != null) {
+                // the recents task re-appeared. This happens if the user gestures before the
+                // task-switch (NEW_TASK) animation finishes.
+                if (mState == STATE_NORMAL) {
+                    Slog.e(TAG, "Returning to recents while recents is already idle.");
+                }
+                if (closingTasks == null || closingTasks.size() == 0) {
+                    Slog.e(TAG, "Returning to recents without closing any opening tasks.");
+                }
+                // Setup may hide it initially since it doesn't know that overview was still active.
+                t.show(recentsOpening.getLeash());
+                t.setAlpha(recentsOpening.getLeash(), 1.f);
+                mState = STATE_NORMAL;
+            }
+            boolean didMergeThings = false;
+            if (closingTasks != null) {
+                // Cancelling a task-switch. Move the tasks back to mPausing from mOpening
+                for (int i = 0; i < closingTasks.size(); ++i) {
+                    final TransitionInfo.Change change = closingTasks.get(i);
+                    int openingIdx = TaskState.indexOf(mOpeningTasks, change);
+                    if (openingIdx < 0) {
+                        Slog.e(TAG, "Back to existing recents animation from an unrecognized "
+                                + "task: " + change.getTaskInfo().taskId);
+                        continue;
+                    }
+                    mPausingTasks.add(mOpeningTasks.remove(openingIdx));
+                    didMergeThings = true;
+                }
+            }
+            RemoteAnimationTarget[] appearedTargets = null;
+            if (openingTasks != null && openingTasks.size() > 0) {
+                // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
+                // enter NEW_TASK state since this will start the switch-to animation.
+                final int layer = mInfo.getChanges().size() * 3;
+                appearedTargets = new RemoteAnimationTarget[openingTasks.size()];
+                for (int i = 0; i < openingTasks.size(); ++i) {
+                    final TransitionInfo.Change change = openingTasks.get(i);
+                    int pausingIdx = TaskState.indexOf(mPausingTasks, change);
+                    if (pausingIdx >= 0) {
+                        // Something is showing/opening a previously-pausing app.
+                        appearedTargets[i] = TransitionUtil.newTarget(
+                                change, layer, mPausingTasks.get(pausingIdx).mLeash);
+                        mOpeningTasks.add(mPausingTasks.remove(pausingIdx));
+                        // Setup hides opening tasks initially, so make it visible again (since we
+                        // are already showing it).
+                        t.show(change.getLeash());
+                        t.setAlpha(change.getLeash(), 1.f);
+                    } else {
+                        // We are receiving new opening tasks, so convert to onTasksAppeared.
+                        appearedTargets[i] = TransitionUtil.newTarget(
+                                change, layer, info, t, mLeashMap);
+                        // reparent into the original `mInfo` since that's where we are animating.
+                        final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+                        t.reparent(appearedTargets[i].leash, mInfo.getRoot(rootIdx).getLeash());
+                        t.setLayer(appearedTargets[i].leash, layer);
+                        mOpeningTasks.add(new TaskState(change, appearedTargets[i].leash));
+                    }
+                }
+                didMergeThings = true;
+                mState = STATE_NEW_TASK;
+            }
+            if (!didMergeThings) {
+                // Didn't recognize anything in incoming transition so don't merge it.
+                Slog.w(TAG, "Don't know how to merge this transition.");
+                return;
+            }
+            // At this point, we are accepting the merge.
+            t.apply();
+            // not using the incoming anim-only surfaces
+            info.releaseAnimSurfaces();
+            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            if (appearedTargets == null) return;
+            try {
+                mListener.onTasksAppeared(appearedTargets);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
+            }
+        }
+
+        @Override
+        public TaskSnapshot screenshotTask(int taskId) {
+            try {
+                return ActivityTaskManager.getService().takeTaskSnapshot(taskId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to screenshot task", e);
+            }
+            return null;
+        }
+
+        @Override
+        public void setInputConsumerEnabled(boolean enabled) {
+            mExecutor.execute(() -> {
+                if (mFinishCB == null || !enabled) return;
+                // transient launches don't receive focus automatically. Since we are taking over
+                // the gesture now, take focus explicitly.
+                // This also moves recents back to top if the user gestured before a switch
+                // animation finished.
+                try {
+                    ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to set focused task", e);
+                }
+            });
+        }
+
+        @Override
+        public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
+        }
+
+        @Override
+        public void setFinishTaskTransaction(int taskId,
+                PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
+            mExecutor.execute(() -> {
+                if (mFinishCB == null) return;
+                mPipTransaction = finishTransaction;
+            });
+        }
+
+        @Override
+        @SuppressLint("NewApi")
+        public void finish(boolean toHome, boolean sendUserLeaveHint) {
+            mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint));
+        }
+
+        private void finishInner(boolean toHome, boolean sendUserLeaveHint) {
+            if (mFinishCB == null) {
+                Slog.e(TAG, "Duplicate call to finish");
+                return;
+            }
+            final Transitions.TransitionFinishCallback finishCB = mFinishCB;
+            mFinishCB = null;
+
+            final SurfaceControl.Transaction t = mFinishTransaction;
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+            if (mKeyguardLocked && mRecentsTask != null) {
+                if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
+                else wct.restoreTransientOrder(mRecentsTask);
+            }
+            if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+                // The gesture is returning to the pausing-task(s) rather than continuing with
+                // recents, so end the transition by moving the app back to the top (and also
+                // re-showing it's task).
+                for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
+                    // reverse order so that index 0 ends up on top
+                    wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */);
+                    t.show(mPausingTasks.get(i).mTaskSurface);
+                }
+                if (!mKeyguardLocked && mRecentsTask != null) {
+                    wct.restoreTransientOrder(mRecentsTask);
+                }
+            } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
+                // Special situation where 3p launcher was changed during recents (this happens
+                // during tapltests...). Here we get both "return to home" AND "home opening".
+                // This is basically going home, but we have to restore the recents and home order.
+                for (int i = 0; i < mOpeningTasks.size(); ++i) {
+                    final TaskState state = mOpeningTasks.get(i);
+                    if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+                        // Make sure it is on top.
+                        wct.reorder(state.mToken, true /* onTop */);
+                    }
+                    t.show(state.mTaskSurface);
+                }
+                for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
+                    t.hide(mPausingTasks.get(i).mTaskSurface);
+                }
+                if (!mKeyguardLocked && mRecentsTask != null) {
+                    wct.restoreTransientOrder(mRecentsTask);
+                }
+            } else {
+                // The general case: committing to recents, going home, or switching tasks.
+                for (int i = 0; i < mOpeningTasks.size(); ++i) {
+                    t.show(mOpeningTasks.get(i).mTaskSurface);
+                }
+                for (int i = 0; i < mPausingTasks.size(); ++i) {
+                    if (!sendUserLeaveHint) {
+                        // This means recents is not *actually* finishing, so of course we gotta
+                        // do special stuff in WMCore to accommodate.
+                        wct.setDoNotPip(mPausingTasks.get(i).mToken);
+                    }
+                    // Since we will reparent out of the leashes, pre-emptively hide the child
+                    // surface to match the leash. Otherwise, there will be a flicker before the
+                    // visibility gets committed in Core when using split-screen (in splitscreen,
+                    // the leaf-tasks are not "independent" so aren't hidden by normal setup).
+                    t.hide(mPausingTasks.get(i).mTaskSurface);
+                }
+                if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) {
+                    t.show(mInfo.getChange(mPipTask).getLeash());
+                    PictureInPictureSurfaceTransaction.apply(mPipTransaction,
+                            mInfo.getChange(mPipTask).getLeash(), t);
+                    mPipTask = null;
+                    mPipTransaction = null;
+                }
+            }
+            cleanUp();
+            finishCB.onTransitionFinished(wct.isEmpty() ? null : wct, null /* wctCB */);
+        }
+
+        @Override
+        public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
+        }
+
+        @Override
+        public void cleanupScreenshot() {
+        }
+
+        @Override
+        public void setWillFinishToHome(boolean willFinishToHome) {
+            mExecutor.execute(() -> {
+                mWillFinishToHome = willFinishToHome;
+            });
+        }
+
+        /**
+         * @see IRecentsAnimationController#removeTask
+         */
+        @Override
+        public boolean removeTask(int taskId) {
+            return false;
+        }
+
+        /**
+         * @see IRecentsAnimationController#detachNavigationBarFromApp
+         */
+        @Override
+        public void detachNavigationBarFromApp(boolean moveHomeToTop) {
+            mExecutor.execute(() -> {
+                if (mTransition == null) return;
+                try {
+                    ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to detach the navigation bar from app", e);
+                }
+            });
+        }
+
+        /**
+         * @see IRecentsAnimationController#animateNavigationBarToApp(long)
+         */
+        @Override
+        public void animateNavigationBarToApp(long duration) {
+        }
+    };
+
+    /** Utility class to track the state of a task as-seen by recents. */
+    private static class TaskState {
+        WindowContainerToken mToken;
+        ActivityManager.RunningTaskInfo mTaskInfo;
+
+        /** The surface/leash of the task provided by Core. */
+        SurfaceControl mTaskSurface;
+
+        /** The (local) animation-leash created for this task. */
+        SurfaceControl mLeash;
+
+        TaskState(TransitionInfo.Change change, SurfaceControl leash) {
+            mToken = change.getContainer();
+            mTaskInfo = change.getTaskInfo();
+            mTaskSurface = change.getLeash();
+            mLeash = leash;
+        }
+
+        static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) {
+            for (int i = list.size() - 1; i >= 0; --i) {
+                if (list.get(i).mToken.equals(change.getContainer())) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        public String toString() {
+            return "" + mToken + " : " + mLeash;
+        }
+    }
+
+    /**
+     * An interface for a mixed handler to receive information about recents requests (since these
+     * come into this handler directly vs from WMCore request).
+     */
+    public interface RecentsMixedHandler {
+        /**
+         * Called when a recents request comes in. The handler can add operations to outWCT. If
+         * the handler wants to "accept" the transition, it should return itself; otherwise, it
+         * should return `null`.
+         *
+         * If a mixed-handler accepts this recents, it will be the de-facto handler for this
+         * transition and is required to call the associated {@link #startAnimation},
+         * {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods.
+         */
+        Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT);
+
+        /**
+         * Reports the transition token associated with the accepted recents request. If there was
+         * a problem starting the request, this will be called with `null`.
+         */
+        void setRecentsTransition(@Nullable IBinder transition);
+    }
+}
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 94b9e90..7d5ab84 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
@@ -31,7 +31,6 @@
 import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -89,7 +88,6 @@
 import com.android.wm.shell.draganddrop.DragAndDropPolicy;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
@@ -329,9 +327,14 @@
         return mTaskOrganizer.getRunningTaskInfo(taskId);
     }
 
+    /** Check task is under split or not by taskId. */
     public boolean isTaskInSplitScreen(int taskId) {
-        return isSplitScreenVisible()
-                && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
+        return mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
+    }
+
+    /** Check split is foreground and task is under split or not by taskId. */
+    public boolean isTaskInSplitScreenForeground(int taskId) {
+        return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
     }
 
     public @SplitPosition int getSplitPosition(int taskId) {
@@ -339,8 +342,7 @@
     }
 
     public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
-        return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition,
-                new WindowContainerTransaction());
+        return moveToStage(taskId, sideStagePosition, new WindowContainerTransaction());
     }
 
     /**
@@ -351,13 +353,13 @@
         mStageCoordinator.updateSurfaces(transaction);
     }
 
-    private boolean moveToStage(int taskId, @StageType int stageType,
-            @SplitPosition int stagePosition, WindowContainerTransaction wct) {
+    private boolean moveToStage(int taskId, @SplitPosition int stagePosition,
+            WindowContainerTransaction wct) {
         final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
         if (task == null) {
             throw new IllegalArgumentException("Unknown taskId" + taskId);
         }
-        return mStageCoordinator.moveToStage(task, stageType, stagePosition, wct);
+        return mStageCoordinator.moveToStage(task, stagePosition, wct);
     }
 
     public boolean removeFromSideStage(int taskId) {
@@ -382,10 +384,9 @@
     }
 
     public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
-        final int stageType = isSplitScreenVisible() ? STAGE_TYPE_UNDEFINED : STAGE_TYPE_SIDE;
         final int stagePosition =
                 leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
-        moveToStage(taskId, stageType, stagePosition, wct);
+        moveToStage(taskId, stagePosition, wct);
     }
 
     public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
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 a1eaf85..def945e 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
@@ -373,56 +373,43 @@
         return STAGE_TYPE_UNDEFINED;
     }
 
-    boolean moveToStage(ActivityManager.RunningTaskInfo task, @StageType int stageType,
-            @SplitPosition int stagePosition, WindowContainerTransaction wct) {
+    boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
+            WindowContainerTransaction wct) {
         StageTaskListener targetStage;
         int sideStagePosition;
-        if (stageType == STAGE_TYPE_MAIN) {
-            targetStage = mMainStage;
-            sideStagePosition = reverseSplitPosition(stagePosition);
-        } else if (stageType == STAGE_TYPE_SIDE) {
+        if (isSplitScreenVisible()) {
+            // If the split screen is foreground, retrieves target stage based on position.
+            targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
+            sideStagePosition = mSideStagePosition;
+        } else {
             targetStage = mSideStage;
             sideStagePosition = stagePosition;
-        } else {
-            if (isSplitScreenVisible()) {
-                // If the split screen is activated, retrieves target stage based on position.
-                targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
-                sideStagePosition = mSideStagePosition;
-            } else {
-                // Exit split if it running background.
-                exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
-
-                targetStage = mSideStage;
-                sideStagePosition = stagePosition;
-            }
         }
 
         if (!isSplitActive()) {
-            // prevent the fling divider to center transitioni if split screen didn't active.
-            mIsDropEntering = true;
+            mSplitLayout.init();
+            prepareEnterSplitScreen(wct, task, stagePosition);
+            mSyncQueue.queue(wct);
+            mSyncQueue.runInSync(t -> {
+                updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+            });
+        } else {
+            setSideStagePosition(sideStagePosition, wct);
+            targetStage.addTask(task, wct);
+            targetStage.evictAllChildren(wct);
+            if (!isSplitScreenVisible()) {
+                final StageTaskListener anotherStage = targetStage == mMainStage
+                        ? mSideStage : mMainStage;
+                anotherStage.reparentTopTask(wct);
+                anotherStage.evictAllChildren(wct);
+                wct.reorder(mRootTaskInfo.token, true);
+            }
+            setRootForceTranslucent(false, wct);
+            mSyncQueue.queue(wct);
         }
 
-        setSideStagePosition(sideStagePosition, wct);
-        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
-        targetStage.evictAllChildren(evictWct);
-
-        // Apply surface bounds before animation start.
-        SurfaceControl.Transaction startT = mTransactionPool.acquire();
-        if (startT != null) {
-            updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */);
-            startT.apply();
-            mTransactionPool.release(startT);
-        }
-        // reparent the task to an invisible split root will make the activity invisible.  Reorder
-        // the root task to front to make the entering transition from pip to split smooth.
-        wct.reorder(mRootTaskInfo.token, true);
-        wct.reorder(targetStage.mRootTaskInfo.token, true);
-        targetStage.addTask(task, wct);
-
-        if (!evictWct.isEmpty()) {
-            wct.merge(evictWct, true /* transfer */);
-        }
-        mTaskOrganizer.applyTransaction(wct);
+        // Due to drag already pip task entering split by this method so need to reset flag here.
+        mIsDropEntering = false;
         return true;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index d094892..586cab0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -43,6 +43,7 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.splitscreen.StageCoordinator;
 import com.android.wm.shell.sysui.ShellInit;
@@ -55,10 +56,12 @@
  * A handler for dealing with transitions involving multiple other handlers. For example: an
  * activity in split-screen going into PiP.
  */
-public class DefaultMixedHandler implements Transitions.TransitionHandler {
+public class DefaultMixedHandler implements Transitions.TransitionHandler,
+        RecentsTransitionHandler.RecentsMixedHandler {
 
     private final Transitions mPlayer;
     private PipTransitionController mPipHandler;
+    private RecentsTransitionHandler mRecentsHandler;
     private StageCoordinator mSplitHandler;
 
     private static class MixedTransition {
@@ -122,7 +125,8 @@
 
     public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player,
             Optional<SplitScreenController> splitScreenControllerOptional,
-            Optional<PipTouchHandler> pipTouchHandlerOptional) {
+            Optional<PipTouchHandler> pipTouchHandlerOptional,
+            Optional<RecentsTransitionHandler> recentsHandlerOptional) {
         mPlayer = player;
         if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent()
                 && splitScreenControllerOptional.isPresent()) {
@@ -134,6 +138,10 @@
                 if (mSplitHandler != null) {
                     mSplitHandler.setMixedHandler(this);
                 }
+                mRecentsHandler = recentsHandlerOptional.orElse(null);
+                if (mRecentsHandler != null) {
+                    mRecentsHandler.addMixer(this);
+                }
             }, this);
         }
     }
@@ -200,6 +208,29 @@
         return null;
     }
 
+    @Override
+    public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
+        if (mRecentsHandler != null && mSplitHandler.isSplitActive()) {
+            return this;
+        }
+        return null;
+    }
+
+    @Override
+    public void setRecentsTransition(IBinder transition) {
+        if (mSplitHandler.isSplitActive()) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+                    + "Split-Screen is active, so treat it as Mixed.");
+            final MixedTransition mixed = new MixedTransition(
+                    MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
+            mixed.mLeftoversHandler = mRecentsHandler;
+            mActiveTransitions.add(mixed);
+        } else {
+            throw new IllegalStateException("Accepted a recents transition but don't know how to"
+                    + " handle it");
+        }
+    }
+
     private TransitionInfo subCopy(@NonNull TransitionInfo info,
             @WindowManager.TransitionType int newType, boolean withChanges) {
         final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
@@ -272,13 +303,18 @@
                 info.getChanges().remove(i);
             }
         }
+        Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+            --mixed.mInFlightSubAnimations;
+            mixed.joinFinishArgs(wct, wctCB);
+            if (mixed.mInFlightSubAnimations > 0) return;
+            mActiveTransitions.remove(mixed);
+            finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
+        };
         if (pipChange == null) {
             if (mixed.mLeftoversHandler != null) {
+                mixed.mInFlightSubAnimations = 1;
                 if (mixed.mLeftoversHandler.startAnimation(mixed.mTransition,
-                        info, startTransaction, finishTransaction, (wct, wctCB) -> {
-                            mActiveTransitions.remove(mixed);
-                            finishCallback.onTransitionFinished(wct, wctCB);
-                        })) {
+                        info, startTransaction, finishTransaction, finishCB)) {
                     return true;
                 }
             }
@@ -287,13 +323,6 @@
         }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
                         + " animation because remote-animation likely doesn't support it");
-        Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
-            --mixed.mInFlightSubAnimations;
-            mixed.joinFinishArgs(wct, wctCB);
-            if (mixed.mInFlightSubAnimations > 0) return;
-            mActiveTransitions.remove(mixed);
-            finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
-        };
         // Split the transition into 2 parts: the pip part and the rest.
         mixed.mInFlightSubAnimations = 2;
         // make a new startTransaction because pip's startEnterAnimation "consumes" it so
@@ -563,6 +592,8 @@
             mPipHandler.onTransitionConsumed(transition, aborted, finishT);
         } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
             mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+        } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
+            mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
         }
     }
 }
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 039bde9..b8b6d5b 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
@@ -534,7 +534,7 @@
         }
 
         if (info.getType() == TRANSIT_SLEEP) {
-            if (activeIdx > 0) {
+            if (activeIdx > 0 || !mActiveTransitions.isEmpty() || mReadyTransitions.size() > 1) {
                 // Sleep starts a process of forcing all prior transitions to finish immediately
                 finishForSleep(null /* forceFinish */);
                 return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 6b45149..317b9a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -166,7 +166,6 @@
         }
 
         decoration.relayout(taskInfo);
-        setupCaptionColor(taskInfo, decoration);
     }
 
     @Override
@@ -252,7 +251,7 @@
                 }
             } else if (id == R.id.back_button) {
                 mTaskOperations.injectBackKey();
-            } else if (id == R.id.caption_handle) {
+            } else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
                 decoration.createHandleMenu();
             } else if (id == R.id.desktop_button) {
                 mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
@@ -262,7 +261,6 @@
                 mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                 mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
                 decoration.closeHandleMenu();
-                decoration.setButtonVisibility(false);
             } else if (id == R.id.collapse_menu_button) {
                 decoration.closeHandleMenu();
             }
@@ -302,7 +300,11 @@
                     return false;
                 }
                 case MotionEvent.ACTION_MOVE: {
+                    final DesktopModeWindowDecoration decoration =
+                            mWindowDecorByTaskId.get(mTaskId);
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+                    mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
+                            decoration.mTaskSurface, e.getRawY(dragPointerIdx)));
                     mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     mIsDragging = true;
@@ -311,18 +313,10 @@
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
-                    final int statusBarHeight = mDisplayController
-                            .getDisplayLayout(taskInfo.displayId).stableInsets().top;
                     mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
-                    if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
-                        if (DesktopModeStatus.isProto2Enabled()
-                                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-                            // Switch a single task to fullscreen
-                            mDesktopTasksController.ifPresent(
-                                    c -> c.moveToFullscreen(taskInfo));
-                        }
-                    }
+                    mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
+                            e.getRawY(dragPointerIdx)));
                     final boolean wasDragging = mIsDragging;
                     mIsDragging = false;
                     return wasDragging;
@@ -556,13 +550,6 @@
         }
     }
 
-    private void setupCaptionColor(RunningTaskInfo taskInfo,
-            DesktopModeWindowDecoration decoration) {
-        if (taskInfo == null || taskInfo.taskDescription == null) return;
-        final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
-        decoration.setCaptionColor(statusBarColor);
-    }
-
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
         return DesktopModeStatus.isProto2Enabled()
@@ -602,7 +589,6 @@
         windowDecoration.setDragPositioningCallback(taskPositioner);
         windowDecoration.setDragDetector(touchEventListener.mDragDetector);
         windowDecoration.relayout(taskInfo, startT, finishT);
-        setupCaptionColor(taskInfo, windowDecoration);
         incrementEventReceiverTasks(taskInfo.displayId);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 3c0ef96..a6a4d1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -22,15 +22,10 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.PointF;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
 import android.util.Log;
 import android.view.Choreographer;
@@ -48,20 +43,24 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowDecorationViewHolder;
+import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
+import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
 
 /**
  * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
- * {@link DesktopModeWindowDecorViewModel}. The caption bar contains a handle, back button, and
- * close button.
+ * {@link DesktopModeWindowDecorViewModel}.
  *
  * The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
  */
 public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
     private static final String TAG = "DesktopModeWindowDecoration";
+
     private final Handler mHandler;
     private final Choreographer mChoreographer;
     private final SyncTransactionQueue mSyncQueue;
 
+    private DesktopModeWindowDecorationViewHolder mWindowDecorViewHolder;
     private View.OnClickListener mOnCaptionButtonClickListener;
     private View.OnTouchListener mOnCaptionTouchListener;
     private DragPositioningCallback mDragPositioningCallback;
@@ -73,7 +72,6 @@
     private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
             new WindowDecoration.RelayoutResult<>();
 
-    private boolean mDesktopActive;
     private AdditionalWindow mHandleMenu;
     private final int mHandleMenuWidthId = R.dimen.freeform_decor_caption_menu_width;
     private final int mHandleMenuShadowRadiusId = R.dimen.caption_menu_shadow_radius;
@@ -94,7 +92,6 @@
         mHandler = handler;
         mChoreographer = choreographer;
         mSyncQueue = syncQueue;
-        mDesktopActive = DesktopModeStatus.isActive(mContext);
     }
 
     @Override
@@ -152,9 +149,11 @@
         final int outsetRightId = R.dimen.freeform_resize_handle;
         final int outsetBottomId = R.dimen.freeform_resize_handle;
 
+        final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
+                taskInfo.getWindowingMode());
         mRelayoutParams.reset();
         mRelayoutParams.mRunningTaskInfo = taskInfo;
-        mRelayoutParams.mLayoutResId = R.layout.desktop_mode_window_decor;
+        mRelayoutParams.mLayoutResId = windowDecorLayoutId;
         mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
         if (isDragResizeable) {
@@ -172,24 +171,28 @@
             return;
         }
         if (oldRootView != mResult.mRootView) {
-            setupRootView();
-        }
-
-        // If this task is not focused, do not show caption.
-        setCaptionVisibility(mTaskInfo.isFocused);
-
-        if (mTaskInfo.isFocused) {
-            if (DesktopModeStatus.isProto2Enabled()) {
-                updateButtonVisibility();
-            } else if (DesktopModeStatus.isProto1Enabled()) {
-                // Only handle should show if Desktop Mode is inactive.
-                boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
-                if (mDesktopActive != desktopCurrentStatus) {
-                    mDesktopActive = desktopCurrentStatus;
-                    setButtonVisibility(mDesktopActive);
-                }
+            if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_focused_window_decor) {
+                mWindowDecorViewHolder = new DesktopModeFocusedWindowDecorationViewHolder(
+                        mResult.mRootView,
+                        mOnCaptionTouchListener,
+                        mOnCaptionButtonClickListener
+                );
+            } else if (mRelayoutParams.mLayoutResId
+                    == R.layout.desktop_mode_app_controls_window_decor) {
+                mWindowDecorViewHolder = new DesktopModeAppControlsWindowDecorationViewHolder(
+                        mResult.mRootView,
+                        mOnCaptionTouchListener,
+                        mOnCaptionButtonClickListener
+                );
+            } else {
+                throw new IllegalArgumentException("Unexpected layout resource id");
             }
         }
+        mWindowDecorViewHolder.bindData(mTaskInfo);
+
+        if (!mTaskInfo.isFocused) {
+            closeHandleMenu();
+        }
 
         if (!isDragResizeable) {
             closeDragResizeListener();
@@ -219,24 +222,6 @@
                 mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
     }
 
-    /**
-     * Sets up listeners when a new root view is created.
-     */
-    private void setupRootView() {
-        final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
-        caption.setOnTouchListener(mOnCaptionTouchListener);
-        final View handle = caption.findViewById(R.id.caption_handle);
-        handle.setOnTouchListener(mOnCaptionTouchListener);
-        handle.setOnClickListener(mOnCaptionButtonClickListener);
-        if (DesktopModeStatus.isProto1Enabled()) {
-            final View back = caption.findViewById(R.id.back_button);
-            back.setOnClickListener(mOnCaptionButtonClickListener);
-            final View close = caption.findViewById(R.id.close_window);
-            close.setOnClickListener(mOnCaptionButtonClickListener);
-        }
-        updateButtonVisibility();
-    }
-
     private void setupHandleMenu() {
         final View menu = mHandleMenu.mWindowViewHost.getView();
         final View fullscreen = menu.findViewById(R.id.fullscreen_button);
@@ -255,98 +240,26 @@
         collapse.setOnClickListener(mOnCaptionButtonClickListener);
         menu.setOnTouchListener(mOnCaptionTouchListener);
 
-        String packageName = mTaskInfo.baseActivity.getPackageName();
-        PackageManager pm = mContext.getApplicationContext().getPackageManager();
-        // TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
-        try {
-            ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
-                    PackageManager.ApplicationInfoFlags.of(0));
-            final ImageView appIcon = menu.findViewById(R.id.application_icon);
-            appIcon.setImageDrawable(pm.getApplicationIcon(applicationInfo));
-            final TextView appName = menu.findViewById(R.id.application_name);
-            appName.setText(pm.getApplicationLabel(applicationInfo));
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.w(TAG, "Package not found: " + packageName, e);
-        }
-    }
-
-    /**
-     * Sets caption visibility based on task focus.
-     * Note: Only applicable to Desktop Proto 1; Proto 2 only closes handle menu on focus loss
-     * @param visible whether or not the caption should be visible
-     */
-    private void setCaptionVisibility(boolean visible) {
-        if (!visible) closeHandleMenu();
-        if (!DesktopModeStatus.isProto1Enabled()) return;
-        final int v = visible ? View.VISIBLE : View.GONE;
-        final View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
-        captionView.setVisibility(v);
-
-    }
-
-    /**
-     * Sets the visibility of buttons and color of caption based on desktop mode status
-     */
-    void updateButtonVisibility() {
-        if (DesktopModeStatus.isProto2Enabled()) {
-            setButtonVisibility(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM);
-        } else if (DesktopModeStatus.isProto1Enabled()) {
-            mDesktopActive = DesktopModeStatus.isActive(mContext);
-            setButtonVisibility(mDesktopActive);
-        }
-    }
-
-    /**
-     * Show or hide buttons
-     */
-    void setButtonVisibility(boolean visible) {
-        final int visibility = visible && DesktopModeStatus.isProto1Enabled()
-                ? View.VISIBLE : View.GONE;
-        final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
-        final View back = caption.findViewById(R.id.back_button);
-        final View close = caption.findViewById(R.id.close_window);
-        back.setVisibility(visibility);
-        close.setVisibility(visibility);
-        final int buttonTintColorRes =
-                mDesktopActive ? R.color.decor_button_dark_color
-                        : R.color.decor_button_light_color;
-        final ColorStateList buttonTintColor =
-                caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
-        final View handle = caption.findViewById(R.id.caption_handle);
-        final VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
-        handleBackground.setTintList(buttonTintColor);
+        final ImageView appIcon = menu.findViewById(R.id.application_icon);
+        final TextView appName = menu.findViewById(R.id.application_name);
+        loadAppInfo(appName, appIcon);
     }
 
     boolean isHandleMenuActive() {
         return mHandleMenu != null;
     }
 
-    void setCaptionColor(int captionColor) {
-        if (mResult.mRootView == null) {
-            return;
-        }
-
-        final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
-        final GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
-        captionDrawable.setColor(captionColor);
-
-        final int buttonTintColorRes =
-                Color.valueOf(captionColor).luminance() < 0.5
-                        ? R.color.decor_button_light_color
-                        : R.color.decor_button_dark_color;
-        final ColorStateList buttonTintColor =
-                caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
-
-        final View handle = caption.findViewById(R.id.caption_handle);
-        final Drawable handleBackground = handle.getBackground();
-        handleBackground.setTintList(buttonTintColor);
-        if (DesktopModeStatus.isProto1Enabled()) {
-            final View back = caption.findViewById(R.id.back_button);
-            final Drawable backBackground = back.getBackground();
-            backBackground.setTintList(buttonTintColor);
-            final View close = caption.findViewById(R.id.close_window);
-            final Drawable closeBackground = close.getBackground();
-            closeBackground.setTintList(buttonTintColor);
+    private void loadAppInfo(TextView appNameTextView, ImageView appIconImageView) {
+        String packageName = mTaskInfo.realActivity.getPackageName();
+        PackageManager pm = mContext.getApplicationContext().getPackageManager();
+        try {
+            // TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
+            ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
+                    PackageManager.ApplicationInfoFlags.of(0));
+            appNameTextView.setText(pm.getApplicationLabel(applicationInfo));
+            appIconImageView.setImageDrawable(pm.getApplicationIcon(applicationInfo));
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "Package not found: " + packageName, e);
         }
     }
 
@@ -371,11 +284,20 @@
         final int shadowRadius = loadDimensionPixelSize(resources, mHandleMenuShadowRadiusId);
         final int cornerRadius = loadDimensionPixelSize(resources, mHandleMenuCornerRadiusId);
 
-        final int x = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
-                - mResult.mDecorContainerOffsetX;
-        final int y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
+        final int x, y;
+        if (mRelayoutParams.mLayoutResId
+                == R.layout.desktop_mode_app_controls_window_decor) {
+            // Align the handle menu to the left of the caption.
+            x = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX;
+            y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
+        } else {
+            // Position the handle menu at the center of the caption.
+            x = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
+                    - mResult.mDecorContainerOffsetX;
+            y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
+        }
         mHandleMenuPosition.set(x, y);
-        String namePrefix = "Caption Menu";
+        final String namePrefix = "Caption Menu";
         mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x, y,
                 menuWidth, menuHeight, shadowRadius, cornerRadius);
         mSyncQueue.runInSync(transaction -> {
@@ -503,6 +425,15 @@
         super.close();
     }
 
+    private int getDesktopModeWindowDecorLayoutId(int windowingMode) {
+        if (DesktopModeStatus.isProto1Enabled()) {
+            return R.layout.desktop_mode_app_controls_window_decor;
+        }
+        return windowingMode == WINDOWING_MODE_FREEFORM
+                ? R.layout.desktop_mode_app_controls_window_decor
+                : R.layout.desktop_mode_focused_window_decor;
+    }
+
     static class Factory {
 
         DesktopModeWindowDecoration create(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ddd3b44..31e93ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -84,6 +84,7 @@
             };
 
     RunningTaskInfo mTaskInfo;
+    int mLayoutResId;
     final SurfaceControl mTaskSurface;
 
     Display mDisplay;
@@ -162,6 +163,8 @@
         if (params.mRunningTaskInfo != null) {
             mTaskInfo = params.mRunningTaskInfo;
         }
+        final int oldLayoutResId = mLayoutResId;
+        mLayoutResId = params.mLayoutResId;
 
         if (!mTaskInfo.isVisible) {
             releaseViews();
@@ -178,7 +181,8 @@
         final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
         if (oldTaskConfig.densityDpi != taskConfig.densityDpi
                 || mDisplay == null
-                || mDisplay.getDisplayId() != mTaskInfo.displayId) {
+                || mDisplay.getDisplayId() != mTaskInfo.displayId
+                || oldLayoutResId != mLayoutResId) {
             releaseViews();
 
             if (!obtainDisplayOrRegisterListener()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
new file mode 100644
index 0000000..95b5051
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -0,0 +1,94 @@
+package com.android.wm.shell.windowdecor.viewholder
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.pm.PackageManager
+import android.content.res.ColorStateList
+import android.graphics.drawable.GradientDrawable
+import android.util.Log
+import android.view.View
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.wm.shell.R
+
+/**
+ * A desktop mode window decoration used when the window is floating (i.e. freeform). It hosts
+ * finer controls such as a close window button and an "app info" section to pull up additional
+ * controls.
+ */
+internal class DesktopModeAppControlsWindowDecorationViewHolder(
+        rootView: View,
+        onCaptionTouchListener: View.OnTouchListener,
+        onCaptionButtonClickListener: View.OnClickListener
+) : DesktopModeWindowDecorationViewHolder(rootView) {
+
+    private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
+    private val captionHandle: View = rootView.findViewById(R.id.caption_handle)
+    private val openMenuButton: View = rootView.findViewById(R.id.open_menu_button)
+    private val closeWindowButton: ImageButton = rootView.findViewById(R.id.close_window)
+    private val expandMenuButton: ImageButton = rootView.findViewById(R.id.expand_menu_button)
+    private val appNameTextView: TextView = rootView.findViewById(R.id.application_name)
+    private val appIconImageView: ImageView = rootView.findViewById(R.id.application_icon)
+
+    init {
+        captionView.setOnTouchListener(onCaptionTouchListener)
+        captionHandle.setOnTouchListener(onCaptionTouchListener)
+        openMenuButton.setOnClickListener(onCaptionButtonClickListener)
+        closeWindowButton.setOnClickListener(onCaptionButtonClickListener)
+    }
+
+    override fun bindData(taskInfo: RunningTaskInfo) {
+        bindAppInfo(taskInfo)
+
+        val captionDrawable = captionView.background as GradientDrawable
+        captionDrawable.setColor(taskInfo.taskDescription.statusBarColor)
+
+        closeWindowButton.imageTintList = ColorStateList.valueOf(
+                getCaptionCloseButtonColor(taskInfo))
+        expandMenuButton.imageTintList = ColorStateList.valueOf(
+                getCaptionExpandButtonColor(taskInfo))
+        appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo))
+    }
+
+    private fun bindAppInfo(taskInfo: RunningTaskInfo) {
+        val packageName: String = taskInfo.realActivity.packageName
+        val pm: PackageManager = context.applicationContext.packageManager
+        try {
+            // TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
+            val applicationInfo = pm.getApplicationInfo(packageName,
+                    PackageManager.ApplicationInfoFlags.of(0))
+            appNameTextView.text = pm.getApplicationLabel(applicationInfo)
+            appIconImageView.setImageDrawable(pm.getApplicationIcon(applicationInfo))
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.w(TAG, "Package not found: $packageName", e)
+        }
+    }
+
+    private fun getCaptionAppNameTextColor(taskInfo: RunningTaskInfo): Int {
+        return if (shouldUseLightCaptionColors(taskInfo)) {
+            context.getColor(R.color.desktop_mode_caption_app_name_light)
+        } else {
+            context.getColor(R.color.desktop_mode_caption_app_name_dark)
+        }
+    }
+
+    private fun getCaptionCloseButtonColor(taskInfo: RunningTaskInfo): Int {
+        return if (shouldUseLightCaptionColors(taskInfo)) {
+            context.getColor(R.color.desktop_mode_caption_close_button_light)
+        } else {
+            context.getColor(R.color.desktop_mode_caption_close_button_dark)
+        }
+    }
+
+    private fun getCaptionExpandButtonColor(taskInfo: RunningTaskInfo): Int {
+        return if (shouldUseLightCaptionColors(taskInfo)) {
+            context.getColor(R.color.desktop_mode_caption_expand_button_light)
+        } else {
+            context.getColor(R.color.desktop_mode_caption_expand_button_dark)
+        }
+    }
+
+    companion object {
+        private const val TAG = "DesktopModeAppControlsWindowDecorationViewHolder"
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
new file mode 100644
index 0000000..47a12a0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -0,0 +1,44 @@
+package com.android.wm.shell.windowdecor.viewholder
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.res.ColorStateList
+import android.graphics.drawable.GradientDrawable
+import android.view.View
+import android.widget.ImageButton
+import com.android.wm.shell.R
+
+/**
+ * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen). It
+ * hosts a simple handle bar from which to initiate a drag motion to enter desktop mode.
+ */
+internal class DesktopModeFocusedWindowDecorationViewHolder(
+        rootView: View,
+        onCaptionTouchListener: View.OnTouchListener,
+        onCaptionButtonClickListener: View.OnClickListener
+) : DesktopModeWindowDecorationViewHolder(rootView) {
+
+    private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
+    private val captionHandle: ImageButton = rootView.findViewById(R.id.caption_handle)
+
+    init {
+        captionView.setOnTouchListener(onCaptionTouchListener)
+        captionHandle.setOnTouchListener(onCaptionTouchListener)
+        captionHandle.setOnClickListener(onCaptionButtonClickListener)
+    }
+
+    override fun bindData(taskInfo: RunningTaskInfo) {
+        val captionColor = taskInfo.taskDescription.statusBarColor
+        val captionDrawable = captionView.background as GradientDrawable
+        captionDrawable.setColor(captionColor)
+
+        captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
+    }
+
+    private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
+        return if (shouldUseLightCaptionColors(taskInfo)) {
+            context.getColor(R.color.desktop_mode_caption_handle_bar_light)
+        } else {
+            context.getColor(R.color.desktop_mode_caption_handle_bar_dark)
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
new file mode 100644
index 0000000..514ea52
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -0,0 +1,28 @@
+package com.android.wm.shell.windowdecor.viewholder
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.graphics.Color
+import android.view.View
+
+/**
+ * Encapsulates the root [View] of a window decoration and its children to facilitate looking up
+ * children (via findViewById) and updating to the latest data from [RunningTaskInfo].
+ */
+internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
+    val context: Context = rootView.context
+
+    /**
+     * A signal to the view holder that new data is available and that the views should be updated
+     * to reflect it.
+     */
+    abstract fun bindData(taskInfo: RunningTaskInfo)
+
+    /**
+     * Whether the caption items should use the 'light' color variant so that there's good contrast
+     * with the caption background color.
+     */
+    protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
+        return Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index ffdb87f..5b06c9c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -79,7 +79,8 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false,
+            appExistAtStart = false)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 792e2b0..c840183 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -82,7 +82,8 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, sendNotificationApp,
+            fromOtherApp = false)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 95e78a8..5cad50d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -37,11 +37,14 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.ExtendedMockito.never
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
@@ -73,7 +76,10 @@
 
     @Mock lateinit var testExecutor: ShellExecutor
     @Mock lateinit var shellController: ShellController
+    @Mock lateinit var displayController: DisplayController
     @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
+    @Mock lateinit var syncQueue: SyncTransactionQueue
+    @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
     @Mock lateinit var transitions: Transitions
 
     lateinit var mockitoSession: StaticMockitoSession
@@ -105,7 +111,10 @@
             context,
             shellInit,
             shellController,
+            displayController,
             shellTaskOrganizer,
+            syncQueue,
+            rootTaskDisplayAreaOrganizer,
             transitions,
             desktopModeTaskRepository,
             TestShellExecutor()
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 2e2e49e..eda6fdc 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
@@ -145,39 +145,48 @@
     }
 
     @Test
-    public void testMoveToStage() {
+    public void testMoveToStage_splitActiveBackground() {
+        when(mStageCoordinator.isSplitActive()).thenReturn(true);
+
         final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        mStageCoordinator.moveToStage(task, STAGE_TYPE_MAIN, SPLIT_POSITION_BOTTOM_OR_RIGHT,
-                new WindowContainerTransaction());
-        verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class));
-        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
-
-        mStageCoordinator.moveToStage(task, STAGE_TYPE_SIDE, SPLIT_POSITION_BOTTOM_OR_RIGHT,
-                new WindowContainerTransaction());
-        verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
+        mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+        verify(mSideStage).addTask(eq(task), eq(wct));
         assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
+        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
     }
 
     @Test
-    public void testMoveToUndefinedStage() {
-        final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
-
-        // Verify move to undefined stage while split screen not activated moves task to side stage.
-        when(mStageCoordinator.isSplitScreenVisible()).thenReturn(false);
-        mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
-        mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT,
-                new WindowContainerTransaction());
-        verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
-        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
-
-        // Verify move to undefined stage after split screen activated moves task based on position.
+    public void testMoveToStage_splitActiveForeground() {
+        when(mStageCoordinator.isSplitActive()).thenReturn(true);
         when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
-        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
-        mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT,
-                new WindowContainerTransaction());
-        verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class));
-        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
+        // Assume current side stage is top or left.
+        mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
+
+        final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+        mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+        verify(mMainStage).addTask(eq(task), eq(wct));
+        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
+        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
+
+        mStageCoordinator.moveToStage(task, SPLIT_POSITION_TOP_OR_LEFT, wct);
+        verify(mSideStage).addTask(eq(task), eq(wct));
+        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
+        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
+    }
+
+    @Test
+    public void testMoveToStage_splitInctive() {
+        final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+        mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+        verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
+                eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
     }
 
     @Test
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index 41ced8c..139cdde 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -54,6 +54,7 @@
     // collection
     bool purgeScratchOnly = true;
     // EXPERIMENTAL: Whether or not to trigger releasing GPU context when all contexts are stopped
+    // WARNING: Enabling this option can lead to instability, see b/266626090
     bool releaseContextOnStoppedOnly = false;
 };
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 8ab7159..8efd180 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6251,6 +6251,7 @@
      * Volume behavior for an audio device where no software attenuation is applied, and
      *     the volume is kept synchronized between the host and the device itself through a
      *     device-specific protocol such as BT AVRCP.
+     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
      */
     @SystemApi
     public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3;
@@ -6261,6 +6262,7 @@
      *     device-specific protocol (such as for hearing aids), based on the audio mode (e.g.
      *     normal vs in phone call).
      * @see #setMode(int)
+     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
      */
     @SystemApi
     public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4;
@@ -6270,11 +6272,6 @@
      * A variant of {@link #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE} where the host cannot reliably set
      * the volume percentage of the audio device. Specifically, {@link #setStreamVolume} will have
      * no effect, or an unreliable effect.
-     *
-     * {@link #DEVICE_VOLUME_BEHAVIOR_FULL} will be returned instead by
-     * {@link #getDeviceVolumeBehavior} for target SDK versions before U.
-     *
-     * @see #RETURN_DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY
      */
     @SystemApi
     public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5;
@@ -6316,27 +6313,18 @@
     public @interface AbsoluteDeviceVolumeBehavior {}
 
     /**
-     * Volume behaviors that can be set with {@link #setDeviceVolumeBehavior}.
      * @hide
-     */
-    @IntDef({
-        DEVICE_VOLUME_BEHAVIOR_VARIABLE,
-        DEVICE_VOLUME_BEHAVIOR_FULL,
-        DEVICE_VOLUME_BEHAVIOR_FIXED,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface SettableDeviceVolumeBehavior {}
-
-    /**
-     * @hide
-     * Throws IAE on a non-settable volume behavior value
+     * Throws IAE on an invalid volume behavior value
      * @param volumeBehavior behavior value to check
      */
-    public static void enforceSettableVolumeBehavior(int volumeBehavior) {
+    public static void enforceValidVolumeBehavior(int volumeBehavior) {
         switch (volumeBehavior) {
             case DEVICE_VOLUME_BEHAVIOR_VARIABLE:
             case DEVICE_VOLUME_BEHAVIOR_FULL:
             case DEVICE_VOLUME_BEHAVIOR_FIXED:
+            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
+            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
+            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY:
                 return;
             default:
                 throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior);
@@ -6346,8 +6334,11 @@
     /**
      * @hide
      * Sets the volume behavior for an audio output device.
-     *
-     * @see SettableDeviceVolumeBehavior
+     * @see #DEVICE_VOLUME_BEHAVIOR_VARIABLE
+     * @see #DEVICE_VOLUME_BEHAVIOR_FULL
+     * @see #DEVICE_VOLUME_BEHAVIOR_FIXED
+     * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE
+     * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE
      * @param device the device to be affected
      * @param deviceVolumeBehavior one of the device behaviors
      */
@@ -6357,10 +6348,10 @@
             Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
     })
     public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
-            @SettableDeviceVolumeBehavior int deviceVolumeBehavior) {
+            @DeviceVolumeBehavior int deviceVolumeBehavior) {
         // verify arguments (validity of device type is enforced in server)
         Objects.requireNonNull(device);
-        enforceSettableVolumeBehavior(deviceVolumeBehavior);
+        enforceValidVolumeBehavior(deviceVolumeBehavior);
         // communicate with service
         final IAudioService service = getService();
         try {
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index 9b238e1..6744359 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -49,6 +49,7 @@
 import libcore.io.IoUtils;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Comparator;
@@ -255,17 +256,19 @@
 
         // get orientation
         if (MediaFile.isExifMimeType(mimeType)) {
-            exif = new ExifInterface(file);
-            switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) {
-                case ExifInterface.ORIENTATION_ROTATE_90:
-                    orientation = 90;
-                    break;
-                case ExifInterface.ORIENTATION_ROTATE_180:
-                    orientation = 180;
-                    break;
-                case ExifInterface.ORIENTATION_ROTATE_270:
-                    orientation = 270;
-                    break;
+            try (FileInputStream is = new FileInputStream(file)) {
+                exif = new ExifInterface(is.getFD());
+                switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) {
+                    case ExifInterface.ORIENTATION_ROTATE_90:
+                        orientation = 90;
+                        break;
+                    case ExifInterface.ORIENTATION_ROTATE_180:
+                        orientation = 180;
+                        break;
+                    case ExifInterface.ORIENTATION_ROTATE_270:
+                        orientation = 270;
+                        break;
+                }
             }
         }
 
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_device_other.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_device_other.xml
new file mode 100644
index 0000000..aafe466
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_device_other.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_200">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M7,20H4Q3.175,20 2.588,19.413Q2,18.825 2,18V6Q2,5.175 2.588,4.588Q3.175,4 4,4H20V6H4Q4,6 4,6Q4,6 4,6V18Q4,18 4,18Q4,18 4,18H7ZM9,20V18.2Q8.55,17.775 8.275,17.225Q8,16.675 8,16Q8,15.325 8.275,14.775Q8.55,14.225 9,13.8V12H13V13.8Q13.45,14.225 13.725,14.775Q14,15.325 14,16Q14,16.675 13.725,17.225Q13.45,17.775 13,18.2V20ZM11,17.5Q11.65,17.5 12.075,17.075Q12.5,16.65 12.5,16Q12.5,15.35 12.075,14.925Q11.65,14.5 11,14.5Q10.35,14.5 9.925,14.925Q9.5,15.35 9.5,16Q9.5,16.65 9.925,17.075Q10.35,17.5 11,17.5ZM21,20H16Q15.575,20 15.288,19.712Q15,19.425 15,19V10Q15,9.575 15.288,9.287Q15.575,9 16,9H21Q21.425,9 21.712,9.287Q22,9.575 22,10V19Q22,19.425 21.712,19.712Q21.425,20 21,20ZM17,18H20V11H17Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_glasses.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_glasses.xml
index 97d201d..190e0a8 100644
--- a/packages/CompanionDeviceManager/res/drawable-night/ic_glasses.xml
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_glasses.xml
@@ -20,7 +20,7 @@
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
-        android:tint="@android:color/system_neutral1_200">
+        android:tint="@android:color/system_accent1_200">
     <path
         android:fillColor="@android:color/white"
         android:pathData="M6.85,15Q7.625,15 8.238,14.55Q8.85,14.1 9.1,13.375L9.475,12.225Q9.875,11.025 9.275,10.012Q8.675,9 7.55,9H4.025L4.5,12.925Q4.625,13.8 5.287,14.4Q5.95,15 6.85,15ZM17.15,15Q18.05,15 18.712,14.4Q19.375,13.8 19.5,12.925L19.975,9H16.475Q15.35,9 14.75,10.025Q14.15,11.05 14.55,12.25L14.9,13.375Q15.15,14.1 15.762,14.55Q16.375,15 17.15,15ZM6.85,17Q5.2,17 3.963,15.912Q2.725,14.825 2.525,13.175L2,9H1V7H7.55Q8.65,7 9.562,7.537Q10.475,8.075 11,9H13.025Q13.55,8.075 14.463,7.537Q15.375,7 16.475,7H23V9H22L21.475,13.175Q21.275,14.825 20.038,15.912Q18.8,17 17.15,17Q15.725,17 14.588,16.188Q13.45,15.375 13,14.025L12.625,12.9Q12.575,12.725 12.525,12.537Q12.475,12.35 12.425,12H11.575Q11.525,12.3 11.475,12.487Q11.425,12.675 11.375,12.85L11,14Q10.55,15.35 9.413,16.175Q8.275,17 6.85,17Z"/>
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml
index 1611861..78120a3d 100644
--- a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml
@@ -19,7 +19,7 @@
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
+        android:tint="@android:color/system_accent1_200">
     <path android:fillColor="@android:color/system_accent1_200"
           android:pathData="M12,16.4 L7.6,12 12,7.6 16.4,12ZM13.4,21.375Q13.125,21.65 12.75,21.8Q12.375,21.95 12,21.95Q11.625,21.95 11.25,21.8Q10.875,21.65 10.6,21.375L2.625,13.4Q2.35,13.125 2.2,12.75Q2.05,12.375 2.05,12Q2.05,11.625 2.2,11.25Q2.35,10.875 2.625,10.6L10.575,2.65Q10.875,2.35 11.238,2.2Q11.6,2.05 12,2.05Q12.4,2.05 12.762,2.2Q13.125,2.35 13.425,2.65L21.375,10.6Q21.65,10.875 21.8,11.25Q21.95,11.625 21.95,12Q21.95,12.375 21.8,12.75Q21.65,13.125 21.375,13.4ZM12,19.2 L19.2,12Q19.2,12 19.2,12Q19.2,12 19.2,12L12,4.8Q12,4.8 12,4.8Q12,4.8 12,4.8L4.8,12Q4.8,12 4.8,12Q4.8,12 4.8,12L12,19.2Q12,19.2 12,19.2Q12,19.2 12,19.2Z"/>
 </vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_watch.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_watch.xml
new file mode 100644
index 0000000..f1fda17
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_watch.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_200">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M9,22 L7.65,17.45Q6.45,16.5 5.725,15.075Q5,13.65 5,12Q5,10.35 5.725,8.925Q6.45,7.5 7.65,6.55L9,2H15L16.35,6.55Q17.55,7.5 18.275,8.925Q19,10.35 19,12Q19,13.65 18.275,15.075Q17.55,16.5 16.35,17.45L15,22ZM12,17Q14.075,17 15.538,15.537Q17,14.075 17,12Q17,9.925 15.538,8.462Q14.075,7 12,7Q9.925,7 8.463,8.462Q7,9.925 7,12Q7,14.075 8.463,15.537Q9.925,17 12,17ZM10.1,5.25Q11.075,4.975 12,4.975Q12.925,4.975 13.9,5.25L13.5,4H10.5ZM10.5,20H13.5L13.9,18.75Q12.925,19.025 12,19.025Q11.075,19.025 10.1,18.75ZM10.1,4H10.5H13.5H13.9Q12.925,4 12,4Q11.075,4 10.1,4ZM10.5,20H10.1Q11.075,20 12,20Q12.925,20 13.9,20H13.5Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml b/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml
index 15f6987..dc12d12 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml
@@ -19,7 +19,7 @@
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
+        android:tint="@android:color/system_accent1_600">
     <path android:fillColor="@android:color/white"
           android:pathData="M7,20H4Q3.175,20 2.588,19.413Q2,18.825 2,18V6Q2,5.175 2.588,4.588Q3.175,4 4,4H20V6H4Q4,6 4,6Q4,6 4,6V18Q4,18 4,18Q4,18 4,18H7ZM9,20V18.2Q8.55,17.775 8.275,17.225Q8,16.675 8,16Q8,15.325 8.275,14.775Q8.55,14.225 9,13.8V12H13V13.8Q13.45,14.225 13.725,14.775Q14,15.325 14,16Q14,16.675 13.725,17.225Q13.45,17.775 13,18.2V20ZM11,17.5Q11.65,17.5 12.075,17.075Q12.5,16.65 12.5,16Q12.5,15.35 12.075,14.925Q11.65,14.5 11,14.5Q10.35,14.5 9.925,14.925Q9.5,15.35 9.5,16Q9.5,16.65 9.925,17.075Q10.35,17.5 11,17.5ZM21,20H16Q15.575,20 15.288,19.712Q15,19.425 15,19V10Q15,9.575 15.288,9.287Q15.575,9 16,9H21Q21.425,9 21.712,9.287Q22,9.575 22,10V19Q22,19.425 21.712,19.712Q21.425,20 21,20ZM17,18H20V11H17Z"/>
 </vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml b/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml
index 9065520..0baf7ef 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml
@@ -20,7 +20,7 @@
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
+        android:tint="@android:color/system_accent1_600">
     <path
         android:fillColor="@android:color/white"
         android:pathData="M6.85,15Q7.625,15 8.238,14.55Q8.85,14.1 9.1,13.375L9.475,12.225Q9.875,11.025 9.275,10.012Q8.675,9 7.55,9H4.025L4.5,12.925Q4.625,13.8 5.287,14.4Q5.95,15 6.85,15ZM17.15,15Q18.05,15 18.712,14.4Q19.375,13.8 19.5,12.925L19.975,9H16.475Q15.35,9 14.75,10.025Q14.15,11.05 14.55,12.25L14.9,13.375Q15.15,14.1 15.762,14.55Q16.375,15 17.15,15ZM6.85,17Q5.2,17 3.963,15.912Q2.725,14.825 2.525,13.175L2,9H1V7H7.55Q8.65,7 9.562,7.537Q10.475,8.075 11,9H13.025Q13.55,8.075 14.463,7.537Q15.375,7 16.475,7H23V9H22L21.475,13.175Q21.275,14.825 20.038,15.912Q18.8,17 17.15,17Q15.725,17 14.588,16.188Q13.45,15.375 13,14.025L12.625,12.9Q12.575,12.725 12.525,12.537Q12.475,12.35 12.425,12H11.575Q11.525,12.3 11.475,12.487Q11.425,12.675 11.375,12.85L11,14Q10.55,15.35 9.413,16.175Q8.275,17 6.85,17Z"/>
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_watch.xml b/packages/CompanionDeviceManager/res/drawable/ic_watch.xml
index d7a28d9..0f6b21f 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_watch.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_watch.xml
@@ -20,7 +20,7 @@
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
+        android:tint="@android:color/system_accent1_600">
     <path android:fillColor="@android:color/white"
           android:pathData="M9,22 L7.65,17.45Q6.45,16.5 5.725,15.075Q5,13.65 5,12Q5,10.35 5.725,8.925Q6.45,7.5 7.65,6.55L9,2H15L16.35,6.55Q17.55,7.5 18.275,8.925Q19,10.35 19,12Q19,13.65 18.275,15.075Q17.55,16.5 16.35,17.45L15,22ZM12,17Q14.075,17 15.538,15.537Q17,14.075 17,12Q17,9.925 15.538,8.462Q14.075,7 12,7Q9.925,7 8.463,8.462Q7,9.925 7,12Q7,14.075 8.463,15.537Q9.925,17 12,17ZM10.1,5.25Q11.075,4.975 12,4.975Q12.925,4.975 13.9,5.25L13.5,4H10.5ZM10.5,20H13.5L13.9,18.75Q12.925,19.025 12,19.025Q11.075,19.025 10.1,18.75ZM10.1,4H10.5H13.5H13.9Q12.925,4 12,4Q11.075,4 10.1,4ZM10.5,20H10.1Q11.075,20 12,20Q12.925,20 13.9,20H13.5Z"/>
 </vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index d1d2c70..d470d4c 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -38,8 +38,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="32dp"
                 android:gravity="center"
-                android:layout_marginTop="18dp"
-                android:tint="@android:color/system_accent1_600"/>
+                android:layout_marginTop="18dp" />
 
             <LinearLayout style="@style/Description">
                 <TextView
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index ac5294a..79f04b9 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -29,7 +29,6 @@
         android:layout_width="24dp"
         android:layout_height="24dp"
         android:layout_marginStart="24dp"
-        android:tint="@android:color/system_accent1_600"
         android:importantForAccessibility="no"
         android:contentDescription="@null"/>
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 99b776c..71ae578 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -39,6 +39,7 @@
 import static com.android.companiondevicemanager.Utils.getApplicationLabel;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 import static com.android.companiondevicemanager.Utils.getIcon;
+import static com.android.companiondevicemanager.Utils.getImageColor;
 import static com.android.companiondevicemanager.Utils.getVendorHeaderIcon;
 import static com.android.companiondevicemanager.Utils.getVendorHeaderName;
 import static com.android.companiondevicemanager.Utils.hasVendorIcon;
@@ -56,7 +57,6 @@
 import android.companion.IAssociationRequestCallback;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.res.Configuration;
 import android.graphics.BlendMode;
 import android.graphics.BlendModeColorFilter;
 import android.graphics.Color;
@@ -463,8 +463,6 @@
         final Drawable vendorIcon;
         final CharSequence vendorName;
         final Spanned title;
-        int nightModeFlags = getResources().getConfiguration().uiMode
-                & Configuration.UI_MODE_NIGHT_MASK;
 
         if (!SUPPORTED_SELF_MANAGED_PROFILES.contains(deviceProfile)) {
             throw new RuntimeException("Unsupported profile " + deviceProfile);
@@ -477,8 +475,7 @@
             vendorName = getVendorHeaderName(this, packageName, userId);
             mVendorHeaderImage.setImageDrawable(vendorIcon);
             if (hasVendorIcon(this, packageName, userId)) {
-                int color = nightModeFlags == Configuration.UI_MODE_NIGHT_YES
-                        ? android.R.color.system_accent1_200 : android.R.color.system_accent1_600;
+                int color = getImageColor(this);
                 mVendorHeaderImage.setColorFilter(getResources().getColor(color, /* Theme= */null));
             }
         } catch (PackageManager.NameNotFoundException e) {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
index 328c67e..d8348d1 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
@@ -17,6 +17,7 @@
 package com.android.companiondevicemanager;
 
 import static com.android.companiondevicemanager.Utils.getIcon;
+import static com.android.companiondevicemanager.Utils.getImageColor;
 
 import android.content.Context;
 import android.view.LayoutInflater;
@@ -65,6 +66,10 @@
             viewHolder.mImageView.setImageDrawable(
                     getIcon(mContext, android.R.drawable.stat_sys_data_bluetooth));
         }
+
+        viewHolder.mImageView.setColorFilter(
+                mContext.getResources().getColor(getImageColor(mContext), /* Theme= */null));
+
         return viewHolder;
     }
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
index fceca91..8c14f80 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
@@ -22,6 +22,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
@@ -120,6 +121,20 @@
         }
     }
 
+    private static boolean isDarkTheme(@NonNull Context context) {
+        int nightModeFlags = context.getResources().getConfiguration().uiMode
+                & Configuration.UI_MODE_NIGHT_MASK;
+        return nightModeFlags == Configuration.UI_MODE_NIGHT_YES;
+    }
+
+    // Get image color for the corresponding theme.
+    static int getImageColor(@NonNull Context context) {
+        if (isDarkTheme(context)) {
+            return android.R.color.system_accent1_200;
+        } else {
+            return android.R.color.system_accent1_600;
+        }
+    }
     /**
      * Getting ApplicationInfo from meta-data.
      */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index b32fe3f..28f9453 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -467,19 +467,19 @@
             GetCredentialRequest.Builder(
                 Bundle()
             ).addCredentialOption(
-                CredentialOption(
+                CredentialOption.Builder(
                     passwordOption.type,
                     passwordOption.requestData,
                     passwordOption.candidateQueryData,
-                    passwordOption.isSystemProviderRequired
-                )
+                ).setIsSystemProviderRequired(passwordOption.isSystemProviderRequired)
+                .build()
             ).addCredentialOption(
-                CredentialOption(
+                CredentialOption.Builder(
                     passkeyOption.type,
                     passkeyOption.requestData,
                     passkeyOption.candidateQueryData,
-                    passkeyOption.isSystemProviderRequired
-                )
+                ).setIsSystemProviderRequired(passkeyOption.isSystemProviderRequired)
+                .build()
             ).build(),
             "com.google.android.youtube"
         )
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 6669e35..a2118fa 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -37,9 +37,8 @@
     <string name="install_confirm_question">Do you want to install this app?</string>
     <!-- Message for updating an existing app [CHAR LIMIT=NONE] -->
     <string name="install_confirm_question_update">Do you want to update this app?</string>
-    <!-- TODO(b/244413073) Revise the description after getting UX input and UXR on this. -->
-    <!-- Message for updating an existing app with update owner reminder [DO NOT TRANSLATE][CHAR LIMIT=NONE] -->
-    <string name="install_confirm_question_update_owner_reminder">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nDo you want to install this update from <xliff:g id="new_update_owner">%2$s</xliff:g>?</string>
+    <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
+    <string name="install_confirm_question_update_owner_reminder">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.</string>
     <!-- [CHAR LIMIT=100] -->
     <string name="install_failed">App not installed.</string>
     <!-- Reason displayed when installation fails because the package was blocked
@@ -82,6 +81,8 @@
 
     <!-- [CHAR LIMIT=15] -->
     <string name="ok">OK</string>
+    <!-- [CHAR LIMIT=30] -->
+    <string name="update_anyway">Update anyway</string>
     <!-- [CHAR LIMIT=15] -->
     <string name="manage_applications">Manage apps</string>
     <!-- [CHAR LIMIT=30] -->
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index d41cfbc2..c81e75b 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -148,10 +148,11 @@
                     && mPendingUserActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP) {
                 viewToEnable.setText(
                         getString(R.string.install_confirm_question_update_owner_reminder,
-                                existingUpdateOwnerLabel, requestedUpdateOwnerLabel));
+                                requestedUpdateOwnerLabel, existingUpdateOwnerLabel));
+                mOk.setText(R.string.update_anyway);
+            } else {
+                mOk.setText(R.string.update);
             }
-
-            mOk.setText(R.string.update);
         } else {
             // This is a new application with no permissions.
             viewToEnable = requireViewById(R.id.install_confirm_question);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 071ab27..a9d15f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -97,7 +97,7 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SelectionBehavior.SELECTION_BEHAVIOR_NONE,
-            SELECTION_BEHAVIOR_TRANSFER,
+            SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER,
             SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP
     })
     public @interface SelectionBehavior {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index a8eeec3..80030f7 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -50,10 +50,6 @@
     @GuardedBy("mLock")
     private final ArrayMap<Integer, ArrayMap<String, Integer>> mKeyToIndexMapMap = new ArrayMap<>();
 
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    // Maximum number of backing stores allowed
-    static final int NUM_MAX_BACKING_STORE = 8;
-
     @GuardedBy("mLock")
     private int mNumBackingStore = 0;
 
@@ -65,8 +61,24 @@
     // The generation number is only increased when a new non-predefined setting is inserted
     private static final String DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS = "";
 
-    public GenerationRegistry(Object lock) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    // Minimum number of backing stores; supports 3 users
+    static final int MIN_NUM_BACKING_STORE = 8;
+    // Maximum number of backing stores; supports 18 users
+    static final int MAX_NUM_BACKING_STORE = 38;
+
+    private final int mMaxNumBackingStore;
+
+    GenerationRegistry(Object lock, int maxNumUsers) {
         mLock = lock;
+        // Add some buffer to maxNumUsers to accommodate corner cases when the actual number of
+        // users in the system exceeds the limit
+        maxNumUsers = maxNumUsers + 2;
+        // Number of backing stores needed for N users is (N + N + 1 + 1) = N * 2 + 2
+        // N Secure backing stores and N System backing stores, 1 Config and 1 Global for all users
+        // However, we always make sure that at least 3 users and at most 18 users are supported.
+        mMaxNumBackingStore = Math.min(Math.max(maxNumUsers * 2 + 2, MIN_NUM_BACKING_STORE),
+                MAX_NUM_BACKING_STORE);
     }
 
     /**
@@ -195,7 +207,7 @@
         }
         if (backingStore == null) {
             try {
-                if (mNumBackingStore >= NUM_MAX_BACKING_STORE) {
+                if (mNumBackingStore >= mMaxNumBackingStore) {
                     if (DEBUG) {
                         Slog.e(LOG_TAG, "Error creating backing store - at capacity");
                     }
@@ -275,4 +287,9 @@
         }
         return -1;
     }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    int getMaxNumBackingStores() {
+        return mMaxNumBackingStore;
+    }
 }
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 7e89bfc..721b3c4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2831,7 +2831,7 @@
 
         public SettingsRegistry() {
             mHandler = new MyHandler(getContext().getMainLooper());
-            mGenerationRegistry = new GenerationRegistry(mLock);
+            mGenerationRegistry = new GenerationRegistry(mLock, UserManager.getMaxSupportedUsers());
             mBackupManager = new BackupManager(getContext());
         }
 
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
index 586d6f7..12865f4 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
@@ -36,7 +36,7 @@
 public class GenerationRegistryTest {
     @Test
     public void testGenerationsWithRegularSetting() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2);
         final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
         final String testSecureSetting = "test_secure_setting";
         Bundle b = new Bundle();
@@ -93,7 +93,7 @@
 
     @Test
     public void testGenerationsWithConfigSetting() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1);
         final String prefix = "test_namespace/";
         final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
 
@@ -110,10 +110,10 @@
 
     @Test
     public void testMaxNumBackingStores() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2);
         final String testSecureSetting = "test_secure_setting";
         Bundle b = new Bundle();
-        for (int i = 0; i < GenerationRegistry.NUM_MAX_BACKING_STORE; i++) {
+        for (int i = 0; i < generationRegistry.getMaxNumBackingStores(); i++) {
             b.clear();
             final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, i);
             generationRegistry.addGenerationData(b, key, testSecureSetting);
@@ -121,7 +121,7 @@
         }
         b.clear();
         final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE,
-                GenerationRegistry.NUM_MAX_BACKING_STORE + 1);
+                generationRegistry.getMaxNumBackingStores() + 1);
         generationRegistry.addGenerationData(b, key, testSecureSetting);
         // Should fail to add generation because the number of backing stores has reached limit
         checkBundle(b, -1, -1, true);
@@ -133,7 +133,7 @@
 
     @Test
     public void testMaxSizeBackingStore() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1);
         final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
         final String testSecureSetting = "test_secure_setting";
         Bundle b = new Bundle();
@@ -153,7 +153,7 @@
 
     @Test
     public void testUnsetSettings() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1);
         final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
         final String testSecureSetting = "test_secure_setting";
         Bundle b = new Bundle();
@@ -172,7 +172,7 @@
 
     @Test
     public void testGlobalSettings() throws IOException {
-        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2);
         final int globalKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 0);
         final String testGlobalSetting = "test_global_setting";
         final Bundle b = new Bundle();
@@ -188,6 +188,18 @@
         assertThat(array).isEqualTo(array2);
     }
 
+    @Test
+    public void testNumberOfBackingStores() {
+        GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 0);
+        // Test that the capacity of the backing stores is always valid
+        assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo(
+                GenerationRegistry.MIN_NUM_BACKING_STORE);
+        generationRegistry = new GenerationRegistry(new Object(), 100);
+        // Test that the capacity of the backing stores is always valid
+        assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo(
+                GenerationRegistry.MAX_NUM_BACKING_STORE);
+    }
+
     private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)
             throws IOException {
         final MemoryIntArray array = getArray(b);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 8b38deb..941697b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -376,7 +376,7 @@
     name: "SystemUIRobo-stub",
     defaults: [
         "platform_app_defaults",
-        "SystemUI_app_defaults",
+        "SystemUI_optimized_defaults",
         "SystemUI_compose_defaults",
     ],
     manifest: "tests/AndroidManifest-base.xml",
@@ -443,7 +443,7 @@
 }
 
 systemui_optimized_java_defaults {
-    name: "SystemUI_app_defaults",
+    name: "SystemUI_optimized_defaults",
     soong_config_variables: {
         SYSTEMUI_OPTIMIZE_JAVA: {
             optimize: {
@@ -452,12 +452,10 @@
                 shrink: true,
                 shrink_resources: true,
                 proguard_compatibility: false,
-                proguard_flags_files: ["proguard.flags"],
             },
             conditions_default: {
                 optimize: {
                     proguard_compatibility: false,
-                    proguard_flags_files: ["proguard.flags"],
                 },
             },
         },
@@ -468,7 +466,7 @@
     name: "SystemUI",
     defaults: [
         "platform_app_defaults",
-        "SystemUI_app_defaults",
+        "SystemUI_optimized_defaults",
     ],
     static_libs: [
         "SystemUI-core",
@@ -483,6 +481,9 @@
     kotlincflags: ["-Xjvm-default=enable"],
 
     dxflags: ["--multi-dex"],
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
     required: [
         "privapp_whitelist_com.android.systemui",
         "wmshell.protolog.json.gz",
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 4f7d099..4d6c2022 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -321,7 +321,8 @@
         <RelativeLayout
             android:id="@+id/bottom_buttons"
             android:layout_width="match_parent"
-            android:layout_height="60dp"
+            android:layout_height="wrap_content"
+            android:minHeight="60dp"
             android:gravity="center_vertical"
             android:paddingStart="4dp"
             android:paddingEnd="4dp"
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 5ec59ab..7b781ce 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -22,6 +22,7 @@
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
 
+import android.annotation.Nullable;
 import android.database.ContentObserver;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -458,6 +459,7 @@
         mView.setClock(clock, mStatusBarStateController.getState());
     }
 
+    @Nullable
     private ClockController getClock() {
         return mClockEventController.getClock();
     }
@@ -510,7 +512,9 @@
     }
 
     /** Gets the animations for the current clock. */
+    @Nullable
     public ClockAnimations getClockAnimations() {
-        return getClock().getAnimations();
+        ClockController clock = getClock();
+        return clock == null ? null : clock.getAnimations();
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f2f0c59..085f202 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -480,6 +480,11 @@
         sCurrentUser = currentUser;
     }
 
+    /**
+     * @deprecated This can potentially return unexpected values in a multi user scenario
+     * as this state is managed by another component. Consider using {@link UserTracker}.
+     */
+    @Deprecated
     public synchronized static int getCurrentUser() {
         return sCurrentUser;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
index 055cd52..7f567aa 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
@@ -23,6 +23,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dreams.callbacks.DreamStatusBarStateCallback;
 import com.android.systemui.dreams.conditions.DreamCondition;
+import com.android.systemui.flags.RestartDozeListener;
 import com.android.systemui.shared.condition.Monitor;
 import com.android.systemui.util.condition.ConditionalCoreStartable;
 
@@ -39,17 +40,19 @@
     private final Monitor mConditionMonitor;
     private final DreamCondition mDreamCondition;
     private final DreamStatusBarStateCallback mCallback;
+    private RestartDozeListener mRestartDozeListener;
 
 
     @Inject
     public DreamMonitor(Monitor monitor, DreamCondition dreamCondition,
             @Named(DREAM_PRETEXT_MONITOR) Monitor pretextMonitor,
-            DreamStatusBarStateCallback callback) {
+            DreamStatusBarStateCallback callback,
+            RestartDozeListener restartDozeListener) {
         super(pretextMonitor);
         mConditionMonitor = monitor;
         mDreamCondition = dreamCondition;
         mCallback = callback;
-
+        mRestartDozeListener = restartDozeListener;
     }
 
     @Override
@@ -61,5 +64,8 @@
         mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
                 .addCondition(mDreamCondition)
                 .build());
+
+        mRestartDozeListener.init();
+        mRestartDozeListener.maybeRestartSleep();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
index 2befce7..5bbfbda 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.dreams.conditions;
 
+import android.app.DreamManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -30,6 +31,7 @@
  */
 public class DreamCondition extends Condition {
     private final Context mContext;
+    private final DreamManager mDreamManager;
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -39,8 +41,10 @@
     };
 
     @Inject
-    public DreamCondition(Context context) {
+    public DreamCondition(Context context,
+            DreamManager dreamManager) {
         mContext = context;
+        mDreamManager = dreamManager;
     }
 
     private void processIntent(Intent intent) {
@@ -62,8 +66,8 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_DREAMING_STARTED);
         filter.addAction(Intent.ACTION_DREAMING_STOPPED);
-        final Intent stickyIntent = mContext.registerReceiver(mReceiver, filter);
-        processIntent(stickyIntent);
+        mContext.registerReceiver(mReceiver, filter);
+        updateCondition(mDreamManager.isDreaming());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 06ca0ad..28c45b8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.util.InitializationChecker
-import com.android.systemui.util.concurrency.DelayableExecutor
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -38,8 +37,6 @@
     private val featureFlags: FeatureFlagsDebug,
     private val broadcastSender: BroadcastSender,
     private val initializationChecker: InitializationChecker,
-    private val restartDozeListener: RestartDozeListener,
-    private val delayableExecutor: DelayableExecutor
 ) : CoreStartable {
 
     init {
@@ -55,9 +52,6 @@
             // protected broadcast should only be sent for the main process
             val intent = Intent(FlagManager.ACTION_SYSUI_STARTED)
             broadcastSender.sendBroadcast(intent)
-
-            restartDozeListener.init()
-            delayableExecutor.executeDelayed({ restartDozeListener.maybeRestartSleep() }, 1000)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
index 133e67f..f97112d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
@@ -18,8 +18,6 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.util.InitializationChecker
-import com.android.systemui.util.concurrency.DelayableExecutor
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -31,9 +29,6 @@
 constructor(
     dumpManager: DumpManager,
     featureFlags: FeatureFlags,
-    private val initializationChecker: InitializationChecker,
-    private val restartDozeListener: RestartDozeListener,
-    private val delayableExecutor: DelayableExecutor
 ) : CoreStartable {
 
     init {
@@ -42,12 +37,7 @@
         }
     }
 
-    override fun start() {
-        if (initializationChecker.initializeComponents()) {
-            restartDozeListener.init()
-            delayableExecutor.executeDelayed({ restartDozeListener.maybeRestartSleep() }, 1000)
-        }
-    }
+    override fun start() {}
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index a9dd260..8f3f64f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -415,7 +415,7 @@
     @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
 
     // TODO(b/270882464): Tracking Bug
-    val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2", teamfood = true)
+    val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2")
 
     // TODO(b/265045965): Tracking Bug
     val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt b/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt
index bd74f4e..b49d60d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt
@@ -20,7 +20,9 @@
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -33,6 +35,7 @@
     private val statusBarStateController: StatusBarStateController,
     private val powerManager: PowerManager,
     private val systemClock: SystemClock,
+    @Background val bgExecutor: DelayableExecutor,
 ) {
 
     companion object {
@@ -44,7 +47,7 @@
     val listener =
         object : StatusBarStateController.StateListener {
             override fun onDreamingChanged(isDreaming: Boolean) {
-                settings.putBool(RESTART_NAP_KEY, isDreaming)
+                storeSleepState(isDreaming)
             }
         }
 
@@ -62,11 +65,19 @@
     }
 
     fun maybeRestartSleep() {
-        if (settings.getBool(RESTART_NAP_KEY, false)) {
-            Log.d("RestartDozeListener", "Restarting sleep state")
-            powerManager.wakeUp(systemClock.uptimeMillis())
-            powerManager.goToSleep(systemClock.uptimeMillis())
-            settings.putBool(RESTART_NAP_KEY, false)
-        }
+        bgExecutor.executeDelayed(
+            {
+                if (settings.getBool(RESTART_NAP_KEY, false)) {
+                    Log.d("RestartDozeListener", "Restarting sleep state")
+                    powerManager.wakeUp(systemClock.uptimeMillis())
+                    powerManager.goToSleep(systemClock.uptimeMillis())
+                }
+            },
+            1000
+        )
+    }
+
+    private fun storeSleepState(sleeping: Boolean) {
+        bgExecutor.execute { settings.putBool(RESTART_NAP_KEY, sleeping) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 889adc7..5704f88 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -160,6 +160,14 @@
         return factory.create("QSLog", 700 /* maxSize */, false /* systrace */);
     }
 
+    /** Provides a logging buffer for logs related to Quick Settings configuration. */
+    @Provides
+    @SysUISingleton
+    @QSConfigLog
+    public static LogBuffer provideQSConfigLogBuffer(LogBufferFactory factory) {
+        return factory.create("QSConfigLog", 100 /* maxSize */, true /* systrace */);
+    }
+
     /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java
new file mode 100644
index 0000000..295bf88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.plugins.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for QS configuration changed messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface QSConfigLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index b71a9193..6cf297c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,10 +16,6 @@
 
 package com.android.systemui.media.dialog;
 
-import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
-import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED;
-import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
-
 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP;
 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE;
 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
@@ -535,11 +531,12 @@
         @DoNotInline
         static Drawable getDeviceStatusIconBasedOnSelectionBehavior(MediaDevice device,
                 Context context) {
-            switch (device.getSubtext()) {
-                case SUBTEXT_AD_ROUTING_DISALLOWED:
-                case SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED:
+            switch (device.getSelectionBehavior()) {
+                case SELECTION_BEHAVIOR_NONE:
                     return context.getDrawable(R.drawable.media_output_status_failed);
-                case SUBTEXT_SUBSCRIPTION_REQUIRED:
+                case SELECTION_BEHAVIOR_TRANSFER:
+                    return null;
+                case SELECTION_BEHAVIOR_GO_TO_APP:
                     return context.getDrawable(R.drawable.media_output_status_help);
             }
             return null;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index f76f049..f92a5ab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -192,8 +192,11 @@
             mSubTitleText.setTextColor(mController.getColorItemContent());
             mTwoLineTitleText.setTextColor(mController.getColorItemContent());
             if (mController.isAdvancedLayoutSupported()) {
-                mIconAreaLayout.setOnClickListener(null);
                 mVolumeValueText.setTextColor(mController.getColorItemContent());
+                mTitleIcon.setOnTouchListener(((v, event) -> {
+                    mSeekBar.dispatchTouchEvent(event);
+                    return false;
+                }));
             }
             mSeekBar.setProgressTintList(
                     ColorStateList.valueOf(mController.getColorSeekbarProgress()));
@@ -444,9 +447,6 @@
         }
 
         void updateIconAreaClickListener(View.OnClickListener listener) {
-            if (mController.isAdvancedLayoutSupported()) {
-                mIconAreaLayout.setOnClickListener(listener);
-            }
             mTitleIcon.setOnClickListener(listener);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java
index 253c3c7..be5d607 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java
@@ -26,6 +26,7 @@
  */
 public class MediaOutputSeekbar extends SeekBar {
     private static final int SCALE_SIZE = 1000;
+    private static final int INITIAL_PROGRESS = 500;
     public static final int VOLUME_PERCENTAGE_SCALE_SIZE = 100000;
 
     public MediaOutputSeekbar(Context context, AttributeSet attrs) {
@@ -38,7 +39,7 @@
     }
 
     static int scaleVolumeToProgress(int volume) {
-        return volume * SCALE_SIZE;
+        return volume == 0 ? 0 : INITIAL_PROGRESS + volume * SCALE_SIZE;
     }
 
     int getVolume() {
@@ -46,7 +47,7 @@
     }
 
     void setVolume(int volume) {
-        setProgress(volume * SCALE_SIZE, true);
+        setProgress(scaleVolumeToProgress(volume), true);
     }
 
     void setMaxVolume(int maxVolume) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index cfcc671..84d23c6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -18,7 +18,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 
 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
-import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMotionEvent;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -244,7 +244,7 @@
     private boolean mIsBackGestureAllowed;
     private boolean mGestureBlockingActivityRunning;
     private boolean mIsNewBackAffordanceEnabled;
-    private boolean mIsTrackpadGestureBackEnabled;
+    private boolean mIsTrackpadGestureFeaturesEnabled;
     private boolean mIsButtonForceVisible;
 
     private InputMonitor mInputMonitor;
@@ -590,7 +590,7 @@
 
             // Add a nav bar panel window
             mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
-            mIsTrackpadGestureBackEnabled = mFeatureFlags.isEnabled(
+            mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(
                     Flags.TRACKPAD_GESTURE_FEATURES);
             resetEdgeBackPlugin();
             mPluginManager.addPluginListener(
@@ -883,7 +883,7 @@
     }
 
     private void onMotionEvent(MotionEvent ev) {
-        boolean isTrackpadEvent = isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev);
+        boolean isTrackpadEvent = isTrackpadThreeFingerSwipe(mIsTrackpadGestureFeaturesEnabled, ev);
         int action = ev.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN) {
             if (DEBUG_MISSING_GESTURE) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
index 1345c9b..9e2b6d3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
@@ -22,9 +22,10 @@
 
 public final class Utilities {
 
-    public static boolean isTrackpadMotionEvent(boolean isTrackpadGestureBackEnabled,
+    public static boolean isTrackpadThreeFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
             MotionEvent event) {
-        return isTrackpadGestureBackEnabled
-                && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE;
+        return isTrackpadGestureFeaturesEnabled
+                && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+                && event.getPointerCount() == 3;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 2668d2e..fdab9b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -91,16 +91,19 @@
             new QSPanel.OnConfigurationChangedListener() {
                 @Override
                 public void onConfigurationChange(Configuration newConfig) {
-                    mQSLogger.logOnConfigurationChanged(
-                        /* lastOrientation= */ mLastOrientation,
-                        /* newOrientation= */ newConfig.orientation,
-                        /* containerName= */ mView.getDumpableTag());
-
-                    boolean previousSplitShadeState = mShouldUseSplitNotificationShade;
+                    final boolean previousSplitShadeState = mShouldUseSplitNotificationShade;
+                    final int previousOrientation = mLastOrientation;
                     mShouldUseSplitNotificationShade =
-                        LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
+                            LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
                     mLastOrientation = newConfig.orientation;
 
+                    mQSLogger.logOnConfigurationChanged(
+                        /* oldOrientation= */ previousOrientation,
+                        /* newOrientation= */ mLastOrientation,
+                        /* oldShouldUseSplitShade= */ previousSplitShadeState,
+                        /* newShouldUseSplitShade= */ mShouldUseSplitNotificationShade,
+                        /* containerName= */ mView.getDumpableTag());
+
                     switchTileLayoutIfNeeded();
                     onConfigurationChanged();
                     if (previousSplitShadeState != mShouldUseSplitNotificationShade) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 23c41db..5b461a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -16,8 +16,12 @@
 
 package com.android.systemui.qs.logging
 
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.content.res.Configuration.Orientation
 import android.service.quicksettings.Tile
 import android.view.View
+import com.android.systemui.log.dagger.QSConfigLog
 import com.android.systemui.log.dagger.QSLog
 import com.android.systemui.plugins.log.ConstantStringsLogger
 import com.android.systemui.plugins.log.ConstantStringsLoggerImpl
@@ -32,8 +36,12 @@
 
 private const val TAG = "QSLog"
 
-class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) :
-    ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
+class QSLogger
+@Inject
+constructor(
+    @QSLog private val buffer: LogBuffer,
+    @QSConfigLog private val configChangedBuffer: LogBuffer,
+) : ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
 
     fun logException(@CompileTimeConstant logMsg: String, ex: Exception) {
         buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
@@ -264,19 +272,28 @@
     }
 
     fun logOnConfigurationChanged(
-        lastOrientation: Int,
-        newOrientation: Int,
+        @Orientation oldOrientation: Int,
+        @Orientation newOrientation: Int,
+        newShouldUseSplitShade: Boolean,
+        oldShouldUseSplitShade: Boolean,
         containerName: String
     ) {
-        buffer.log(
+        configChangedBuffer.log(
             TAG,
             DEBUG,
             {
                 str1 = containerName
-                int1 = lastOrientation
+                int1 = oldOrientation
                 int2 = newOrientation
+                bool1 = oldShouldUseSplitShade
+                bool2 = newShouldUseSplitShade
             },
-            { "configuration change: $str1 orientation was $int1, now $int2" }
+            {
+                "config change: " +
+                    "$str1 orientation=${toOrientationString(int2)} " +
+                    "(was ${toOrientationString(int1)}), " +
+                    "splitShade=$bool2 (was $bool1)"
+            }
         )
     }
 
@@ -353,3 +370,11 @@
         }
     }
 }
+
+private inline fun toOrientationString(@Orientation orientation: Int): String {
+    return when (orientation) {
+        ORIENTATION_LANDSCAPE -> "land"
+        ORIENTATION_PORTRAIT -> "port"
+        else -> "undefined"
+    }
+}
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 aa2ea0b6..46412a3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -35,6 +35,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.graph.SignalDrawable;
@@ -174,6 +175,15 @@
         @Nullable
         String mEthernetContentDescription;
 
+        public void copyTo(EthernetCallbackInfo ethernetCallbackInfo) {
+            if (ethernetCallbackInfo == null) {
+                throw new IllegalArgumentException();
+            }
+            ethernetCallbackInfo.mConnected = this.mConnected;
+            ethernetCallbackInfo.mEthernetSignalIconId = this.mEthernetSignalIconId;
+            ethernetCallbackInfo.mEthernetContentDescription = this.mEthernetContentDescription;
+        }
+
         @Override
         public String toString() {
             return new StringBuilder("EthernetCallbackInfo[")
@@ -200,6 +210,23 @@
         boolean mNoValidatedNetwork;
         boolean mNoNetworksAvailable;
 
+        public void copyTo(WifiCallbackInfo wifiCallbackInfo) {
+            if (wifiCallbackInfo == null) {
+                throw new IllegalArgumentException();
+            }
+            wifiCallbackInfo.mAirplaneModeEnabled = this.mAirplaneModeEnabled;
+            wifiCallbackInfo.mEnabled = this.mEnabled;
+            wifiCallbackInfo.mConnected = this.mConnected;
+            wifiCallbackInfo.mWifiSignalIconId = this.mWifiSignalIconId;
+            wifiCallbackInfo.mSsid = this.mSsid;
+            wifiCallbackInfo.mWifiSignalContentDescription = this.mWifiSignalContentDescription;
+            wifiCallbackInfo.mIsTransient = this.mIsTransient;
+            wifiCallbackInfo.mStatusLabel = this.mStatusLabel;
+            wifiCallbackInfo.mNoDefaultNetwork = this.mNoDefaultNetwork;
+            wifiCallbackInfo.mNoValidatedNetwork = this.mNoValidatedNetwork;
+            wifiCallbackInfo.mNoNetworksAvailable = this.mNoNetworksAvailable;
+        }
+
         @Override
         public String toString() {
             return new StringBuilder("WifiCallbackInfo[")
@@ -232,6 +259,23 @@
         boolean mNoValidatedNetwork;
         boolean mNoNetworksAvailable;
 
+        public void copyTo(CellularCallbackInfo cellularCallbackInfo) {
+            if (cellularCallbackInfo == null) {
+                throw new IllegalArgumentException();
+            }
+            cellularCallbackInfo.mAirplaneModeEnabled = this.mAirplaneModeEnabled;
+            cellularCallbackInfo.mDataSubscriptionName = this.mDataSubscriptionName;
+            cellularCallbackInfo.mDataContentDescription = this.mDataContentDescription;
+            cellularCallbackInfo.mMobileSignalIconId = this.mMobileSignalIconId;
+            cellularCallbackInfo.mQsTypeIcon = this.mQsTypeIcon;
+            cellularCallbackInfo.mNoSim = this.mNoSim;
+            cellularCallbackInfo.mRoaming = this.mRoaming;
+            cellularCallbackInfo.mMultipleSubs = this.mMultipleSubs;
+            cellularCallbackInfo.mNoDefaultNetwork = this.mNoDefaultNetwork;
+            cellularCallbackInfo.mNoValidatedNetwork = this.mNoValidatedNetwork;
+            cellularCallbackInfo.mNoNetworksAvailable = this.mNoNetworksAvailable;
+        }
+
         @Override
         public String toString() {
             return new StringBuilder("CellularCallbackInfo[")
@@ -251,8 +295,11 @@
     }
 
     protected final class InternetSignalCallback implements SignalCallback {
+        @GuardedBy("mWifiInfo")
         final WifiCallbackInfo mWifiInfo = new WifiCallbackInfo();
+        @GuardedBy("mCellularInfo")
         final CellularCallbackInfo mCellularInfo = new CellularCallbackInfo();
+        @GuardedBy("mEthernetInfo")
         final EthernetCallbackInfo mEthernetInfo = new EthernetCallbackInfo();
 
 
@@ -261,19 +308,23 @@
             if (DEBUG) {
                 Log.d(TAG, "setWifiIndicators: " + indicators);
             }
-            mWifiInfo.mEnabled = indicators.enabled;
-            mWifiInfo.mSsid = indicators.description;
-            mWifiInfo.mIsTransient = indicators.isTransient;
-            mWifiInfo.mStatusLabel = indicators.statusLabel;
+            synchronized (mWifiInfo) {
+                mWifiInfo.mEnabled = indicators.enabled;
+                mWifiInfo.mSsid = indicators.description;
+                mWifiInfo.mIsTransient = indicators.isTransient;
+                mWifiInfo.mStatusLabel = indicators.statusLabel;
+                if (indicators.qsIcon != null) {
+                    mWifiInfo.mConnected = indicators.qsIcon.visible;
+                    mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
+                    mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
+                } else {
+                    mWifiInfo.mConnected = false;
+                    mWifiInfo.mWifiSignalIconId = 0;
+                    mWifiInfo.mWifiSignalContentDescription = null;
+                }
+            }
             if (indicators.qsIcon != null) {
-                mWifiInfo.mConnected = indicators.qsIcon.visible;
-                mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
-                mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
                 refreshState(mWifiInfo);
-            } else {
-                mWifiInfo.mConnected = false;
-                mWifiInfo.mWifiSignalIconId = 0;
-                mWifiInfo.mWifiSignalContentDescription = null;
             }
         }
 
@@ -286,14 +337,16 @@
                 // Not data sim, don't display.
                 return;
             }
-            mCellularInfo.mDataSubscriptionName = indicators.qsDescription == null
+            synchronized (mCellularInfo) {
+                mCellularInfo.mDataSubscriptionName = indicators.qsDescription == null
                     ? mController.getMobileDataNetworkName() : indicators.qsDescription;
-            mCellularInfo.mDataContentDescription = indicators.qsDescription != null
+                mCellularInfo.mDataContentDescription = indicators.qsDescription != null
                     ? indicators.typeContentDescriptionHtml : null;
-            mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
-            mCellularInfo.mQsTypeIcon = indicators.qsType;
-            mCellularInfo.mRoaming = indicators.roaming;
-            mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
+                mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
+                mCellularInfo.mQsTypeIcon = indicators.qsType;
+                mCellularInfo.mRoaming = indicators.roaming;
+                mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
+            }
             refreshState(mCellularInfo);
         }
 
@@ -303,9 +356,11 @@
                 Log.d(TAG, "setEthernetIndicators: "
                         + "icon = " + (icon == null ? "" :  icon.toString()));
             }
-            mEthernetInfo.mConnected = icon.visible;
-            mEthernetInfo.mEthernetSignalIconId = icon.icon;
-            mEthernetInfo.mEthernetContentDescription = icon.contentDescription;
+            synchronized (mEthernetInfo) {
+                mEthernetInfo.mConnected = icon.visible;
+                mEthernetInfo.mEthernetSignalIconId = icon.icon;
+                mEthernetInfo.mEthernetContentDescription = icon.contentDescription;
+            }
             if (icon.visible) {
                 refreshState(mEthernetInfo);
             }
@@ -318,11 +373,13 @@
                         + "show = " + show + ","
                         + "simDetected = " + simDetected);
             }
-            mCellularInfo.mNoSim = show;
-            if (mCellularInfo.mNoSim) {
-                // Make sure signal gets cleared out when no sims.
-                mCellularInfo.mMobileSignalIconId = 0;
-                mCellularInfo.mQsTypeIcon = 0;
+            synchronized (mCellularInfo) {
+                mCellularInfo.mNoSim = show;
+                if (mCellularInfo.mNoSim) {
+                    // Make sure signal gets cleared out when no sims.
+                    mCellularInfo.mMobileSignalIconId = 0;
+                    mCellularInfo.mQsTypeIcon = 0;
+                }
             }
         }
 
@@ -335,8 +392,12 @@
             if (mCellularInfo.mAirplaneModeEnabled == icon.visible) {
                 return;
             }
-            mCellularInfo.mAirplaneModeEnabled = icon.visible;
-            mWifiInfo.mAirplaneModeEnabled = icon.visible;
+            synchronized (mCellularInfo) {
+                mCellularInfo.mAirplaneModeEnabled = icon.visible;
+            }
+            synchronized (mWifiInfo) {
+                mWifiInfo.mAirplaneModeEnabled = icon.visible;
+            }
             if (!mSignalCallback.mEthernetInfo.mConnected) {
                 // Always use mWifiInfo to refresh the Internet Tile if airplane mode is enabled,
                 // because Internet Tile will show different information depending on whether WiFi
@@ -363,12 +424,16 @@
                         + "noValidatedNetwork = " + noValidatedNetwork + ","
                         + "noNetworksAvailable = " + noNetworksAvailable);
             }
-            mCellularInfo.mNoDefaultNetwork = noDefaultNetwork;
-            mCellularInfo.mNoValidatedNetwork = noValidatedNetwork;
-            mCellularInfo.mNoNetworksAvailable = noNetworksAvailable;
-            mWifiInfo.mNoDefaultNetwork = noDefaultNetwork;
-            mWifiInfo.mNoValidatedNetwork = noValidatedNetwork;
-            mWifiInfo.mNoNetworksAvailable = noNetworksAvailable;
+            synchronized (mCellularInfo) {
+                mCellularInfo.mNoDefaultNetwork = noDefaultNetwork;
+                mCellularInfo.mNoValidatedNetwork = noValidatedNetwork;
+                mCellularInfo.mNoNetworksAvailable = noNetworksAvailable;
+            }
+            synchronized (mWifiInfo) {
+                mWifiInfo.mNoDefaultNetwork = noDefaultNetwork;
+                mWifiInfo.mNoValidatedNetwork = noValidatedNetwork;
+                mWifiInfo.mNoNetworksAvailable = noNetworksAvailable;
+            }
             if (!noDefaultNetwork) {
                 return;
             }
@@ -403,11 +468,23 @@
             // arg = null, in this case the last updated CellularCallbackInfo or WifiCallbackInfo
             // should be used to refresh the tile.
             if (mLastTileState == LAST_STATE_CELLULAR) {
-                handleUpdateCellularState(state, mSignalCallback.mCellularInfo);
+                CellularCallbackInfo cellularInfo = new CellularCallbackInfo();
+                synchronized (mSignalCallback.mCellularInfo) {
+                    mSignalCallback.mCellularInfo.copyTo(cellularInfo);
+                }
+                handleUpdateCellularState(state, cellularInfo);
             } else if (mLastTileState == LAST_STATE_WIFI) {
-                handleUpdateWifiState(state, mSignalCallback.mWifiInfo);
+                WifiCallbackInfo mifiInfo = new WifiCallbackInfo();
+                synchronized (mSignalCallback.mWifiInfo) {
+                    mSignalCallback.mWifiInfo.copyTo(mifiInfo);
+                }
+                handleUpdateCellularState(state, mifiInfo);
             } else if (mLastTileState == LAST_STATE_ETHERNET) {
-                handleUpdateEthernetState(state, mSignalCallback.mEthernetInfo);
+                EthernetCallbackInfo ethernetInfo = new EthernetCallbackInfo();
+                synchronized (mSignalCallback.mEthernetInfo) {
+                    mSignalCallback.mEthernetInfo.copyTo(ethernetInfo);
+                }
+                handleUpdateCellularState(state, ethernetInfo);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 557e95c..7ad594e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -115,6 +115,8 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -613,11 +615,13 @@
         // Note that this may block if the sound is still being loaded (very unlikely) but we can't
         // reliably release in the background because the service is being destroyed.
         try {
-            MediaPlayer player = mCameraSound.get();
+            MediaPlayer player = mCameraSound.get(1, TimeUnit.SECONDS);
             if (player != null) {
                 player.release();
             }
-        } catch (InterruptedException | ExecutionException e) {
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            mCameraSound.cancel(true);
+            Log.w(TAG, "Error releasing shutter sound", e);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
index 48aa60f..253f07d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.screenshot
 
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ComponentInfoFlags
+import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS
 import android.view.Display
 import android.view.IWindowManager
 import android.view.ViewGroup
@@ -45,7 +47,7 @@
         // Convert component names to app names.
         return components.map {
             packageManager
-                .getActivityInfo(it, PackageManager.ComponentInfoFlags.of(0))
+                .getActivityInfo(it, ComponentInfoFlags.of(MATCH_DISABLED_COMPONENTS.toLong()))
                 .loadLabel(packageManager)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index 236213c..798c490 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -19,6 +19,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ComponentInfoFlags
 import android.graphics.drawable.Drawable
 import android.os.UserHandle
 import android.os.UserManager
@@ -53,12 +54,9 @@
             var badgedIcon: Drawable? = null
             var label: CharSequence? = null
             val fileManager = fileManagerComponentName()
+                ?: return WorkProfileFirstRunData(defaultFileAppName(), null)
             try {
-                val info =
-                    packageManager.getActivityInfo(
-                        fileManager,
-                        PackageManager.ComponentInfoFlags.of(0)
-                    )
+                val info = packageManager.getActivityInfo(fileManager, ComponentInfoFlags.of(0L))
                 val icon = packageManager.getActivityIcon(fileManager)
                 badgedIcon = packageManager.getUserBadgedIcon(icon, userHandle)
                 label = info.loadLabel(packageManager)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 435a596..3360511 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -159,6 +159,7 @@
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
 import com.android.systemui.plugins.qs.QS;
@@ -1592,10 +1593,9 @@
                 transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
                 transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
 
-                boolean customClockAnimation =
-                            mKeyguardStatusViewController.getClockAnimations() != null
-                            && mKeyguardStatusViewController.getClockAnimations()
-                                    .getHasCustomPositionUpdatedAnimation();
+                ClockAnimations clockAnims = mKeyguardStatusViewController.getClockAnimations();
+                boolean customClockAnimation = clockAnims != null
+                        && clockAnims.getHasCustomPositionUpdatedAnimation();
 
                 if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
                     // Find the clock, so we can exclude it from this transition.
@@ -5119,9 +5119,14 @@
             Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
             Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
 
-            anim.addUpdateListener(
-                    animation -> mController.getClockAnimations().onPositionUpdated(
-                            from, to, animation.getAnimatedFraction()));
+            anim.addUpdateListener(animation -> {
+                ClockAnimations clockAnims = mController.getClockAnimations();
+                if (clockAnims == null) {
+                    return;
+                }
+
+                clockAnims.onPositionUpdated(from, to, animation.getAnimatedFraction());
+            });
 
             return anim;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 779be2b..fda2277 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -28,7 +28,6 @@
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
-import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
@@ -99,6 +98,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -146,6 +146,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final AuthController mAuthController;
     private final KeyguardLogger mKeyguardLogger;
+    private final UserTracker mUserTracker;
     private ViewGroup mIndicationArea;
     private KeyguardIndicationTextView mTopIndicationView;
     private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -251,7 +252,8 @@
             FaceHelpMessageDeferral faceHelpMessageDeferral,
             KeyguardLogger keyguardLogger,
             AlternateBouncerInteractor alternateBouncerInteractor,
-            AlarmManager alarmManager
+            AlarmManager alarmManager,
+            UserTracker userTracker
     ) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -275,6 +277,7 @@
         mKeyguardLogger = keyguardLogger;
         mScreenLifecycle.addObserver(mScreenObserver);
         mAlternateBouncerInteractor = alternateBouncerInteractor;
+        mUserTracker = userTracker;
 
         mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
         mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>();
@@ -475,6 +478,10 @@
         }
     }
 
+    private int getCurrentUser() {
+        return mUserTracker.getUserId();
+    }
+
     private void updateLockScreenOwnerInfo() {
         // Check device owner info on a bg thread.
         // It makes multiple IPCs that could block the thread it's run on.
@@ -1166,8 +1173,7 @@
                             mContext.getString(R.string.keyguard_unlock)
                     );
                 } else if (fpAuthFailed
-                        && mKeyguardUpdateMonitor.getUserHasTrust(
-                                KeyguardUpdateMonitor.getCurrentUser())) {
+                        && mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())) {
                     showBiometricMessage(
                             getTrustGrantedIndication(),
                             mContext.getString(R.string.keyguard_unlock)
@@ -1421,7 +1427,7 @@
 
     private boolean canUnlockWithFingerprint() {
         return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                KeyguardUpdateMonitor.getCurrentUser());
+                getCurrentUser());
     }
 
     private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index f866d65..d0c6215 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -105,10 +105,14 @@
      * The reason we need to do this is because TelephonyManager limits the number of registered
      * listeners per-process, so we don't want to create a new listener for every callback.
      *
-     * A note on the design for back pressure here: We use the [coalesce] operator here to change
-     * the backpressure strategy to store exactly the last callback event of _each type_ here, as
-     * opposed to the default strategy which is to drop the oldest event (regardless of type). This
-     * means that we should never miss any single event as long as the flow has been started.
+     * A note on the design for back pressure here: We don't control _which_ telephony callback
+     * comes in first, since we register every relevant bit of information as a batch. E.g., if a
+     * downstream starts collecting on a field which is backed by
+     * [TelephonyCallback.ServiceStateListener], it's not possible for us to guarantee that _that_
+     * callback comes in -- the first callback could very well be
+     * [TelephonyCallback.DataActivityListener], which would promptly be dropped if we didn't keep
+     * it tracked. We use the [scan] operator here to track the most recent callback of _each type_
+     * here. See [TelephonyCallbackState] to see how the callbacks are stored.
      */
     private val callbackEvents: StateFlow<TelephonyCallbackState> = run {
         val initial = TelephonyCallbackState()
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletCardsUpdatedListener.aidl b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletCardsUpdatedListener.aidl
new file mode 100644
index 0000000..aa7ef57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletCardsUpdatedListener.aidl
@@ -0,0 +1,7 @@
+package com.android.systemui.wallet.controller;
+
+import android.service.quickaccesswallet.WalletCard;
+
+interface IWalletCardsUpdatedListener {
+  void registerNewWalletCards(in List<WalletCard> cards);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletContextualLocationsService.aidl b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletContextualLocationsService.aidl
new file mode 100644
index 0000000..eebbdfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletContextualLocationsService.aidl
@@ -0,0 +1,9 @@
+package com.android.systemui.wallet.controller;
+
+import com.android.systemui.wallet.controller.IWalletCardsUpdatedListener;
+
+interface IWalletContextualLocationsService {
+  void addWalletCardsUpdatedListener(in IWalletCardsUpdatedListener listener);
+
+  void onWalletContextualLocationsStateUpdated(in List<String> storeLocations);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 2ef3511..ce2d15f 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -18,7 +18,7 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:sharedUserId="android.uid.system"
-    package="com.android.systemui" >
+    package="com.android.systemui.tests" >
 
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
@@ -64,7 +64,7 @@
             </intent-filter>
         </receiver>
 
-        <activity android:name=".wmshell.BubblesTestActivity"
+        <activity android:name="com.android.systemui.wmshell.BubblesTestActivity"
             android:allowEmbedded="true"
             android:documentLaunchMode="always"
             android:excludeFromRecents="true"
@@ -88,7 +88,7 @@
                   android:excludeFromRecents="true"
                   />
 
-        <activity android:name=".settings.brightness.BrightnessDialogTest$TestDialog"
+        <activity android:name="com.android.systemui.settings.brightness.BrightnessDialogTest$TestDialog"
             android:exported="false"
             android:excludeFromRecents="true"
             />
@@ -115,19 +115,19 @@
                   android:exported="false" />
 
         <!-- started from UsbDeviceSettingsManager -->
-        <activity android:name=".usb.UsbPermissionActivityTest$UsbPermissionActivityTestable"
+        <activity android:name="com.android.systemui.usb.UsbPermissionActivityTest$UsbPermissionActivityTestable"
                   android:exported="false"
                   android:theme="@style/Theme.SystemUI.Dialog.Alert"
                   android:finishOnCloseSystemDialogs="true"
                   android:excludeFromRecents="true" />
 
-        <activity android:name=".user.CreateUserActivityTest$CreateUserActivityTestable"
+        <activity android:name="com.android.systemui.user.CreateUserActivityTest$CreateUserActivityTestable"
             android:exported="false"
             android:theme="@style/Theme.SystemUI.Dialog.Alert"
             android:finishOnCloseSystemDialogs="true"
             android:excludeFromRecents="true" />
 
-        <activity android:name=".sensorprivacy.SensorUseStartedActivityTest$SensorUseStartedActivityTestable"
+        <activity android:name="com.android.systemui.sensorprivacy.SensorUseStartedActivityTest$SensorUseStartedActivityTestable"
                   android:exported="false"
                   android:theme="@style/Theme.SystemUI.Dialog.Alert"
                   android:finishOnCloseSystemDialogs="true"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index a5f90f8..b15ac39 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
@@ -365,6 +366,12 @@
         assertEquals(View.VISIBLE, mFakeWeatherView.getVisibility());
     }
 
+    @Test
+    public void testGetClockAnimations_nullClock_returnsNull() {
+        when(mClockEventController.getClock()).thenReturn(null);
+        assertNull(mController.getClockAnimations());
+    }
+
     private void verifyAttachment(VerificationMode times) {
         verify(mClockRegistry, times).registerClockChangeListener(
                 any(ClockRegistry.ClockChangeListener.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
new file mode 100644
index 0000000..6ddba0b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.ShadeExpansionStateManager
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
+
+    private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    private lateinit var detector: AuthDialogPanelInteractionDetector
+
+    @Mock private lateinit var action: Runnable
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    @Before
+    fun setUp() {
+        shadeExpansionStateManager = ShadeExpansionStateManager()
+        detector =
+            AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor)
+    }
+
+    @Test
+    fun testEnableDetector_shouldPostRunnable() {
+        detector.enable(action)
+        // simulate notification expand
+        shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
+        verify(action, timeout(5000).times(1)).run()
+    }
+
+    @Test
+    fun testEnableDetector_shouldNotPostRunnable() {
+        var detector =
+            AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor)
+        detector.enable(action)
+        detector.disable()
+        shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
+        verifyZeroInteractions(action)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
index 19347c7..58eb7d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -21,9 +21,11 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.DreamManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -50,6 +52,9 @@
     @Mock
     Condition.Callback mCallback;
 
+    @Mock
+    DreamManager mDreamManager;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -59,29 +64,39 @@
      * Ensure a dreaming state immediately triggers the condition.
      */
     @Test
-    public void testInitialState() {
-        final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED);
-        when(mContext.registerReceiver(any(), any())).thenReturn(intent);
-        final DreamCondition condition = new DreamCondition(mContext);
+    public void testInitialDreamingState() {
+        when(mDreamManager.isDreaming()).thenReturn(true);
+        final DreamCondition condition = new DreamCondition(mContext, mDreamManager);
         condition.addCallback(mCallback);
-        condition.start();
 
         verify(mCallback).onConditionChanged(eq(condition));
         assertThat(condition.isConditionMet()).isTrue();
     }
 
     /**
+     * Ensure a non-dreaming state does not trigger the condition.
+     */
+    @Test
+    public void testInitialNonDreamingState() {
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        final DreamCondition condition = new DreamCondition(mContext, mDreamManager);
+        condition.addCallback(mCallback);
+
+        verify(mCallback, never()).onConditionChanged(eq(condition));
+        assertThat(condition.isConditionMet()).isFalse();
+    }
+
+    /**
      * Ensure that changing dream state triggers condition.
      */
     @Test
     public void testChange() {
-        final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED);
         final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
-        when(mContext.registerReceiver(receiverCaptor.capture(), any())).thenReturn(intent);
-        final DreamCondition condition = new DreamCondition(mContext);
+        when(mDreamManager.isDreaming()).thenReturn(true);
+        final DreamCondition condition = new DreamCondition(mContext, mDreamManager);
         condition.addCallback(mCallback);
-        condition.start();
+        verify(mContext).registerReceiver(receiverCaptor.capture(), any());
         clearInvocations(mCallback);
         receiverCaptor.getValue().onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STOPPED));
         verify(mCallback).onConditionChanged(eq(condition));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
index de0e511..2db4596 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
@@ -20,6 +20,7 @@
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -41,13 +42,14 @@
     @Mock lateinit var statusBarStateController: StatusBarStateController
     @Mock lateinit var powerManager: PowerManager
     val clock = FakeSystemClock()
+    val executor = FakeExecutor(clock)
     lateinit var listener: StatusBarStateController.StateListener
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
         restartDozeListener =
-            RestartDozeListener(settings, statusBarStateController, powerManager, clock)
+            RestartDozeListener(settings, statusBarStateController, powerManager, clock, executor)
 
         val captor = ArgumentCaptor.forClass(StatusBarStateController.StateListener::class.java)
         restartDozeListener.init()
@@ -58,12 +60,14 @@
     @Test
     fun testStoreDreamState_onDreamingStarted() {
         listener.onDreamingChanged(true)
+        executor.runAllReady()
         assertThat(settings.getBool(RestartDozeListener.RESTART_NAP_KEY)).isTrue()
     }
 
     @Test
     fun testStoreDreamState_onDreamingStopped() {
         listener.onDreamingChanged(false)
+        executor.runAllReady()
         assertThat(settings.getBool(RestartDozeListener.RESTART_NAP_KEY)).isFalse()
     }
 
@@ -71,6 +75,8 @@
     fun testRestoreDreamState_dreamingShouldStart() {
         settings.putBool(RestartDozeListener.RESTART_NAP_KEY, true)
         restartDozeListener.maybeRestartSleep()
+        executor.advanceClockToLast()
+        executor.runAllReady()
         verify(powerManager).wakeUp(clock.uptimeMillis())
         verify(powerManager).goToSleep(clock.uptimeMillis())
     }
@@ -79,6 +85,8 @@
     fun testRestoreDreamState_dreamingShouldNot() {
         settings.putBool(RestartDozeListener.RESTART_NAP_KEY, false)
         restartDozeListener.maybeRestartSleep()
+        executor.advanceClockToLast()
+        executor.runAllReady()
         verify(powerManager, never()).wakeUp(anyLong())
         verify(powerManager, never()).goToSleep(anyLong())
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
index 1f18d91..08b5d2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
@@ -19,12 +19,14 @@
 import android.content.ComponentName
 import android.content.pm.ActivityInfo
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS
 import android.testing.AndroidTestingRunner
 import android.view.Display
 import android.view.IWindowManager
 import android.view.WindowManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import junit.framework.Assert.assertEquals
@@ -32,6 +34,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatcher
 import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
@@ -158,4 +161,56 @@
         assertEquals(appName2, list[1])
         assertEquals(appName3, list[2])
     }
+
+    private fun includesFlagBits(@PackageManager.ComponentInfoFlagsBits mask: Int) =
+        ComponentInfoFlagMatcher(mask, mask)
+    private fun excludesFlagBits(@PackageManager.ComponentInfoFlagsBits mask: Int) =
+        ComponentInfoFlagMatcher(mask, 0)
+
+    private class ComponentInfoFlagMatcher(
+        @PackageManager.ComponentInfoFlagsBits val mask: Int, val value: Int
+    ): ArgumentMatcher<PackageManager.ComponentInfoFlags> {
+        override fun matches(flags: PackageManager.ComponentInfoFlags): Boolean {
+            return (mask.toLong() and flags.value) == value.toLong()
+        }
+
+        override fun toString(): String{
+            return "mask 0x%08x == 0x%08x".format(mask, value)
+        }
+    }
+
+    @Test
+    fun testMaybeNotifyOfScreenshot_disabledApp() {
+        val data = ScreenshotData.forTesting()
+        data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+
+        val component = ComponentName("package1", "class1")
+        val appName = "app name"
+        val activityInfo = mock(ActivityInfo::class.java)
+
+        whenever(
+            packageManager.getActivityInfo(
+                eq(component),
+                argThat(includesFlagBits(MATCH_DISABLED_COMPONENTS))
+            )
+        ).thenReturn(activityInfo);
+
+        whenever(
+            packageManager.getActivityInfo(
+                eq(component),
+                argThat(excludesFlagBits(MATCH_DISABLED_COMPONENTS))
+            )
+        ).thenThrow(PackageManager.NameNotFoundException::class.java);
+
+        whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY)))
+            .thenReturn(listOf(component))
+
+        whenever(activityInfo.loadLabel(eq(packageManager))).thenReturn(appName)
+
+        val list = controller.maybeNotifyOfScreenshot(data)
+
+        assertEquals(1, list.size)
+        assertEquals(appName, list[0])
+    }
+
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index 3440f91..31f7771 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -45,7 +45,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -58,8 +57,9 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class WorkProfileMessageControllerTest extends SysuiTestCase {
-    private static final String DEFAULT_LABEL = "default label";
-    private static final String APP_LABEL = "app label";
+    private static final String FILES_APP_COMPONENT = "com.android.test/.FilesComponent";
+    private static final String FILES_APP_LABEL = "Custom Files App";
+    private static final String DEFAULT_FILES_APP_LABEL = "Files";
     private static final UserHandle NON_WORK_USER = UserHandle.of(0);
     private static final UserHandle WORK_USER = UserHandle.of(10);
 
@@ -88,14 +88,21 @@
         when(mMockContext.getSharedPreferences(
                 eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME),
                 eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences);
-        when(mMockContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL);
-        when(mPackageManager.getActivityIcon(any(ComponentName.class)))
+        when(mMockContext.getString(R.string.config_sceenshotWorkProfileFilesApp))
+                .thenReturn(FILES_APP_COMPONENT);
+        when(mMockContext.getString(R.string.screenshot_default_files_app_name))
+                .thenReturn(DEFAULT_FILES_APP_LABEL);
+        when(mPackageManager.getActivityIcon(
+                eq(ComponentName.unflattenFromString(FILES_APP_COMPONENT))))
                 .thenReturn(mActivityIcon);
-        when(mPackageManager.getUserBadgedIcon(
-                any(), any())).thenReturn(mBadgedActivityIcon);
-        when(mPackageManager.getActivityInfo(any(),
-                any(PackageManager.ComponentInfoFlags.class))).thenReturn(mActivityInfo);
-        when(mActivityInfo.loadLabel(eq(mPackageManager))).thenReturn(APP_LABEL);
+        when(mPackageManager.getUserBadgedIcon(any(), any()))
+                .thenReturn(mBadgedActivityIcon);
+        when(mPackageManager.getActivityInfo(
+                eq(ComponentName.unflattenFromString(FILES_APP_COMPONENT)),
+                any(PackageManager.ComponentInfoFlags.class)))
+                .thenReturn(mActivityInfo);
+        when(mActivityInfo.loadLabel(eq(mPackageManager)))
+                .thenReturn(FILES_APP_LABEL);
 
         mSharedPreferences.edit().putBoolean(
                 WorkProfileMessageController.PREFERENCE_KEY, false).apply();
@@ -120,14 +127,15 @@
     @Test
     public void testOnScreenshotTaken_packageNotFound()
             throws PackageManager.NameNotFoundException {
-        when(mPackageManager.getActivityInfo(any(),
+        when(mPackageManager.getActivityInfo(
+                eq(ComponentName.unflattenFromString(FILES_APP_COMPONENT)),
                 any(PackageManager.ComponentInfoFlags.class))).thenThrow(
                 new PackageManager.NameNotFoundException());
 
         WorkProfileMessageController.WorkProfileFirstRunData data =
                 mMessageController.onScreenshotTaken(WORK_USER);
 
-        assertEquals(DEFAULT_LABEL, data.getAppName());
+        assertEquals(DEFAULT_FILES_APP_LABEL, data.getAppName());
         assertNull(data.getIcon());
     }
 
@@ -136,16 +144,28 @@
         WorkProfileMessageController.WorkProfileFirstRunData data =
                 mMessageController.onScreenshotTaken(WORK_USER);
 
-        assertEquals(APP_LABEL, data.getAppName());
+        assertEquals(FILES_APP_LABEL, data.getAppName());
         assertEquals(mBadgedActivityIcon, data.getIcon());
     }
 
     @Test
+    public void testOnScreenshotTaken_noFilesAppComponentDefined() {
+        when(mMockContext.getString(R.string.config_sceenshotWorkProfileFilesApp))
+                .thenReturn("");
+
+        WorkProfileMessageController.WorkProfileFirstRunData data =
+                mMessageController.onScreenshotTaken(WORK_USER);
+
+        assertEquals(DEFAULT_FILES_APP_LABEL, data.getAppName());
+        assertNull(data.getIcon());
+    }
+
+    @Test
     public void testPopulateView() throws InterruptedException {
         ViewGroup layout = (ViewGroup) LayoutInflater.from(mContext).inflate(
                 R.layout.screenshot_work_profile_first_run, null);
         WorkProfileMessageController.WorkProfileFirstRunData data =
-                new WorkProfileMessageController.WorkProfileFirstRunData(APP_LABEL,
+                new WorkProfileMessageController.WorkProfileFirstRunData(FILES_APP_LABEL,
                         mBadgedActivityIcon);
         final CountDownLatch countdown = new CountDownLatch(1);
         mMessageController.populateView(layout, data, () -> {
@@ -157,7 +177,7 @@
         assertEquals(mBadgedActivityIcon, image.getDrawable());
         TextView text = layout.findViewById(R.id.screenshot_message_content);
         // The app name is used in a template, but at least validate that it was inserted.
-        assertTrue(text.getText().toString().contains(APP_LABEL));
+        assertTrue(text.getText().toString().contains(FILES_APP_LABEL));
 
         // Validate that clicking the dismiss button calls back properly.
         assertEquals(1, countdown.getCount());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 251aced..569f90b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -105,6 +105,7 @@
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -188,6 +189,8 @@
     private AuthController mAuthController;
     @Mock
     private AlarmManager mAlarmManager;
+    @Mock
+    private UserTracker mUserTracker;
     @Captor
     private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
     @Captor
@@ -209,6 +212,7 @@
     private BroadcastReceiver mBroadcastReceiver;
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
     private TestableLooper mTestableLooper;
+    private final int mCurrentUserId = 1;
 
     private KeyguardIndicationTextView mTextView; // AOD text
 
@@ -260,6 +264,7 @@
                 .thenReturn(mDisclosureGeneric);
         when(mDevicePolicyResourcesManager.getString(anyString(), any(), anyString()))
                 .thenReturn(mDisclosureWithOrganization);
+        when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
 
         mWakeLock = new WakeLockFake();
         mWakeLockBuilder = new WakeLockFake.Builder(mContext);
@@ -291,7 +296,8 @@
                 mKeyguardBypassController, mAccessibilityManager,
                 mFaceHelpMessageDeferral, mock(KeyguardLogger.class),
                 mAlternateBouncerInteractor,
-                mAlarmManager
+                mAlarmManager,
+                mUserTracker
         );
         mController.init();
         mController.setIndicationArea(mIndicationArea);
@@ -813,7 +819,7 @@
     public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
         createController();
         when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                0)).thenReturn(true);
+                getCurrentUser())).thenReturn(true);
         String message = "A message";
 
         mController.setVisible(true);
@@ -828,7 +834,7 @@
 
         // GIVEN fingerprint enrolled
         when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                0)).thenReturn(true);
+                getCurrentUser())).thenReturn(true);
 
         // WHEN help messages received that are allowed to show
         final String helpString = "helpString";
@@ -855,7 +861,7 @@
 
         // GIVEN fingerprint enrolled
         when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                0)).thenReturn(true);
+                getCurrentUser())).thenReturn(true);
 
         // WHEN help messages received that aren't supposed to show
         final String helpString = "helpString";
@@ -882,7 +888,7 @@
 
         // GIVEN fingerprint NOT enrolled
         when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                0)).thenReturn(false);
+                getCurrentUser())).thenReturn(false);
 
         // WHEN help messages received
         final Set<CharSequence> helpStrings = new HashSet<>();
@@ -913,7 +919,7 @@
 
         // GIVEN fingerprint NOT enrolled
         when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                0)).thenReturn(false);
+                getCurrentUser())).thenReturn(false);
 
         // WHEN help message received and deferred message is valid
         final String helpString = "helpMsg";
@@ -944,7 +950,7 @@
 
         // GIVEN fingerprint enrolled
         when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                0)).thenReturn(true);
+                getCurrentUser())).thenReturn(true);
 
         // WHEN help message received and deferredMessage is valid
         final String helpString = "helpMsg";
@@ -1173,7 +1179,7 @@
 
         // WHEN trust is granted
         when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
-        mKeyguardUpdateMonitorCallback.onTrustChanged(KeyguardUpdateMonitor.getCurrentUser());
+        mKeyguardUpdateMonitorCallback.onTrustChanged(getCurrentUser());
 
         // THEN verify the trust granted message shows
         verifyIndicationMessage(
@@ -1238,7 +1244,7 @@
     public void coEx_faceSuccess_showsPressToOpen() {
         // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, no a11y enabled
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
                 .thenReturn(true);
         when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
         when(mAccessibilityManager.isEnabled()).thenReturn(false);
@@ -1262,7 +1268,7 @@
     public void coEx_faceSuccess_touchExplorationEnabled_showsFaceUnlockedSwipeToOpen() {
         // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y enabled
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
                 .thenReturn(true);
         when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
         when(mAccessibilityManager.isEnabled()).thenReturn(true);
@@ -1286,7 +1292,7 @@
     public void coEx_faceSuccess_a11yEnabled_showsFaceUnlockedSwipeToOpen() {
         // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y is enabled
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
                 .thenReturn(true);
         when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
         when(mAccessibilityManager.isEnabled()).thenReturn(true);
@@ -1309,7 +1315,7 @@
     public void faceOnly_faceSuccess_showsFaceUnlockedSwipeToOpen() {
         // GIVEN bouncer isn't showing, can skip bouncer, no udfps supported
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
                 .thenReturn(true);
         when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
         createController();
@@ -1331,7 +1337,7 @@
     public void udfpsOnly_a11yEnabled_showsSwipeToOpen() {
         // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y is enabled
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
                 .thenReturn(true);
         when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
         when(mAccessibilityManager.isEnabled()).thenReturn(true);
@@ -1351,7 +1357,7 @@
     public void udfpsOnly_showsPressToOpen() {
         // GIVEN bouncer isn't showing, udfps is supported, a11y is NOT enabled, can skip bouncer
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
                 .thenReturn(true);
         when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
         when(mAccessibilityManager.isEnabled()).thenReturn(false);
@@ -1372,7 +1378,7 @@
         // GIVEN bouncer isn't showing, can skip bouncer, no security (udfps isn't supported,
         // face wasn't authenticated)
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
                 .thenReturn(true);
         when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
         createController();
@@ -1390,7 +1396,7 @@
     public void cannotSkipBouncer_showSwipeToUnlockHint() {
         // GIVEN bouncer isn't showing and cannot skip bouncer
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
                 .thenReturn(false);
         createController();
         mController.setVisible(true);
@@ -1746,10 +1752,14 @@
 
     private void setupFingerprintUnlockPossible(boolean possible) {
         when(mKeyguardUpdateMonitor
-                .getCachedIsUnlockWithFingerprintPossible(KeyguardUpdateMonitor.getCurrentUser()))
+                .getCachedIsUnlockWithFingerprintPossible(getCurrentUser()))
                 .thenReturn(possible);
     }
 
+    private int getCurrentUser() {
+        return mCurrentUserId;
+    }
+
     private void onFaceLockoutError(String errMsg) {
         mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_LOCKOUT_PERMANENT,
                 errMsg,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 542b688..934e1c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -25,7 +25,6 @@
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.DataActivityListener
 import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
 import android.telephony.TelephonyManager
@@ -68,6 +67,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.telephonyDisplayInfo
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -392,13 +392,17 @@
             val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
-            val type = NETWORK_TYPE_UNKNOWN
-            val expected = UnknownNetworkType
-            val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+            val ti =
+                telephonyDisplayInfo(
+                    networkType = NETWORK_TYPE_UNKNOWN,
+                    overrideNetworkType = NETWORK_TYPE_UNKNOWN,
+                )
+
             callback.onDisplayInfoChanged(ti)
 
+            val expected = UnknownNetworkType
             assertThat(latest).isEqualTo(expected)
-            assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(type))
+            assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(NETWORK_TYPE_UNKNOWN))
 
             job.cancel()
         }
@@ -412,14 +416,10 @@
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
             val overrideType = OVERRIDE_NETWORK_TYPE_NONE
             val type = NETWORK_TYPE_LTE
-            val expected = DefaultNetworkType(mobileMappings.toIconKey(type))
-            val ti =
-                mock<TelephonyDisplayInfo>().also {
-                    whenever(it.overrideNetworkType).thenReturn(overrideType)
-                    whenever(it.networkType).thenReturn(type)
-                }
+            val ti = telephonyDisplayInfo(networkType = type, overrideNetworkType = overrideType)
             callback.onDisplayInfoChanged(ti)
 
+            val expected = DefaultNetworkType(mobileMappings.toIconKey(type))
             assertThat(latest).isEqualTo(expected)
 
             job.cancel()
@@ -433,14 +433,10 @@
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
             val type = OVERRIDE_NETWORK_TYPE_LTE_CA
-            val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
-            val ti =
-                mock<TelephonyDisplayInfo>().also {
-                    whenever(it.networkType).thenReturn(type)
-                    whenever(it.overrideNetworkType).thenReturn(type)
-                }
+            val ti = telephonyDisplayInfo(networkType = type, overrideNetworkType = type)
             callback.onDisplayInfoChanged(ti)
 
+            val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
             assertThat(latest).isEqualTo(expected)
 
             job.cancel()
@@ -455,14 +451,10 @@
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
             val unknown = NETWORK_TYPE_UNKNOWN
             val type = OVERRIDE_NETWORK_TYPE_LTE_CA
-            val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
-            val ti =
-                mock<TelephonyDisplayInfo>().also {
-                    whenever(it.networkType).thenReturn(unknown)
-                    whenever(it.overrideNetworkType).thenReturn(type)
-                }
+            val ti = telephonyDisplayInfo(unknown, type)
             callback.onDisplayInfoChanged(ti)
 
+            val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
             assertThat(latest).isEqualTo(expected)
 
             job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index bbf04ed2..9da9ff7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -64,10 +64,14 @@
  *
  * Kind of like an interaction test case build just for [TelephonyCallback]
  *
- * The list of telephony callbacks we use is: [TelephonyCallback.CarrierNetworkListener]
- * [TelephonyCallback.DataActivityListener] [TelephonyCallback.DataConnectionStateListener]
- * [TelephonyCallback.DataEnabledListener] [TelephonyCallback.DisplayInfoListener]
- * [TelephonyCallback.ServiceStateListener] [TelephonyCallback.SignalStrengthsListener]
+ * The list of telephony callbacks we use is:
+ * - [TelephonyCallback.CarrierNetworkListener]
+ * - [TelephonyCallback.DataActivityListener]
+ * - [TelephonyCallback.DataConnectionStateListener]
+ * - [TelephonyCallback.DataEnabledListener]
+ * - [TelephonyCallback.DisplayInfoListener]
+ * - [TelephonyCallback.ServiceStateListener]
+ * - [TelephonyCallback.SignalStrengthsListener]
  *
  * Because each of these callbacks comes in on the same callbackFlow, collecting on a field backed
  * by only a single callback can immediately create backpressure on the other fields related to a
@@ -201,7 +205,6 @@
                 200 /* unused */
             )
 
-            // Send a bunch of events that we don't care about, to overrun the replay buffer
             flipActivity(100, activityCallback)
 
             val connectionJob = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -225,7 +228,6 @@
 
             enabledCallback.onDataEnabledChanged(true, 1 /* unused */)
 
-            // Send a bunch of events that we don't care about, to overrun the replay buffer
             flipActivity(100, activityCallback)
 
             val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
@@ -252,7 +254,6 @@
             val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
             displayInfoCallback.onDisplayInfoChanged(ti)
 
-            // Send a bunch of events that we don't care about, to overrun the replay buffer
             flipActivity(100, activityCallback)
 
             val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
index d07b96f..cf815c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
@@ -19,6 +19,7 @@
 import android.telephony.CellSignalStrengthCdma
 import android.telephony.SignalStrength
 import android.telephony.TelephonyCallback
+import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -48,6 +49,12 @@
         return signalStrength
     }
 
+    fun telephonyDisplayInfo(networkType: Int, overrideNetworkType: Int) =
+        mock<TelephonyDisplayInfo>().also {
+            whenever(it.networkType).thenReturn(networkType)
+            whenever(it.overrideNetworkType).thenReturn(overrideNetworkType)
+        }
+
     inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
         val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
         assertThat(cbs.size).isEqualTo(1)
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index ee806a6..1298f63 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -2076,6 +2076,7 @@
         ret.outputId.id = output.getId();
         ret.physicalCameraId = output.getPhysicalCameraId();
         ret.surfaceGroupId = output.getSurfaceGroupId();
+        ret.isMultiResolutionOutput = false;
         if (output instanceof SurfaceOutputConfigImpl) {
             SurfaceOutputConfigImpl surfaceConfig = (SurfaceOutputConfigImpl) output;
             ret.type = CameraOutputConfig.TYPE_SURFACE;
@@ -2095,6 +2096,7 @@
             ret.type = CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER;
             ret.imageFormat = multiResReaderConfig.getImageFormat();
             ret.capacity = multiResReaderConfig.getMaxImages();
+            ret.isMultiResolutionOutput = true;
         } else {
             throw new IllegalStateException("Unknown output config type!");
         }
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 7d1de40..ca743cb 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -26,6 +26,12 @@
 import static android.view.autofill.AutofillManager.COMMIT_REASON_VIEW_COMMITTED;
 
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_RESULT_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__AUTHENTICATION_TYPE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__DATASET_AUTHENTICATION;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__FULL_AUTHENTICATION;
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__DIALOG;
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU;
@@ -84,6 +90,32 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface NotShownReason {}
 
+    /**
+     * Reasons why presentation was not shown. These are wrappers around
+     * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.AuthenticationType}.
+     */
+    @IntDef(prefix = {"AUTHENTICATION_TYPE"}, value = {
+            AUTHENTICATION_TYPE_UNKNOWN,
+            AUTHENTICATION_TYPE_DATASET_AUTHENTICATION,
+            AUTHENTICATION_TYPE_FULL_AUTHENTICATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AuthenticationType {
+    }
+
+    /**
+     * Reasons why presentation was not shown. These are wrappers around
+     * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.AuthenticationResult}.
+     */
+    @IntDef(prefix = {"AUTHENTICATION_RESULT"}, value = {
+            AUTHENTICATION_RESULT_UNKNOWN,
+            AUTHENTICATION_RESULT_SUCCESS,
+            AUTHENTICATION_RESULT_FAILURE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AuthenticationResult {
+    }
+
     public static final int NOT_SHOWN_REASON_ANY_SHOWN =
             AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN;
     public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED =
@@ -105,6 +137,20 @@
     public static final int NOT_SHOWN_REASON_UNKNOWN =
             AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON;
 
+    public static final int AUTHENTICATION_TYPE_UNKNOWN =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__AUTHENTICATION_TYPE_UNKNOWN;
+    public static final int AUTHENTICATION_TYPE_DATASET_AUTHENTICATION =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__DATASET_AUTHENTICATION;
+    public static final int AUTHENTICATION_TYPE_FULL_AUTHENTICATION =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__FULL_AUTHENTICATION;
+
+    public static final int AUTHENTICATION_RESULT_UNKNOWN =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_RESULT_UNKNOWN;
+    public static final int AUTHENTICATION_RESULT_SUCCESS =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_SUCCESS;
+    public static final int AUTHENTICATION_RESULT_FAILURE =
+            AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_FAILURE;
+
     private final int mSessionId;
     private Optional<PresentationStatsEventInternal> mEventInternal;
 
@@ -146,7 +192,7 @@
     }
 
     public void maybeSetAvailableCount(@Nullable List<Dataset> datasetList,
-                                       AutofillId currentViewId) {
+            AutofillId currentViewId) {
         mEventInternal.ifPresent(event -> {
             int availableCount = getDatasetCountForAutofillId(datasetList, currentViewId);
             event.mAvailableCount = availableCount;
@@ -155,7 +201,7 @@
     }
 
     public void maybeSetCountShown(@Nullable List<Dataset> datasetList,
-                                   AutofillId currentViewId) {
+            AutofillId currentViewId) {
         mEventInternal.ifPresent(event -> {
             int countShown = getDatasetCountForAutofillId(datasetList, currentViewId);
             event.mCountShown = countShown;
@@ -166,7 +212,7 @@
     }
 
     private static int getDatasetCountForAutofillId(@Nullable List<Dataset> datasetList,
-                                                    AutofillId currentViewId) {
+            AutofillId currentViewId) {
         int availableCount = 0;
         if (datasetList != null) {
             for (int i = 0; i < datasetList.size(); i++) {
@@ -293,6 +339,43 @@
         });
     }
 
+    /**
+     * Set authentication_type as long as mEventInternal presents.
+     */
+    public void maybeSetAuthenticationType(@AuthenticationType int val) {
+        mEventInternal.ifPresent(event -> {
+            event.mAuthenticationType = val;
+        });
+    }
+
+    /**
+     * Set authentication_result as long as mEventInternal presents.
+     */
+    public void maybeSetAuthenticationResult(@AuthenticationResult int val) {
+        mEventInternal.ifPresent(event -> {
+            event.mAuthenticationResult = val;
+        });
+    }
+
+    /**
+     * Set latency_authentication_ui_display_millis as long as mEventInternal presents.
+     */
+    public void maybeSetLatencyAuthenticationUiDisplayMillis(int val) {
+        mEventInternal.ifPresent(event -> {
+            event.mLatencyAuthenticationUiDisplayMillis = val;
+        });
+    }
+
+    /**
+     * Set latency_dataset_display_millis as long as mEventInternal presents.
+     */
+    public void maybeSetLatencyDatasetDisplayMillis(int val) {
+        mEventInternal.ifPresent(event -> {
+            event.mLatencyDatasetDisplayMillis = val;
+        });
+    }
+
+
     public void logAndEndEvent() {
         if (!mEventInternal.isPresent()) {
             Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -322,7 +405,12 @@
                     + " mSelectedDatasetId=" + event.mSelectedDatasetId
                     + " mDialogDismissed=" + event.mDialogDismissed
                     + " mNegativeCtaButtonClicked=" + event.mNegativeCtaButtonClicked
-                    + " mPositiveCtaButtonClicked=" + event.mPositiveCtaButtonClicked);
+                    + " mPositiveCtaButtonClicked=" + event.mPositiveCtaButtonClicked
+                    + " mAuthenticationType=" + event.mAuthenticationType
+                    + " mAuthenticationResult=" + event.mAuthenticationResult
+                    + " mLatencyAuthenticationUiDisplayMillis="
+                    + event.mLatencyAuthenticationUiDisplayMillis
+                    + " mLatencyDatasetDisplayMillis=" + event.mLatencyDatasetDisplayMillis);
         }
 
         // TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -351,11 +439,15 @@
                 event.mSelectedDatasetId,
                 event.mDialogDismissed,
                 event.mNegativeCtaButtonClicked,
-                event.mPositiveCtaButtonClicked);
+                event.mPositiveCtaButtonClicked,
+                event.mAuthenticationType,
+                event.mAuthenticationResult,
+                event.mLatencyAuthenticationUiDisplayMillis,
+                event.mLatencyDatasetDisplayMillis);
         mEventInternal = Optional.empty();
     }
 
-    private final class PresentationStatsEventInternal {
+    private static final class PresentationStatsEventInternal {
         int mRequestId;
         @NotShownReason int mNoPresentationReason = NOT_SHOWN_REASON_UNKNOWN;
         boolean mIsDatasetAvailable;
@@ -376,6 +468,10 @@
         boolean mDialogDismissed = false;
         boolean mNegativeCtaButtonClicked = false;
         boolean mPositiveCtaButtonClicked = false;
+        int mAuthenticationType = AUTHENTICATION_TYPE_UNKNOWN;
+        int mAuthenticationResult = AUTHENTICATION_RESULT_UNKNOWN;
+        int mLatencyAuthenticationUiDisplayMillis = -1;
+        int mLatencyDatasetDisplayMillis = -1;
 
         PresentationStatsEventInternal() {}
     }
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index 742c94a..d6f1348 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -343,10 +343,8 @@
             // Check if user is associated with the subscription
             if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
                     Binder.getCallingUserHandle())) {
-                if (TelephonyUtils.isUidForeground(mContext, Binder.getCallingUid())) {
-                    TelephonyUtils.showErrorIfSubscriptionAssociatedWithManagedProfile(mContext,
-                            subId);
-                }
+                TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext,
+                        subId, Binder.getCallingUid(), callingPkg);
                 return;
             }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4a0a228..461103e 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -602,7 +602,7 @@
                     try {
                         final ServiceRecord.StartItem si = r.pendingStarts.get(0);
                         startServiceInnerLocked(this, si.intent, r, false, true, si.callingId,
-                                si.mCallingProcessName, r.startRequested);
+                                si.mCallingProcessName, r.startRequested, si.mCallingPackageName);
                     } catch (TransactionTooLargeException e) {
                         // Ignore, nobody upstack cares.
                     }
@@ -969,7 +969,7 @@
                 startServiceInnerLocked(r, service, callingUid, callingPid,
                         getCallingProcessNameLocked(callingUid, callingPid, callingPackage),
                         fgRequired, callerFg,
-                        backgroundStartPrivileges);
+                        backgroundStartPrivileges, callingPackage);
         if (res.aliasComponent != null
                 && !realResult.getPackageName().startsWith("!")
                 && !realResult.getPackageName().startsWith("?")) {
@@ -990,7 +990,7 @@
     private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
             int callingUid, int callingPid, String callingProcessName, boolean fgRequired,
             boolean callerFg,
-            BackgroundStartPrivileges backgroundStartPrivileges)
+            BackgroundStartPrivileges backgroundStartPrivileges, String callingPackage)
             throws TransactionTooLargeException {
         NeededUriGrants neededGrants = mAm.mUgmInternal.checkGrantUriPermissionFromIntent(
                 service, callingUid, r.packageName, r.userId);
@@ -1003,7 +1003,7 @@
         r.delayedStop = false;
         r.fgRequired = fgRequired;
         r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
-                service, neededGrants, callingUid, callingProcessName));
+                service, neededGrants, callingUid, callingProcessName, callingPackage));
 
         if (fgRequired) {
             // We are now effectively running a foreground service.
@@ -1088,7 +1088,7 @@
             r.allowBgActivityStartsOnServiceStart(backgroundStartPrivileges);
         }
         ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting,
-                callingUid, callingProcessName, wasStartRequested);
+                callingUid, callingProcessName, wasStartRequested, callingPackage);
         return cmp;
     }
 
@@ -1241,7 +1241,7 @@
                         try {
                             startServiceInnerLocked(s, serviceIntent, callingUid, callingPid,
                                     callingProcessName, fgRequired, callerFg,
-                                    backgroundStartPrivileges);
+                                    backgroundStartPrivileges, callingPackage);
                         } catch (TransactionTooLargeException e) {
                             /* ignore - local call */
                         }
@@ -1287,7 +1287,7 @@
 
     ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
             boolean callerFg, boolean addToStarting, int callingUid, String callingProcessName,
-            boolean wasStartRequested) throws TransactionTooLargeException {
+            boolean wasStartRequested, String callingPackage) throws TransactionTooLargeException {
         synchronized (mAm.mProcessStats.mLock) {
             final ServiceState stracker = r.getTracker();
             if (stracker != null) {
@@ -1328,7 +1328,9 @@
                 : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM),
                 getShortProcessNameForStats(callingUid, callingProcessName),
                 getShortServiceNameForStats(r),
-                packageState);
+                packageState,
+                packageName,
+                callingPackage);
 
         if (r.startRequested && addToStarting) {
             boolean first = smap.mStartingBackground.size() == 0;
@@ -3661,7 +3663,9 @@
                     : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM),
                     getShortProcessNameForStats(callingUid, callerApp.processName),
                     getShortServiceNameForStats(s),
-                    packageState);
+                    packageState,
+                    s.packageName,
+                    callerApp.info.packageName);
 
             if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b
                     + ": received=" + b.intent.received
@@ -5253,7 +5257,7 @@
         // be called.
         if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
             r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
-                    null, null, 0, null));
+                    null, null, 0, null, null));
         }
 
         sendServiceArgsLocked(r, execInFg, true);
@@ -6247,7 +6251,7 @@
                     stopServiceLocked(sr, true);
                 } else {
                     sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
-                            sr.getLastStartId(), baseIntent, null, 0, null));
+                            sr.getLastStartId(), baseIntent, null, 0, null, 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.
@@ -7318,7 +7322,7 @@
         if (!r.mAllowWhileInUsePermissionInFgs
                 || (r.mAllowStartForeground == REASON_DENIED)) {
             final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
-                    callingPackage, callingPid, callingUid, r, backgroundStartPrivileges,
+                    callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges,
                     isBindService);
             if (!r.mAllowWhileInUsePermissionInFgs) {
                 r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
@@ -7345,7 +7349,7 @@
             return true;
         }
         final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
-                callingPackage, callingPid, callingUid, null /* serviceRecord */,
+                callingPackage, callingPid, callingUid, null /* targetProcess */,
                 BackgroundStartPrivileges.NONE, false);
         @ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundNoBindingCheckLocked(
                 allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */,
@@ -7362,13 +7366,14 @@
     /**
      * Should allow while-in-use permissions in FGS or not.
      * A typical BG started FGS is not allowed to have while-in-use permissions.
+     *
      * @param callingPackage caller app's package name.
-     * @param callingUid caller app's uid.
-     * @param targetService the service to start.
+     * @param callingUid     caller app's uid.
+     * @param targetProcess  the process of the service to start.
      * @return {@link ReasonCode}
      */
     private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
-            int callingPid, int callingUid, @Nullable ServiceRecord targetService,
+            int callingPid, int callingUid, @Nullable ProcessRecord targetProcess,
             BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
         int ret = REASON_DENIED;
 
@@ -7436,8 +7441,8 @@
         }
 
         if (ret == REASON_DENIED) {
-            if (targetService != null && targetService.app != null) {
-                ActiveInstrumentation instr = targetService.app.getActiveInstrumentation();
+            if (targetProcess != null) {
+                ActiveInstrumentation instr = targetProcess.getActiveInstrumentation();
                 if (instr != null && instr.mHasBackgroundActivityStartsPermission) {
                     ret = REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
                 }
@@ -7522,7 +7527,7 @@
                                 final @ReasonCode int allowWhileInUse2 =
                                         shouldAllowFgsWhileInUsePermissionLocked(
                                                 clientPackageName,
-                                                clientPid, clientUid, null /* serviceRecord */,
+                                                clientPid, clientUid, null /* targetProcess */,
                                                 BackgroundStartPrivileges.NONE, false);
                                 final @ReasonCode int allowStartFgs =
                                         shouldAllowFgsStartForegroundNoBindingCheckLocked(
@@ -7942,11 +7947,18 @@
     boolean canAllowWhileInUsePermissionInFgsLocked(int callingPid, int callingUid,
             String callingPackage) {
         return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
-                /* targetService */ null,
+                /* targetProcess */ null,
                 BackgroundStartPrivileges.NONE, false)
                 != REASON_DENIED;
     }
 
+    boolean canAllowWhileInUsePermissionInFgsLocked(int callingPid, int callingUid,
+            String callingPackage, @Nullable ProcessRecord targetProcess,
+            @NonNull BackgroundStartPrivileges backgroundStartPrivileges) {
+        return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
+                targetProcess, backgroundStartPrivileges, false) != REASON_DENIED;
+    }
+
     /**
      * Checks if a given packageName belongs to a given uid.
      * @param packageName the package of the caller
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 4fa28a1..82c4796a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -118,6 +118,8 @@
     static final String KEY_PROCESS_CRASH_COUNT_LIMIT = "process_crash_count_limit";
     static final String KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION = "boot_time_temp_allowlist_duration";
     static final String KEY_FG_TO_BG_FGS_GRACE_DURATION = "fg_to_bg_fgs_grace_duration";
+    static final String KEY_VISIBLE_TO_INVISIBLE_UIJ_SCHEDULE_GRACE_DURATION =
+            "vis_to_invis_uij_schedule_grace_duration";
     static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout";
     static final String KEY_FGS_ATOM_SAMPLE_RATE = "fgs_atom_sample_rate";
     static final String KEY_FGS_START_ALLOWED_LOG_SAMPLE_RATE = "fgs_start_allowed_log_sample_rate";
@@ -191,6 +193,8 @@
     private static final int DEFAULT_PROCESS_CRASH_COUNT_LIMIT = 12;
     private static final int DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION = 20 * 1000;
     private static final long DEFAULT_FG_TO_BG_FGS_GRACE_DURATION = 5 * 1000;
+    private static final long DEFAULT_VISIBLE_TO_INVISIBLE_UIJ_SCHEDULE_GRACE_DURATION =
+            DEFAULT_FG_TO_BG_FGS_GRACE_DURATION;
     private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000;
     private static final float DEFAULT_FGS_ATOM_SAMPLE_RATE = 1; // 100 %
     private static final float DEFAULT_FGS_START_ALLOWED_LOG_SAMPLE_RATE = 0.25f; // 25%
@@ -680,6 +684,15 @@
     volatile long mFgToBgFgsGraceDuration = DEFAULT_FG_TO_BG_FGS_GRACE_DURATION;
 
     /**
+     * The grace period in milliseconds to allow a process to schedule a
+     * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}
+     * after switching from visible to a non-visible state.
+     * Currently it's only applicable to its activities.
+     */
+    volatile long mVisibleToInvisibleUijScheduleGraceDurationMs =
+            DEFAULT_VISIBLE_TO_INVISIBLE_UIJ_SCHEDULE_GRACE_DURATION;
+
+    /**
      * When service started from background, before the timeout it can be promoted to FGS by calling
      * Service.startForeground().
      */
@@ -1123,6 +1136,9 @@
                             case KEY_FG_TO_BG_FGS_GRACE_DURATION:
                                 updateFgToBgFgsGraceDuration();
                                 break;
+                            case KEY_VISIBLE_TO_INVISIBLE_UIJ_SCHEDULE_GRACE_DURATION:
+                                updateFgToBgFgsGraceDuration();
+                                break;
                             case KEY_FGS_START_FOREGROUND_TIMEOUT:
                                 updateFgsStartForegroundTimeout();
                                 break;
@@ -1598,6 +1614,13 @@
                 DEFAULT_FG_TO_BG_FGS_GRACE_DURATION);
     }
 
+    private void updateVisibleToInvisibleUijScheduleGraceDuration() {
+        mVisibleToInvisibleUijScheduleGraceDurationMs = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_VISIBLE_TO_INVISIBLE_UIJ_SCHEDULE_GRACE_DURATION,
+                DEFAULT_VISIBLE_TO_INVISIBLE_UIJ_SCHEDULE_GRACE_DURATION);
+    }
+
     private void updateFgsStartForegroundTimeout() {
         mFgsStartForegroundTimeoutMs = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9e5acf7..415c859 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -23,6 +23,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
 import static android.Manifest.permission.MANAGE_USERS;
+import static android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND;
 import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
 import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
 import static android.app.ActivityManager.INSTR_FLAG_ALWAYS_CHECK_SIGNATURE;
@@ -32,6 +33,7 @@
 import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART;
 import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
 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;
@@ -60,9 +62,22 @@
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
 import static android.os.IServiceManager.DUMP_FLAG_PROTO;
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
+import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
+import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_BTOP;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_TOP;
+import static android.os.PowerExemptionManager.REASON_START_ACTIVITY_FLAG;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
 import static android.os.PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
+import static android.os.PowerExemptionManager.REASON_UID_VISIBLE;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
+import static android.os.PowerExemptionManager.getReasonCodeFromProcState;
 import static android.os.Process.BLUETOOTH_UID;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.INVALID_UID;
@@ -6541,6 +6556,143 @@
     }
 
     /**
+     * Returns true if the reasonCode is included in the base set of reasons an app may be
+     * allowed to schedule a
+     * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}.
+     * This is a shortcut and <b>DOES NOT</b> include all reasons.
+     * Use {@link #canScheduleUserInitiatedJobs(int, int, String)} to cover all cases.
+     */
+    private boolean doesReasonCodeAllowSchedulingUserInitiatedJobs(int reasonCode) {
+        switch (reasonCode) {
+            case REASON_PROC_STATE_PERSISTENT:
+            case REASON_PROC_STATE_PERSISTENT_UI:
+            case REASON_PROC_STATE_TOP:
+            case REASON_PROC_STATE_BTOP:
+            case REASON_UID_VISIBLE:
+            case REASON_SYSTEM_UID:
+            case REASON_START_ACTIVITY_FLAG:
+            case REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD:
+            case REASON_SYSTEM_ALERT_WINDOW_PERMISSION:
+            case REASON_COMPANION_DEVICE_MANAGER:
+            case REASON_BACKGROUND_ACTIVITY_PERMISSION:
+            case REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION:
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the ProcessRecord has some conditions that allow the app to schedule a
+     * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}.
+     * This is a shortcut and <b>DOES NOT</b> include all reasons.
+     * Use {@link #canScheduleUserInitiatedJobs(int, int, String)} to cover all cases.
+     */
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    private boolean isProcessInStateToScheduleUserInitiatedJobsLocked(
+            @Nullable ProcessRecord pr, long nowElapsed) {
+        if (pr == null) {
+            return false;
+        }
+
+        final BackgroundStartPrivileges backgroundStartPrivileges =
+                pr.getBackgroundStartPrivileges();
+        // Is the allow activity background start flag on?
+        if (backgroundStartPrivileges.allowsBackgroundActivityStarts()) {
+            // REASON_START_ACTIVITY_FLAG;
+            return true;
+        }
+
+        final ProcessStateRecord state = pr.mState;
+        final int procstate = state.getCurProcState();
+        if (procstate <= PROCESS_STATE_BOUND_TOP) {
+            if (doesReasonCodeAllowSchedulingUserInitiatedJobs(
+                    getReasonCodeFromProcState(procstate))) {
+                return true;
+            }
+        }
+
+        final long lastInvisibleTime = state.getLastInvisibleTime();
+        if (lastInvisibleTime > 0 && lastInvisibleTime < Long.MAX_VALUE) {
+            final long timeSinceVisibleMs = nowElapsed - lastInvisibleTime;
+            if (timeSinceVisibleMs < mConstants.mVisibleToInvisibleUijScheduleGraceDurationMs) {
+                // REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns whether the app in question is in a state where we allow scheduling a
+     * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}.
+     */
+    // TODO(262260570): log allow reason to an atom
+    private boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
+        synchronized (this) {
+            final ProcessRecord processRecord;
+            synchronized (mPidsSelfLocked) {
+                processRecord = mPidsSelfLocked.get(pid);
+            }
+
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            final BackgroundStartPrivileges backgroundStartPrivileges;
+            if (processRecord != null) {
+                if (isProcessInStateToScheduleUserInitiatedJobsLocked(processRecord, nowElapsed)) {
+                    return true;
+                }
+                backgroundStartPrivileges = processRecord.getBackgroundStartPrivileges();
+            } else {
+                backgroundStartPrivileges = getBackgroundStartPrivileges(uid);
+            }
+            // Is the allow activity background start flag on?
+            if (backgroundStartPrivileges.allowsBackgroundActivityStarts()) {
+                // REASON_START_ACTIVITY_FLAG;
+                return true;
+            }
+
+            // We allow scheduling a user-initiated job when the app is in the TOP or a
+            // Background Activity Launch approved state. These are cases that indicate the user
+            // has interacted with the app and therefore it is reasonable to believe the app may
+            // attempt to schedule a user-initiated job in response to the user interaction.
+            // As of Android UDC, the conditions required to grant a while-in-use permission
+            // covers the majority of those cases, and so we piggyback on that logic as the base.
+            // Missing cases are added after.
+            if (mServices.canAllowWhileInUsePermissionInFgsLocked(
+                    pid, uid, pkgName, processRecord, backgroundStartPrivileges)) {
+                return true;
+            }
+
+            final UidRecord uidRecord = mProcessList.getUidRecordLOSP(uid);
+            if (uidRecord != null) {
+                for (int i = uidRecord.getNumOfProcs() - 1; i >= 0; --i) {
+                    ProcessRecord pr = uidRecord.getProcessRecordByIndex(i);
+                    if (isProcessInStateToScheduleUserInitiatedJobsLocked(pr, nowElapsed)) {
+                        return true;
+                    }
+                }
+            }
+
+            if (mAtmInternal.hasSystemAlertWindowPermission(uid, pid, pkgName)) {
+                // REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+                return true;
+            }
+
+            final int userId = UserHandle.getUserId(uid);
+            final boolean isCompanionApp = mInternal.isAssociatedCompanionApp(userId, uid);
+            if (isCompanionApp) {
+                if (checkPermission(REQUEST_COMPANION_RUN_IN_BACKGROUND, pid, uid)
+                        == PERMISSION_GRANTED) {
+                    // REASON_COMPANION_DEVICE_MANAGER;
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
      * @return allowlist tag for a uid from mPendingTempAllowlist, null if not currently on
      * the allowlist
      */
@@ -18156,6 +18308,11 @@
             return ActivityManagerService.this.getBackgroundStartPrivileges(uid);
         }
 
+        @Override
+        public boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
+            return ActivityManagerService.this.canScheduleUserInitiatedJobs(uid, pid, pkgName);
+        }
+
         public void reportCurKeyguardUsageEvent(boolean keyguardShowing) {
             ActivityManagerService.this.reportGlobalUsageEvent(keyguardShowing
                     ? UsageEvents.Event.KEYGUARD_SHOWN
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 4d46963..33d4004 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -242,7 +242,7 @@
      */
     public boolean CORE_DEFER_UNTIL_ACTIVE = DEFAULT_CORE_DEFER_UNTIL_ACTIVE;
     private static final String KEY_CORE_DEFER_UNTIL_ACTIVE = "bcast_core_defer_until_active";
-    private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = true;
+    private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = false;
 
     // Settings override tracking for this instance
     private String mSettingsKey;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index c085706..fcddff0 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -601,7 +601,9 @@
                     r.dispatchTime - r.enqueueTime,
                     r.receiverTime - r.dispatchTime,
                     finishTime - r.receiverTime,
-                    packageState);
+                    packageState,
+                    r.curApp.info.packageName,
+                    r.callerPackage);
         }
         if (state == BroadcastRecord.IDLE) {
             Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
@@ -780,7 +782,8 @@
                     BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
                     BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
                     dispatchDelay, receiveDelay, 0 /* finish_delay */,
-                    SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL);
+                    SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
+                    app != null ? app.info.packageName : null, callingPackage);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index ff4f9b9..1f0b162 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1122,7 +1122,7 @@
             }
 
             r.terminalCount++;
-            notifyFinishReceiver(queue, r, index, receiver);
+            notifyFinishReceiver(queue, app, r, index, receiver);
             checkFinished = true;
         }
         // When entire ordered broadcast finished, deliver final result
@@ -1595,9 +1595,10 @@
      * typically for internal bookkeeping.
      */
     private void notifyFinishReceiver(@Nullable BroadcastProcessQueue queue,
-            @NonNull BroadcastRecord r, int index, @NonNull Object receiver) {
+            @Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index,
+            @NonNull Object receiver) {
         if (r.wasDeliveryAttempted(index)) {
-            logBroadcastDeliveryEventReported(queue, r, index, receiver);
+            logBroadcastDeliveryEventReported(queue, app, r, index, receiver);
         }
 
         final boolean recordFinished = (r.terminalCount == r.receivers.size());
@@ -1607,7 +1608,8 @@
     }
 
     private void logBroadcastDeliveryEventReported(@Nullable BroadcastProcessQueue queue,
-            @NonNull BroadcastRecord r, int index, @NonNull Object receiver) {
+            @Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index,
+            @NonNull Object receiver) {
         // Report statistics for each individual receiver
         final int uid = getReceiverUid(receiver);
         final int senderUid = (r.callingUid == -1) ? Process.SYSTEM_UID : r.callingUid;
@@ -1633,7 +1635,8 @@
                     ? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
                     : SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
             FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
-                    receiverType, type, dispatchDelay, receiveDelay, finishDelay, packageState);
+                    receiverType, type, dispatchDelay, receiveDelay, finishDelay, packageState,
+                    app != null ? app.info.packageName : null, r.callerPackage);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ComponentAliasResolver.java b/services/core/java/com/android/server/am/ComponentAliasResolver.java
index 01735a7..f9eaf02 100644
--- a/services/core/java/com/android/server/am/ComponentAliasResolver.java
+++ b/services/core/java/com/android/server/am/ComponentAliasResolver.java
@@ -30,7 +30,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.os.Binder;
-import android.os.Build;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -43,7 +42,6 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
-import com.android.server.compat.CompatChange;
 import com.android.server.compat.PlatformCompat;
 
 import java.io.PrintWriter;
@@ -52,26 +50,11 @@
 import java.util.function.Supplier;
 
 /**
- * Manages and handles component aliases, which is an experimental feature.
+ * @deprecated This feature is no longer used. Delete this class.
  *
- * NOTE: THIS CLASS IS PURELY EXPERIMENTAL AND WILL BE REMOVED IN FUTURE ANDROID VERSIONS.
- * DO NOT USE IT.
- *
- * "Component alias" allows an android manifest component (for now only broadcasts and services)
- * to be defined in one android package while having the implementation in a different package.
- *
- * When/if this becomes a real feature, it will be most likely implemented very differently,
- * which is why this shouldn't be used.
- *
- * For now, because this is an experimental feature to evaluate feasibility, the implementation is
- * "quick & dirty". For example, to define aliases, we use a regular intent filter and meta-data
- * in the manifest, instead of adding proper tags/attributes to AndroidManifest.xml.
- *
- * This feature is disabled by default.
- *
- * Also, for now, aliases can be defined across packages with different certificates, but
- * in a final version this will most likely be tightened.
+ * Also delete Intnt.(set|get)OriginalIntent.
  */
+@Deprecated
 public class ComponentAliasResolver {
     private static final String TAG = "ComponentAliasResolver";
     private static final boolean DEBUG = true;
@@ -149,11 +132,6 @@
         }
     };
 
-    private final CompatChange.ChangeListener mCompatChangeListener = (packageName) -> {
-        if (DEBUG) Slog.d(TAG, "USE_EXPERIMENTAL_COMPONENT_ALIAS changed.");
-        BackgroundThread.getHandler().post(this::refresh);
-    };
-
     /**
      * Call this on systemRead().
      */
@@ -161,8 +139,6 @@
         synchronized (mLock) {
             mPlatformCompat = (PlatformCompat) ServiceManager.getService(
                     Context.PLATFORM_COMPAT_SERVICE);
-            mPlatformCompat.registerListener(USE_EXPERIMENTAL_COMPONENT_ALIAS,
-                    mCompatChangeListener);
         }
         if (DEBUG) Slog.d(TAG, "Compat listener set.");
         update(enabledByDeviceConfig, overrides);
@@ -176,10 +152,8 @@
             if (mPlatformCompat == null) {
                 return; // System not ready.
             }
-            final boolean enabled = Build.isDebuggable()
-                    && (enabledByDeviceConfig
-                        || mPlatformCompat.isChangeEnabledByPackageName(
-                        USE_EXPERIMENTAL_COMPONENT_ALIAS, "android", UserHandle.USER_SYSTEM));
+            // Never enable it.
+            final boolean enabled = false;
             if (enabled != mEnabled) {
                 Slog.i(TAG, (enabled ? "Enabling" : "Disabling") + " component aliases...");
                 FgThread.getHandler().post(() -> {
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 48df1494..a1fcd42 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -263,7 +263,8 @@
                             PROVIDER_ACQUISITION_EVENT_REPORTED,
                             r.uid, callingUid,
                             PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
-                            PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL);
+                            PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
+                            cpi.packageName, callingPackage);
                     return holder;
                 }
 
@@ -334,7 +335,8 @@
                                 PROVIDER_ACQUISITION_EVENT_REPORTED,
                                 cpr.proc.uid, callingUid,
                                 PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
-                                PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL);
+                                PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
+                                cpi.packageName, callingPackage);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(origId);
@@ -511,7 +513,8 @@
                                     PROVIDER_ACQUISITION_EVENT_REPORTED,
                                     proc.uid, callingUid,
                                     PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
-                                    PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL);
+                                    PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
+                                    cpi.packageName, callingPackage);
                         } else {
                             final int packageState =
                                     ((cpr.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0)
@@ -536,7 +539,7 @@
                                     PROVIDER_ACQUISITION_EVENT_REPORTED,
                                     proc.uid, callingUid,
                                     PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
-                                    packageState);
+                                    packageState, cpi.packageName, callingPackage);
                         }
                         cpr.launchingApp = proc;
                         mLaunchingProviders.add(cpr);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 663121e..4defdc6 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -249,6 +249,7 @@
         final String mCallingProcessName;
         final Intent intent;
         final NeededUriGrants neededGrants;
+        final @Nullable String mCallingPackageName;
         long deliveredTime;
         int deliveryCount;
         int doneExecutingCount;
@@ -258,7 +259,7 @@
 
         StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id,
                 Intent _intent, NeededUriGrants _neededGrants, int _callingId,
-                String callingProcessName) {
+                String callingProcessName, @Nullable String callingPackageName) {
             sr = _sr;
             taskRemoved = _taskRemoved;
             id = _id;
@@ -266,6 +267,7 @@
             neededGrants = _neededGrants;
             callingId = _callingId;
             mCallingProcessName = callingProcessName;
+            mCallingPackageName = callingPackageName;
         }
 
         UriPermissionOwner getUriPermissionsLocked() {
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index e38e8c1..e39ac2b 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -311,6 +311,11 @@
     }
 
     @GuardedBy(anyOf = {"mService", "mProcLock"})
+    ProcessRecord getProcessRecordByIndex(int idx) {
+        return mProcRecords.valueAt(idx);
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     ProcessRecord getProcessInPackage(String packageName) {
         for (int i = mProcRecords.size() - 1; i >= 0; i--) {
             final ProcessRecord app = mProcRecords.valueAt(i);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 127a9d8b..16bf355 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -170,6 +170,7 @@
 import android.provider.Settings.System;
 import android.service.notification.ZenModeConfig;
 import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
@@ -184,6 +185,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
 
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -393,6 +395,7 @@
     private static final int MSG_NO_LOG_FOR_PLAYER_I = 51;
     private static final int MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES = 52;
     private static final int MSG_LOWER_VOLUME_TO_RS1 = 53;
+    private static final int MSG_CONFIGURATION_CHANGED = 54;
 
     /** Messages handled by the {@link SoundDoseHelper}. */
     /*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000;
@@ -1046,9 +1049,14 @@
 
         mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
 
-        final boolean headTrackingDefault = mContext.getResources().getBoolean(
+        final boolean binauralEnabledDefault = SystemProperties.getBoolean(
+                "ro.audio.spatializer_binaural_enabled_default", true);
+        final boolean transauralEnabledDefault = SystemProperties.getBoolean(
+                "ro.audio.spatializer_transaural_enabled_default", true);
+        final boolean headTrackingEnabledDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
-        mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, headTrackingDefault);
+        mSpatializerHelper = new SpatializerHelper(this, mAudioSystem,
+                binauralEnabledDefault, transauralEnabledDefault, headTrackingEnabledDefault);
 
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
         mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
@@ -1214,17 +1222,6 @@
 
         updateAudioHalPids();
 
-        boolean cameraSoundForced = readCameraSoundForced();
-        mCameraSoundForced = new Boolean(cameraSoundForced);
-        sendMsg(mAudioHandler,
-                MSG_SET_FORCE_USE,
-                SENDMSG_QUEUE,
-                AudioSystem.FOR_SYSTEM,
-                cameraSoundForced ?
-                        AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
-                new String("AudioService ctor"),
-                0);
-
         mUseFixedVolume = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useFixedVolume);
 
@@ -1309,6 +1306,18 @@
      * Called by handling of MSG_INIT_STREAMS_VOLUMES
      */
     private void onInitStreamsAndVolumes() {
+        synchronized (mSettingsLock) {
+            mCameraSoundForced = readCameraSoundForced();
+            sendMsg(mAudioHandler,
+                    MSG_SET_FORCE_USE,
+                    SENDMSG_QUEUE,
+                    AudioSystem.FOR_SYSTEM,
+                    mCameraSoundForced
+                            ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
+                    new String("AudioService ctor"),
+                    0);
+        }
+
         createStreamStates();
 
         // must be called after createStreamStates() as it uses MUSIC volume as default if no
@@ -1344,8 +1353,19 @@
 
         // check on volume initialization
         checkVolumeRangeInitialization("AudioService()");
+
     }
 
+    private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener =
+            new SubscriptionManager.OnSubscriptionsChangedListener() {
+                @Override
+                public void onSubscriptionsChanged() {
+                    Log.i(TAG, "onSubscriptionsChanged()");
+                    sendMsg(mAudioHandler, MSG_CONFIGURATION_CHANGED, SENDMSG_REPLACE,
+                            0, 0, null, 0);
+                }
+            };
+
     /**
      * Initialize intent receives and settings observers for this service.
      * Must be called after createStreamStates() as the handling of some events
@@ -1383,6 +1403,13 @@
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
                 Context.RECEIVER_EXPORTED);
 
+        SubscriptionManager subscriptionManager = mContext.getSystemService(
+                SubscriptionManager.class);
+        if (subscriptionManager == null) {
+            Log.e(TAG, "initExternalEventReceivers cannot create SubscriptionManager!");
+        } else {
+            subscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener);
+        }
     }
 
     public void systemReady() {
@@ -3660,7 +3687,7 @@
             for (int stream = 0; stream < mStreamStates.length; stream++) {
                 VolumeStreamState vss = mStreamStates[stream];
                 if (streamAlias == mStreamVolumeAlias[stream] && vss.isMutable()) {
-                    if (!(readCameraSoundForced()
+                    if (!(mCameraSoundForced
                             && (vss.getStreamType()
                                     == AudioSystem.STREAM_SYSTEM_ENFORCED))) {
                         boolean changed = vss.mute(state, /* apply= */ false);
@@ -7237,7 +7264,7 @@
         super.setDeviceVolumeBehavior_enforcePermission();
         // verify arguments
         Objects.requireNonNull(device);
-        AudioManager.enforceSettableVolumeBehavior(deviceVolumeBehavior);
+        AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
 
         sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
                 + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:"
@@ -9232,6 +9259,10 @@
                     onLowerVolumeToRs1();
                     break;
 
+                case MSG_CONFIGURATION_CHANGED:
+                    onConfigurationChanged();
+                    break;
+
                 default:
                     if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) {
                         // msg could be for the SoundDoseHelper
@@ -9414,7 +9445,12 @@
                 }
                 AudioSystem.setParameters("screen_state=off");
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
-                handleConfigurationChanged(context);
+                sendMsg(mAudioHandler,
+                        MSG_CONFIGURATION_CHANGED,
+                        SENDMSG_REPLACE,
+                        0,
+                        0,
+                        null, 0);
             } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
                 if (mUserSwitchedReceived) {
                     // attempt to stop music playback for background user except on first user
@@ -10156,10 +10192,30 @@
     }
 
     //==========================================================================================
+
+    // camera sound is forced if any of the resources corresponding to one active SIM
+    // demands it.
     private boolean readCameraSoundForced() {
-        return SystemProperties.getBoolean("audio.camerasound.force", false) ||
-                mContext.getResources().getBoolean(
-                        com.android.internal.R.bool.config_camera_sound_forced);
+        if (SystemProperties.getBoolean("audio.camerasound.force", false)
+                || mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_camera_sound_forced)) {
+            return true;
+        }
+
+        SubscriptionManager subscriptionManager = mContext.getSystemService(
+                SubscriptionManager.class);
+        if (subscriptionManager == null) {
+            Log.e(TAG, "readCameraSoundForced cannot create SubscriptionManager!");
+            return false;
+        }
+        int[] subscriptionIds = subscriptionManager.getActiveSubscriptionIdList(false);
+        for (int subId : subscriptionIds) {
+            if (SubscriptionManager.getResourcesForSubId(mContext, subId).getBoolean(
+                    com.android.internal.R.bool.config_camera_sound_forced)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     //==========================================================================================
@@ -10370,11 +10426,11 @@
      * Monitoring rotation is optional, and is defined by the definition and value
      * of the "ro.audio.monitorRotation" system property.
      */
-    private void handleConfigurationChanged(Context context) {
+    private void onConfigurationChanged() {
         try {
             // reading new configuration "safely" (i.e. under try catch) in case anything
             // goes wrong.
-            Configuration config = context.getResources().getConfiguration();
+            Configuration config = mContext.getResources().getConfiguration();
             mSoundDoseHelper.configureSafeMedia(/*forced*/false, TAG);
 
             boolean cameraSoundForced = readCameraSoundForced();
@@ -10401,7 +10457,7 @@
                     mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM,
                             cameraSoundForced ?
                                     AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
-                            "handleConfigurationChanged");
+                            "onConfigurationChanged");
                     sendMsg(mAudioHandler,
                             MSG_SET_ALL_VOLUMES,
                             SENDMSG_QUEUE,
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 8f54e45..5edd434 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -172,13 +172,17 @@
     // initialization
     @SuppressWarnings("StaticAssignmentInConstructor")
     SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa,
-            boolean headTrackingEnabledByDefault) {
+            boolean binauralEnabledDefault,
+            boolean transauralEnabledDefault,
+            boolean headTrackingEnabledDefault) {
         mAudioService = mother;
         mASA = asa;
         // "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being
         // constructed here is the factory for SADeviceState, thus SADeviceState and its
         // private static field sHeadTrackingEnabledDefault should never be accessed directly.
-        SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledByDefault;
+        SADeviceState.sBinauralEnabledDefault = binauralEnabledDefault;
+        SADeviceState.sTransauralEnabledDefault = transauralEnabledDefault;
+        SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault;
     }
 
     synchronized void init(boolean effectExpected, @Nullable String settings) {
@@ -1547,10 +1551,12 @@
     }
 
     /*package*/ static final class SADeviceState {
+        private static boolean sBinauralEnabledDefault = true;
+        private static boolean sTransauralEnabledDefault = true;
         private static boolean sHeadTrackingEnabledDefault = false;
         final @AudioDeviceInfo.AudioDeviceType int mDeviceType;
         final @NonNull String mDeviceAddress;
-        boolean mEnabled = true;               // by default, SA is enabled on any device
+        boolean mEnabled;
         boolean mHasHeadTracker = false;
         boolean mHeadTrackerEnabled;
         static final String SETTING_FIELD_SEPARATOR = ",";
@@ -1566,6 +1572,12 @@
         SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) {
             mDeviceType = deviceType;
             mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
+            final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
+            mEnabled = spatMode == SpatializationMode.SPATIALIZER_BINAURAL
+                    ? sBinauralEnabledDefault
+                    : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
+                            ? sTransauralEnabledDefault
+                            : false;
             mHeadTrackerEnabled = sHeadTrackingEnabledDefault;
         }
 
diff --git a/services/core/java/com/android/server/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java
index ce68edbb..70d7bde 100644
--- a/services/core/java/com/android/server/cpu/CpuInfoReader.java
+++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java
@@ -52,11 +52,6 @@
     private static final String POLICY_DIR_PREFIX = "policy";
     private static final String RELATED_CPUS_FILE = "related_cpus";
     private static final String AFFECTED_CPUS_FILE = "affected_cpus";
-    // TODO(b/263154344): Avoid reading from cpuinfo_cur_freq because non-root users don't have
-    //  read permission for this file. The file permissions are set by the Kernel. Instead, read
-    //  the current frequency only from scaling_cur_freq.
-    private static final String CUR_CPUFREQ_FILE = "cpuinfo_cur_freq";
-    private static final String MAX_CPUFREQ_FILE = "cpuinfo_max_freq";
     private static final String CUR_SCALING_FREQ_FILE = "scaling_cur_freq";
     private static final String MAX_SCALING_FREQ_FILE = "scaling_max_freq";
     private static final String TIME_IN_STATE_FILE = "stats/time_in_state";
@@ -207,26 +202,16 @@
                 Slogf.w(TAG, "Missing dynamic policy info for policy ID %d", policyId);
                 continue;
             }
-            long curFreqKHz = CpuInfo.MISSING_FREQUENCY;
-            long maxFreqKHz = CpuInfo.MISSING_FREQUENCY;
-            if (dynamicPolicyInfo.curCpuFreqPair.cpuFreqKHz != CpuInfo.MISSING_FREQUENCY
-                    && staticPolicyInfo.maxCpuFreqPair.cpuFreqKHz != CpuInfo.MISSING_FREQUENCY) {
-                curFreqKHz = dynamicPolicyInfo.curCpuFreqPair.cpuFreqKHz;
-                maxFreqKHz = staticPolicyInfo.maxCpuFreqPair.cpuFreqKHz;
-            } else if (dynamicPolicyInfo.curCpuFreqPair.scalingFreqKHz != CpuInfo.MISSING_FREQUENCY
-                    && staticPolicyInfo.maxCpuFreqPair.scalingFreqKHz
-                    != CpuInfo.MISSING_FREQUENCY) {
-                curFreqKHz = dynamicPolicyInfo.curCpuFreqPair.scalingFreqKHz;
-                maxFreqKHz = staticPolicyInfo.maxCpuFreqPair.scalingFreqKHz;
-            } else {
+            if (dynamicPolicyInfo.curCpuFreqKHz == CpuInfo.MISSING_FREQUENCY
+                    || staticPolicyInfo.maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
                 Slogf.w(TAG, "Current and maximum CPU frequency information mismatch/missing for"
                         + " policy ID %d", policyId);
                 continue;
             }
-            if (curFreqKHz > maxFreqKHz) {
+            if (dynamicPolicyInfo.curCpuFreqKHz > staticPolicyInfo.maxCpuFreqKHz) {
                 Slogf.w(TAG, "Current CPU frequency (%d) is greater than maximum CPU frequency"
-                        + " (%d) for policy ID (%d). Skipping CPU frequency policy", curFreqKHz,
-                        maxFreqKHz, policyId);
+                        + " (%d) for policy ID (%d). Skipping CPU frequency policy",
+                        dynamicPolicyInfo.curCpuFreqKHz,  staticPolicyInfo.maxCpuFreqKHz, policyId);
                 continue;
             }
             for (int coreIdx = 0; coreIdx < staticPolicyInfo.relatedCpuCores.size(); coreIdx++) {
@@ -249,7 +234,7 @@
                 if (dynamicPolicyInfo.affectedCpuCores.indexOf(relatedCpuCore) < 0) {
                     cpuInfoByCpus.append(relatedCpuCore, new CpuInfo(relatedCpuCore,
                             cpusetCategories, /* isOnline= */false, CpuInfo.MISSING_FREQUENCY,
-                            maxFreqKHz, CpuInfo.MISSING_FREQUENCY, usageStats));
+                            staticPolicyInfo.maxCpuFreqKHz, CpuInfo.MISSING_FREQUENCY, usageStats));
                     continue;
                 }
                 // If a CPU core is online, it must have the usage stats. When the usage stats is
@@ -260,8 +245,8 @@
                     continue;
                 }
                 CpuInfo cpuInfo = new CpuInfo(relatedCpuCore, cpusetCategories, /* isOnline= */true,
-                        curFreqKHz, maxFreqKHz, dynamicPolicyInfo.avgTimeInStateCpuFreqKHz,
-                        usageStats);
+                        dynamicPolicyInfo.curCpuFreqKHz, staticPolicyInfo.maxCpuFreqKHz,
+                        dynamicPolicyInfo.avgTimeInStateCpuFreqKHz, usageStats);
                 cpuInfoByCpus.append(relatedCpuCore, cpuInfo);
                 if (DEBUG) {
                     Slogf.d(TAG, "Added %s for CPU core %d", cpuInfo, relatedCpuCore);
@@ -438,8 +423,8 @@
         for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) {
             int policyId = mCpuFreqPolicyDirsById.keyAt(i);
             File policyDir = mCpuFreqPolicyDirsById.valueAt(i);
-            FrequencyPair maxCpuFreqPair = readMaxCpuFrequency(policyDir);
-            if (maxCpuFreqPair.isEmpty()) {
+            long maxCpuFreqKHz = readCpuFreqKHz(new File(policyDir, MAX_SCALING_FREQ_FILE));
+            if (maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
                 Slogf.w(TAG, "Missing max CPU frequency information at %s",
                         policyDir.getAbsolutePath());
                 continue;
@@ -451,7 +436,7 @@
                         cpuCoresFile.getAbsolutePath());
                 continue;
             }
-            StaticPolicyInfo staticPolicyInfo = new StaticPolicyInfo(maxCpuFreqPair,
+            StaticPolicyInfo staticPolicyInfo = new StaticPolicyInfo(maxCpuFreqKHz,
                     relatedCpuCores);
             mStaticPolicyInfoById.append(policyId, staticPolicyInfo);
             if (DEBUG) {
@@ -461,18 +446,13 @@
         }
     }
 
-    private FrequencyPair readMaxCpuFrequency(File policyDir) {
-        return new FrequencyPair(readCpuFreqKHz(new File(policyDir, MAX_CPUFREQ_FILE)),
-                readCpuFreqKHz(new File(policyDir, MAX_SCALING_FREQ_FILE)));
-    }
-
     private SparseArray<DynamicPolicyInfo> readDynamicPolicyInfo() {
         SparseArray<DynamicPolicyInfo> dynamicPolicyInfoById = new SparseArray<>();
         for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) {
             int policyId = mCpuFreqPolicyDirsById.keyAt(i);
             File policyDir = mCpuFreqPolicyDirsById.valueAt(i);
-            FrequencyPair curCpuFreqPair = readCurrentCpuFrequency(policyDir);
-            if (curCpuFreqPair.isEmpty()) {
+            long curCpuFreqKHz = readCpuFreqKHz(new File(policyDir, CUR_SCALING_FREQ_FILE));
+            if (curCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
                 Slogf.w(TAG, "Missing current frequency information at %s",
                         policyDir.getAbsolutePath());
                 continue;
@@ -484,7 +464,7 @@
                 Slogf.e(TAG, "Failed to read CPU cores from %s", cpuCoresFile.getAbsolutePath());
                 continue;
             }
-            DynamicPolicyInfo dynamicPolicyInfo = new DynamicPolicyInfo(curCpuFreqPair,
+            DynamicPolicyInfo dynamicPolicyInfo = new DynamicPolicyInfo(curCpuFreqKHz,
                     avgTimeInStateCpuFreqKHz, affectedCpuCores);
             dynamicPolicyInfoById.append(policyId, dynamicPolicyInfo);
             if (DEBUG) {
@@ -495,11 +475,6 @@
         return dynamicPolicyInfoById;
     }
 
-    private FrequencyPair readCurrentCpuFrequency(File policyDir) {
-        return new FrequencyPair(readCpuFreqKHz(new File(policyDir, CUR_CPUFREQ_FILE)),
-                readCpuFreqKHz(new File(policyDir, CUR_SCALING_FREQ_FILE)));
-    }
-
     private long readAvgTimeInStateCpuFrequency(int policyId, File policyDir) {
         LongSparseLongArray latestTimeInState = readTimeInState(policyDir);
         if (latestTimeInState == null || latestTimeInState.size() == 0) {
@@ -913,58 +888,37 @@
         }
     }
 
-    private static final class FrequencyPair {
-        public final long cpuFreqKHz;
-        public final long scalingFreqKHz;
-
-        FrequencyPair(long cpuFreqKHz, long scalingFreqKHz) {
-            this.cpuFreqKHz = cpuFreqKHz;
-            this.scalingFreqKHz = scalingFreqKHz;
-        }
-
-        boolean isEmpty() {
-            return cpuFreqKHz == CpuInfo.MISSING_FREQUENCY
-                    && scalingFreqKHz == CpuInfo.MISSING_FREQUENCY;
-        }
-
-        @Override
-        public String toString() {
-            return "FrequencyPair{cpuFreqKHz = " + cpuFreqKHz + ", scalingFreqKHz = "
-                    + scalingFreqKHz + '}';
-        }
-    }
-
     private static final class StaticPolicyInfo {
-        public final FrequencyPair maxCpuFreqPair;
+        public final long maxCpuFreqKHz;
         public final IntArray relatedCpuCores;
 
-        StaticPolicyInfo(FrequencyPair maxCpuFreqPair, IntArray relatedCpuCores) {
-            this.maxCpuFreqPair = maxCpuFreqPair;
+        StaticPolicyInfo(long maxCpuFreqKHz, IntArray relatedCpuCores) {
+            this.maxCpuFreqKHz = maxCpuFreqKHz;
             this.relatedCpuCores = relatedCpuCores;
         }
 
         @Override
         public String toString() {
-            return "StaticPolicyInfo{maxCpuFreqPair = " + maxCpuFreqPair + ", relatedCpuCores = "
+            return "StaticPolicyInfo{maxCpuFreqKHz = " + maxCpuFreqKHz + ", relatedCpuCores = "
                     + relatedCpuCores + '}';
         }
     }
 
     private static final class DynamicPolicyInfo {
-        public final FrequencyPair curCpuFreqPair;
+        public final long curCpuFreqKHz;
         public final long avgTimeInStateCpuFreqKHz;
         public final IntArray affectedCpuCores;
 
-        DynamicPolicyInfo(FrequencyPair curCpuFreqPair, long avgTimeInStateCpuFreqKHz,
+        DynamicPolicyInfo(long curCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
                 IntArray affectedCpuCores) {
-            this.curCpuFreqPair = curCpuFreqPair;
+            this.curCpuFreqKHz = curCpuFreqKHz;
             this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz;
             this.affectedCpuCores = affectedCpuCores;
         }
 
         @Override
         public String toString() {
-            return "DynamicPolicyInfo{curCpuFreqPair = " + curCpuFreqPair
+            return "DynamicPolicyInfo{curCpuFreqKHz = " + curCpuFreqKHz
                     + ", avgTimeInStateCpuFreqKHz = " + avgTimeInStateCpuFreqKHz
                     + ", affectedCpuCores = " + affectedCpuCores + '}';
         }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ea157c8..e01aa9b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1514,14 +1514,17 @@
                 }
             }
 
-            // When calling setContentRecordingSession into the WindowManagerService, the WMS
+            // When calling WindowManagerService#setContentRecordingSession, WindowManagerService
             // attempts to acquire a lock before executing its main body. Due to this, we need
             // to be sure that it isn't called while the DisplayManagerService is also holding
             // a lock, to avoid a deadlock scenario.
             final ContentRecordingSession session =
                     virtualDisplayConfig.getContentRecordingSession();
-
-            if (displayId != Display.INVALID_DISPLAY && session != null) {
+            // Ensure session details are only set when mirroring (through VirtualDisplay flags or
+            // MediaProjection).
+            final boolean shouldMirror =
+                    projection != null || (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0;
+            if (shouldMirror && displayId != Display.INVALID_DISPLAY && session != null) {
                 // Only attempt to set content recording session if there are details to set and a
                 // VirtualDisplay has been successfully constructed.
                 session.setDisplayId(displayId);
@@ -1529,8 +1532,8 @@
                 // We set the content recording session here on the server side instead of using
                 // a second AIDL call in MediaProjection. By ensuring that a virtual display has
                 // been constructed before calling setContentRecordingSession, we avoid a race
-                // condition between the DMS & WMS which could lead to the MediaProjection
-                // being pre-emptively torn down.
+                // condition between the DisplayManagerService & WindowManagerService which could
+                // lead to the MediaProjection being pre-emptively torn down.
                 if (!mWindowManagerInternal.setContentRecordingSession(session)) {
                     // Unable to start mirroring, so tear down projection & release VirtualDisplay.
                     try {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index e12cd8c..656882f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1056,10 +1056,11 @@
         }
 
         float userLux = BrightnessMappingStrategy.NO_USER_LUX;
-        float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+        float userNits = -1;
         if (mInteractiveModeBrightnessMapper != null) {
             userLux = mInteractiveModeBrightnessMapper.getUserLux();
-            userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
+            float userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
+            userNits = mInteractiveModeBrightnessMapper.convertToNits(userBrightness);
         }
 
         final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
@@ -1179,6 +1180,13 @@
             if (mAutomaticBrightnessController != null) {
                 mAutomaticBrightnessController.stop();
             }
+            float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+            if (userNits >= 0) {
+                userBrightness = mInteractiveModeBrightnessMapper.convertToFloatScale(userNits);
+                if (userBrightness == PowerManager.BRIGHTNESS_INVALID_FLOAT) {
+                    userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+                }
+            }
             mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
                     this, handler.getLooper(), mSensorManager, mLightSensor,
                     mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index fbc354e..3e01222 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -893,10 +893,11 @@
         }
 
         float userLux = BrightnessMappingStrategy.NO_USER_LUX;
-        float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+        float userNits = -1;
         if (mInteractiveModeBrightnessMapper != null) {
             userLux = mInteractiveModeBrightnessMapper.getUserLux();
-            userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
+            float userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
+            userNits = mInteractiveModeBrightnessMapper.convertToNits(userBrightness);
         }
 
         final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
@@ -1016,6 +1017,13 @@
             if (mAutomaticBrightnessController != null) {
                 mAutomaticBrightnessController.stop();
             }
+            float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+            if (userNits >= 0) {
+                userBrightness = mInteractiveModeBrightnessMapper.convertToFloatScale(userNits);
+                if (userBrightness == PowerManager.BRIGHTNESS_INVALID_FLOAT) {
+                    userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+                }
+            }
             mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
                     this, handler.getLooper(), mSensorManager, mLightSensor,
                     mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 8d0689f..79984c9 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -911,7 +911,8 @@
                         final float newHdrSdrRatio;
                         if (displayNits != DisplayDeviceConfig.NITS_INVALID
                                 && sdrNits != DisplayDeviceConfig.NITS_INVALID) {
-                            newHdrSdrRatio = displayNits / sdrNits;
+                            // Ensure the ratio stays >= 1.0f as values below that are nonsensical
+                            newHdrSdrRatio = Math.max(1.f, displayNits / sdrNits);
                         } else {
                             newHdrSdrRatio = Float.NaN;
                         }
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index a081dff..5ef89ad 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -54,7 +54,9 @@
 
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final String DEBUG_PROPERTIES_FILE = "/etc/gps_debug.conf";
+    private static final String DEBUG_PROPERTIES_SYSTEM_FILE = "/etc/gps_debug.conf";
+
+    private static final String DEBUG_PROPERTIES_VENDOR_FILE = "/vendor/etc/gps_debug.conf";
 
     // config.xml properties
     private static final String CONFIG_SUPL_HOST = "SUPL_HOST";
@@ -285,7 +287,8 @@
         /*
          * Overlay carrier properties from a debug configuration file.
          */
-        loadPropertiesFromGpsDebugConfig(mProperties);
+        loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_VENDOR_FILE);
+        loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_SYSTEM_FILE);
         mEsExtensionSec = getRangeCheckedConfigEsExtensionSec();
 
         logConfigurations();
@@ -392,9 +395,9 @@
         }
     }
 
-    private void loadPropertiesFromGpsDebugConfig(Properties properties) {
+    private void loadPropertiesFromGpsDebugConfig(Properties properties, String filePath) {
         try {
-            File file = new File(DEBUG_PROPERTIES_FILE);
+            File file = new File(filePath);
             FileInputStream stream = null;
             try {
                 stream = new FileInputStream(file);
@@ -403,7 +406,7 @@
                 IoUtils.closeQuietly(stream);
             }
         } catch (IOException e) {
-            if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + DEBUG_PROPERTIES_FILE);
+            if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + filePath);
         }
     }
 
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 1aee345..f107d0b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -106,6 +106,14 @@
                 case COMMAND_HELP:
                     onHelp();
                     return 0;
+                case COMMAND_GET_DISABLED:
+                    runGetDisabled();
+                    return 0;
+                case COMMAND_SET_DISABLED:
+                    // Note: if the user has an LSKF, then this has no immediate effect but instead
+                    // just ensures the lockscreen will be disabled later when the LSKF is cleared.
+                    runSetDisabled();
+                    return 0;
             }
             if (!checkCredential()) {
                 return -1;
@@ -124,15 +132,9 @@
                 case COMMAND_CLEAR:
                     success = runClear();
                     break;
-                case COMMAND_SET_DISABLED:
-                    runSetDisabled();
-                    break;
                 case COMMAND_VERIFY:
                     runVerify();
                     break;
-                case COMMAND_GET_DISABLED:
-                    runGetDisabled();
-                    break;
                 default:
                     getErrPrintWriter().println("Unknown command: " + cmd);
                     break;
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 2e86df8..f95f7bc 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -18,7 +18,6 @@
 
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
-import static com.android.server.pm.DexOptHelper.useArtService;
 import static com.android.server.pm.PackageManagerService.TAG;
 import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
 import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
@@ -245,7 +244,7 @@
                 }
             }
 
-            if (!useArtService()) { // ART Service handles this on demand instead.
+            if (!DexOptHelper.useArtService()) { // ART Service handles this on demand instead.
                 // Prepare the application profiles only for upgrades and
                 // first boot (so that we don't repeat the same operation at
                 // each boot).
@@ -591,7 +590,7 @@
             Slog.wtf(TAG, "Package was null!", new Throwable());
             return;
         }
-        if (useArtService()) {
+        if (DexOptHelper.useArtService()) {
             destroyAppProfilesWithArtService(pkg);
         } else {
             try {
@@ -637,7 +636,7 @@
     }
 
     private void destroyAppProfilesLeafLIF(AndroidPackage pkg) {
-        if (useArtService()) {
+        if (DexOptHelper.useArtService()) {
             destroyAppProfilesWithArtService(pkg);
         } else {
             try {
@@ -651,6 +650,15 @@
     }
 
     private void destroyAppProfilesWithArtService(AndroidPackage pkg) {
+        if (!DexOptHelper.artManagerLocalIsInitialized()) {
+            // This function may get called while PackageManagerService is constructed (via e.g.
+            // InitAppsHelper.initSystemApps), and ART Service hasn't yet been started then (it
+            // requires a registered PackageManagerLocal instance). We can skip clearing any stale
+            // app profiles in this case, because ART Service and the runtime will ignore stale or
+            // otherwise invalid ref and cur profiles.
+            return;
+        }
+
         try (PackageManagerLocal.FilteredSnapshot snapshot =
                         getPackageManagerLocal().withFilteredSnapshot()) {
             try {
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index a9d4115..064be7c 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -99,6 +99,8 @@
 public final class DexOptHelper {
     private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
 
+    private static boolean sArtManagerLocalIsInitialized = false;
+
     private final PackageManagerService mPm;
 
     // Start time for the boot dexopt in performPackageDexOptUpgradeIfNeeded when ART Service is
@@ -1035,6 +1037,7 @@
         artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run,
                 pm.getDexOptHelper().new DexoptDoneHandler());
         LocalManagerRegistry.addManager(ArtManagerLocal.class, artManager);
+        sArtManagerLocalIsInitialized = true;
 
         // Schedule the background job when boot is complete. This decouples us from when
         // JobSchedulerService is initialized.
@@ -1048,6 +1051,15 @@
     }
 
     /**
+     * Returns true if an {@link ArtManagerLocal} instance has been created.
+     *
+     * Avoid this function if at all possible, because it may hide initialization order problems.
+     */
+    public static boolean artManagerLocalIsInitialized() {
+        return sArtManagerLocalIsInitialized;
+    }
+
+    /**
      * Returns the registered {@link ArtManagerLocal} instance, or else throws an unchecked error.
      */
     public static @NonNull ArtManagerLocal getArtManagerLocal() {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7fe6c7d..cd1ae74 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3279,7 +3279,7 @@
         final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
         removePackageHelper.removePackage(stubPkg, true /*chatty*/);
         try {
-            return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, null);
+            return initPackageTracedLI(scanFile, parseFlags, scanFlags);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
                     e);
@@ -3410,8 +3410,7 @@
                         | ParsingPackageUtils.PARSE_MUST_BE_APK
                         | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
         @PackageManagerService.ScanFlags int scanFlags = mPm.getSystemPackageScanFlags(codePath);
-        final AndroidPackage pkg = scanSystemPackageTracedLI(
-                codePath, parseFlags, scanFlags, null);
+        final AndroidPackage pkg = initPackageTracedLI(codePath, parseFlags, scanFlags);
 
         synchronized (mPm.mLock) {
             PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName());
@@ -3591,7 +3590,7 @@
                 try {
                     final File codePath = new File(pkg.getPath());
                     synchronized (mPm.mInstallLock) {
-                        scanSystemPackageTracedLI(codePath, 0, scanFlags, null);
+                        initPackageTracedLI(codePath, 0, scanFlags);
                     }
                 } catch (PackageManagerException e) {
                     Slog.e(TAG, "Failed to parse updated, ex-system package: "
@@ -3734,12 +3733,6 @@
             String errorMsg = null;
 
             if (throwable == null) {
-                // TODO(b/194319951): move lower in the scan chain
-                // Static shared libraries have synthetic package names
-                if (parseResult.parsedPackage.isStaticSharedLibrary()) {
-                    PackageManagerService.renameStaticSharedLibraryPackage(
-                            parseResult.parsedPackage);
-                }
                 try {
                     addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
                             new UserHandle(UserHandle.USER_SYSTEM), apexInfo);
@@ -3804,8 +3797,8 @@
 
             try {
                 synchronized (mPm.mInstallLock) {
-                    final AndroidPackage newPkg = scanSystemPackageTracedLI(
-                            scanFile, reparseFlags, rescanFlags, null);
+                    final AndroidPackage newPkg = initPackageTracedLI(
+                            scanFile, reparseFlags, rescanFlags);
                     // We rescanned a stub, add it to the list of stubbed system packages
                     if (newPkg.isStub()) {
                         stubSystemApps.add(packageName);
@@ -3819,28 +3812,26 @@
     }
 
     /**
-     *  Traces a package scan.
-     *  @see #scanSystemPackageLI(File, int, int, UserHandle)
+     *  Traces a package scan and registers it with the system.
+     *  @see #initPackageLI(File, int, int)
      */
     @GuardedBy("mPm.mInstallLock")
-    public AndroidPackage scanSystemPackageTracedLI(File scanFile, final int parseFlags,
-            int scanFlags, @Nullable ApexManager.ActiveApexInfo apexInfo)
+    public AndroidPackage initPackageTracedLI(File scanFile, final int parseFlags, int scanFlags)
             throws PackageManagerException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage [" + scanFile.toString() + "]");
         try {
-            return scanSystemPackageLI(scanFile, parseFlags, scanFlags, apexInfo);
+            return initPackageLI(scanFile, parseFlags, scanFlags);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
     }
 
     /**
-     *  Scans a package and returns the newly parsed package.
+     *  Scans a package, registers it with the system and returns the newly parsed package.
      *  Returns {@code null} in case of errors and the error code is stored in mLastScanError
      */
     @GuardedBy("mPm.mInstallLock")
-    private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags,
-            @Nullable ApexManager.ActiveApexInfo apexInfo)
+    private AndroidPackage initPackageLI(File scanFile, int parseFlags, int scanFlags)
             throws PackageManagerException {
         if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
 
@@ -3852,13 +3843,8 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
-        // Static shared libraries have synthetic package names
-        if (parsedPackage.isStaticSharedLibrary()) {
-            PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
-        }
-
         return addForInitLI(parsedPackage, parseFlags, scanFlags,
-                new UserHandle(UserHandle.USER_SYSTEM), apexInfo);
+                new UserHandle(UserHandle.USER_SYSTEM), null);
     }
 
     /**
@@ -3882,6 +3868,10 @@
             throws PackageManagerException {
         PackageSetting disabledPkgSetting;
         synchronized (mPm.mLock) {
+            // Static shared libraries have synthetic package names
+            if (activeApexInfo == null && parsedPackage.isStaticSharedLibrary()) {
+                PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
+            }
             disabledPkgSetting =
                     mPm.mSettings.getDisabledSystemPkgLPr(parsedPackage.getPackageName());
             if (activeApexInfo != null && disabledPkgSetting != null) {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 0d417e4..68c8abf 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -821,8 +821,10 @@
      * Creates an oat dir for given package and instruction set.
      */
     public void createOatDir(String packageName, String oatDir, String dexInstructionSet)
-            throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
+            throws InstallerException {
+        // This method should be allowed even if ART Service is enabled, because it's used for
+        // creating oat dirs before creating hard links for partial installation.
+        // TODO(b/274658735): Add an ART Service API to support hard linking.
         if (!checkBeforeRemote()) return;
         try {
             mInstalld.createOatDir(packageName, oatDir, dexInstructionSet);
@@ -1177,7 +1179,7 @@
         // TODO(b/260124949): Remove the legacy dexopt code paths, i.e. this exception and all code
         // that may throw it.
         public LegacyDexoptDisabledException() {
-            super("Invalid call to legacy dexopt installd method while ART Service is in use.");
+            super("Invalid call to legacy dexopt method while ART Service is in use.");
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 03e0d36..36aeca1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -49,7 +49,6 @@
 import static com.android.internal.util.XmlUtils.writeByteArrayAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.internal.util.XmlUtils.writeUriAttribute;
-import static com.android.server.pm.DexOptHelper.useArtService;
 import static com.android.server.pm.PackageInstallerService.prepareStageDir;
 import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
 
@@ -173,7 +172,6 @@
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
 import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
@@ -2560,15 +2558,9 @@
                 }
 
                 if (isLinkPossible(fromFiles, toDir)) {
-                    if (!useArtService()) { // ART Service creates oat dirs on demand instead.
-                        if (!mResolvedInstructionSets.isEmpty()) {
-                            final File oatDir = new File(toDir, "oat");
-                            try {
-                                createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir);
-                            } catch (LegacyDexoptDisabledException e) {
-                                throw new RuntimeException(e);
-                            }
-                        }
+                    if (!mResolvedInstructionSets.isEmpty()) {
+                        final File oatDir = new File(toDir, "oat");
+                        createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir);
                     }
                     // pre-create lib dirs for linking if necessary
                     if (!mResolvedNativeLibPaths.isEmpty()) {
@@ -3829,7 +3821,7 @@
     }
 
     private void createOatDirs(String packageName, List<String> instructionSets, File fromDir)
-            throws PackageManagerException, LegacyDexoptDisabledException {
+            throws PackageManagerException {
         for (String instructionSet : instructionSets) {
             try {
                 mInstaller.createOatDir(packageName, fromDir.getAbsolutePath(), instructionSet);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6bc8760..b92e3c0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5561,32 +5561,18 @@
         public void registerDexModule(String packageName, String dexModulePath,
                 boolean isSharedModule,
                 IDexModuleRegisterCallback callback) {
-            if (useArtService()) {
-                // ART Service currently doesn't support this explicit dexopting and instead relies
-                // on background dexopt for secondary dex files. This API is problematic since it
-                // doesn't provide the correct classloader context.
-                Slog.i(TAG,
-                        "Ignored unsupported registerDexModule call for " + dexModulePath + " in "
-                                + packageName);
-                return;
-            }
-
-            int userId = UserHandle.getCallingUserId();
-            ApplicationInfo ai = snapshot().getApplicationInfo(packageName, /*flags*/ 0, userId);
-            DexManager.RegisterDexModuleResult result;
-            if (ai == null) {
-                Slog.w(PackageManagerService.TAG,
-                        "Registering a dex module for a package that does not exist for the" +
-                                " calling user. package=" + packageName + ", user=" + userId);
-                result = new DexManager.RegisterDexModuleResult(false, "Package not installed");
-            } else {
-                try {
-                    result = mDexManager.registerDexModule(
-                            ai, dexModulePath, isSharedModule, userId);
-                } catch (LegacyDexoptDisabledException e) {
-                    throw new RuntimeException(e);
-                }
-            }
+            // ART Service doesn't support this explicit dexopting and instead relies on background
+            // dexopt for secondary dex files. For compat parity between ART Service and the legacy
+            // code it's disabled for both.
+            //
+            // Also, this API is problematic anyway since it doesn't provide the correct classloader
+            // context, so it is hard to produce dexopt artifacts that the runtime can load
+            // successfully.
+            Slog.i(TAG,
+                    "Ignored unsupported registerDexModule call for " + dexModulePath + " in "
+                            + packageName);
+            DexManager.RegisterDexModuleResult result = new DexManager.RegisterDexModuleResult(
+                    false, "registerDexModule call not supported since Android U");
 
             if (callback != null) {
                 mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 7684a49..8f8f437 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -159,8 +159,8 @@
             synchronized (mPm.mInstallLock) {
                 final AndroidPackage pkg;
                 try {
-                    pkg = installPackageHelper.scanSystemPackageTracedLI(
-                            ps.getPath(), parseFlags, SCAN_INITIAL, null);
+                    pkg = installPackageHelper.initPackageTracedLI(
+                            ps.getPath(), parseFlags, SCAN_INITIAL);
                     loaded.add(pkg);
 
                 } catch (PackageManagerException e) {
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 18eebe4..b4b8cb2 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -619,10 +619,10 @@
         final Bundle extras = new Bundle(3);
         extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
         extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+        final int flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND;
         handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
-                extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
-                null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
-                null /* broadcastAllowList */,
+                extras, flags, null /* targetPkg */, null /* finishedReceiver */,
+                new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
                 (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
                         mPm.snapshotComputer(), callingUid, intentExtras),
                 null /* bOptions */));
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7414640..b066cad 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1275,7 +1275,7 @@
         intent.putExtra(Intent.EXTRA_USER_HANDLE, profileHandle.getIdentifier());
         getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers(
                 intent, parentHandle, /* requiresPermission= */ true);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendBroadcastAsUser(intent, parentHandle);
     }
 
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 7f0c3f9..6e738da 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm.dex;
 
-import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
@@ -659,62 +658,6 @@
         }
     }
 
-    // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the
-    // compilation happening here will use a pessimistic context.
-    public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
-            boolean isSharedModule, int userId) throws LegacyDexoptDisabledException {
-        // Find the owning package record.
-        DexSearchResult searchResult = getDexPackage(info, dexPath, userId);
-
-        if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) {
-            return new RegisterDexModuleResult(false, "Package not found");
-        }
-        if (!info.packageName.equals(searchResult.mOwningPackageName)) {
-            return new RegisterDexModuleResult(false, "Dex path does not belong to package");
-        }
-        if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
-                searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) {
-            return new RegisterDexModuleResult(false, "Main apks cannot be registered");
-        }
-
-        // We found the package. Now record the usage for all declared ISAs.
-        boolean update = false;
-        // If this is a shared module set the loading package to an arbitrary package name
-        // so that we can mark that module as usedByOthers.
-        String loadingPackage = isSharedModule ? ".shared.module" : searchResult.mOwningPackageName;
-        for (String isa : getAppDexInstructionSets(info.primaryCpuAbi, info.secondaryCpuAbi)) {
-            boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
-                    dexPath, userId, isa, /*primaryOrSplit*/ false,
-                    loadingPackage,
-                    PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT,
-                    /*overwriteCLC=*/ false);
-            update |= newUpdate;
-        }
-        if (update) {
-            mPackageDexUsage.maybeWriteAsync();
-        }
-
-        DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName)
-                .getDexUseInfoMap().get(dexPath);
-
-        // Try to optimize the package according to the install reason.
-        DexoptOptions options = new DexoptOptions(info.packageName,
-                PackageManagerService.REASON_INSTALL, /*flags*/0);
-
-        int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo,
-                options);
-
-        // If we fail to optimize the package log an error but don't propagate the error
-        // back to the app. The app cannot do much about it and the background job
-        // will rety again when it executes.
-        // TODO(calin): there might be some value to return the error here but it may
-        // cause red herrings since that doesn't mean the app cannot use the module.
-        if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
-            Slog.e(TAG, "Failed to optimize dex module " + dexPath);
-        }
-        return new RegisterDexModuleResult(true, "Dex module registered successfully");
-    }
-
     /**
      * Return all packages that contain records of secondary dex files.
      */
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index c0d71ac..579d4e3 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -26,10 +26,10 @@
             ]
         },
         {
-            "name": "CtsPermission2TestCases",
+            "name": "CtsPermissionPolicyTestCases",
             "options": [
                 {
-                    "include-filter": "android.permission2.cts.RestrictedPermissionsTest"
+                    "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
                 },
                 {
                     "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index 094e70f..9f1cb1a 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -29,16 +29,16 @@
       ]
     },
     {
-      "name": "CtsPermission2TestCases",
+      "name": "CtsPermissionPolicyTestCases",
       "options": [
         {
-          "include-filter": "android.permission2.cts.RestrictedPermissionsTest"
+          "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
         },
         {
-          "include-filter": "android.permission2.cts.RestrictedStoragePermissionSharedUidTest"
+          "include-filter": "android.permissionpolicy.cts.RestrictedStoragePermissionSharedUidTest"
         },
         {
-          "include-filter": "android.permission2.cts.RestrictedStoragePermissionTest"
+          "include-filter": "android.permissionpolicy.cts.RestrictedStoragePermissionTest"
         }
       ]
     },
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 62932f9..f5bc8ff 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -121,6 +121,7 @@
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
+import static android.view.WindowManager.TRANSIT_RELAUNCH;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
@@ -5205,7 +5206,7 @@
             Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: " + token);
             return;
         }
-        if (visible == mVisibleRequested && visible == mVisible
+        if (visible == mVisibleRequested && visible == mVisible && visible == isClientVisible()
                 && mTransitionController.isShellTransitionsEnabled()) {
             // For shell transition, it is no-op if there is no state change.
             return;
@@ -9701,9 +9702,36 @@
             return;
         }
 
-        if (getParent() != null) {
+        if (mTransitionController.isShellTransitionsEnabled()) {
+            final Transition transition = new Transition(TRANSIT_RELAUNCH, 0 /* flags */,
+                    mTransitionController, mWmService.mSyncEngine);
+            final Runnable executeRestart = () -> {
+                if (mState != RESTARTING_PROCESS || !attachedToProcess()) {
+                    transition.abort();
+                    return;
+                }
+                // Request invisible so there will be a change after the activity is restarted
+                // to be visible.
+                setVisibleRequested(false);
+                transition.collect(this);
+                mTransitionController.requestStartTransition(transition, task,
+                        null /* remoteTransition */, null /* displayChange */);
+                scheduleStopForRestartProcess();
+            };
+            if (mWmService.mSyncEngine.hasActiveSync()) {
+                mWmService.mSyncEngine.queueSyncSet(
+                        () -> mTransitionController.moveToCollecting(transition), executeRestart);
+            } else {
+                mTransitionController.moveToCollecting(transition);
+                executeRestart.run();
+            }
+        } else {
             startFreezingScreen();
+            scheduleStopForRestartProcess();
         }
+    }
+
+    private void scheduleStopForRestartProcess() {
         // The process will be killed until the activity reports stopped with saved state (see
         // {@link ActivityTaskManagerService.activityStopped}).
         try {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 5e066fa..f8fb76a 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -50,8 +50,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.RemoteAnimationAdapter;
-import android.view.WindowManager;
-import android.window.RemoteTransition;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -562,9 +560,8 @@
         final Task rootTask = mService.mRootWindowContainer.getDefaultTaskDisplayArea()
                 .getRootTask(WINDOWING_MODE_UNDEFINED, activityType);
         if (rootTask == null) return false;
-        final RemoteTransition remote = options.getRemoteTransition();
         final ActivityRecord r = rootTask.topRunningActivity();
-        if (r == null || r.isVisibleRequested() || !r.attachedToProcess() || remote == null
+        if (r == null || r.isVisibleRequested() || !r.attachedToProcess()
                 || !r.mActivityComponent.equals(intent.getComponent())
                 // Recents keeps invisible while device is locked.
                 || r.mDisplayContent.isKeyguardLocked()) {
@@ -573,47 +570,13 @@
         mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r);
         final ActivityMetricsLogger.LaunchingState launchingState =
                 mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
-        final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT,
-                0 /* flags */, r.mTransitionController, mService.mWindowManager.mSyncEngine);
-        if (r.mTransitionController.isCollecting()) {
-            // Special case: we are entering recents while an existing transition is running. In
-            // this case, we know it's safe to "defer" the activity launch, so lets do so now so
-            // that it can get its own transition and thus update launcher correctly.
-            mService.mWindowManager.mSyncEngine.queueSyncSet(
-                    () -> {
-                        if (r.isAttached()) {
-                            r.mTransitionController.moveToCollecting(transition);
-                        }
-                    },
-                    () -> {
-                        if (r.isAttached() && transition.isCollecting()) {
-                            startExistingRecentsIfPossibleInner(options, r, rootTask,
-                                    launchingState, remote, transition);
-                        }
-                    });
-        } else {
-            r.mTransitionController.moveToCollecting(transition);
-            startExistingRecentsIfPossibleInner(options, r, rootTask, launchingState, remote,
-                    transition);
-        }
-        return true;
-    }
-
-    private void startExistingRecentsIfPossibleInner(ActivityOptions options, ActivityRecord r,
-            Task rootTask, ActivityMetricsLogger.LaunchingState launchingState,
-            RemoteTransition remoteTransition, Transition transition) {
         final Task task = r.getTask();
         mService.deferWindowLayout();
         try {
             final TransitionController controller = r.mTransitionController;
             if (controller.getTransitionPlayer() != null) {
-                controller.requestStartTransition(transition, task, remoteTransition,
-                        null /* displayChange */);
                 controller.collect(task);
                 controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask));
-            } else {
-                // The transition player might be died when executing the queued transition.
-                transition.abort();
             }
             task.moveToFront("startExistingRecents");
             task.mInResumeTopActivity = true;
@@ -624,6 +587,7 @@
             task.mInResumeTopActivity = false;
             mService.continueWindowLayout();
         }
+        return true;
     }
 
     void registerRemoteAnimationForNextActivityStart(String packageName,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index ce29564..12be1d3 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1582,19 +1582,19 @@
             }
         }
         if (isTransientLaunch) {
-            if (forceTransientTransition && newTransition != null) {
-                newTransition.collect(mLastStartActivityRecord);
-                newTransition.collect(mPriorAboveTask);
+            if (forceTransientTransition) {
+                transitionController.collect(mLastStartActivityRecord);
+                transitionController.collect(mPriorAboveTask);
             }
             // `started` isn't guaranteed to be the actual relevant activity, so we must wait
             // until after we launched to identify the relevant activity.
             transitionController.setTransientLaunch(mLastStartActivityRecord, mPriorAboveTask);
-            if (forceTransientTransition && newTransition != null) {
+            if (forceTransientTransition) {
                 final DisplayContent dc = mLastStartActivityRecord.getDisplayContent();
                 // update wallpaper target to TransientHide
                 dc.mWallpaperController.adjustWallpaperWindows();
                 // execute transition because there is no change
-                newTransition.setReady(dc, true /* ready */);
+                transitionController.setReady(dc, true /* ready */);
             }
         }
         if (!userLeaving) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 992743a..555cd38 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1240,25 +1240,6 @@
             ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
 
         final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions);
-        // A quick path (skip general intent/task resolving) to start recents animation if the
-        // recents (or home) activity is available in background.
-        if (opts != null && opts.getOriginalOptions().getTransientLaunch()
-                && isCallerRecents(Binder.getCallingUid())) {
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                synchronized (mGlobalLock) {
-                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "startExistingRecents");
-                    if (mActivityStartController.startExistingRecentsIfPossible(
-                            intent, opts.getOriginalOptions())) {
-                        return ActivityManager.START_TASK_TO_FRONT;
-                    }
-                    // Else follow the standard launch procedure.
-                }
-            } finally {
-                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-                Binder.restoreCallingIdentity(origId);
-            }
-        }
 
         assertPackageMatchesCallingUid(callingPackage);
         enforceNotIsolatedCaller("startActivityAsUser");
@@ -5718,6 +5699,23 @@
                 boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
                 BackgroundStartPrivileges backgroundStartPrivileges) {
             assertPackageMatchesCallingUid(callingPackage);
+            // A quick path (skip general intent/task resolving) to start recents animation if the
+            // recents (or home) activity is available in background.
+            if (options != null && options.getOriginalOptions() != null
+                    && options.getOriginalOptions().getTransientLaunch() && isCallerRecents(uid)) {
+                try {
+                    synchronized (mGlobalLock) {
+                        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "startExistingRecents");
+                        if (mActivityStartController.startExistingRecentsIfPossible(
+                                intent, options.getOriginalOptions())) {
+                            return ActivityManager.START_TASK_TO_FRONT;
+                        }
+                        // Else follow the standard launch procedure.
+                    }
+                } finally {
+                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                }
+            }
             return getActivityStartController().startActivityInPackage(uid, realCallingPid,
                     realCallingUid, callingPackage, callingFeatureId, intent, resolvedType,
                     resultTo, resultWho, requestCode, startFlags, options, userId, inTask,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a4d475f..1344788 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1228,7 +1228,8 @@
     private void finishHoldScreenUpdate() {
         final boolean hold = mTmpHoldScreenWindow != null;
         if (hold && mTmpHoldScreenWindow != mHoldScreenWindow) {
-            mHoldScreenWakeLock.setWorkSource(new WorkSource(mTmpHoldScreenWindow.mSession.mUid));
+            mHoldScreenWakeLock.setWorkSource(new WorkSource(mTmpHoldScreenWindow.mSession.mUid,
+                    mTmpHoldScreenWindow.mSession.mPackageName));
         }
         mHoldScreenWindow = mTmpHoldScreenWindow;
         mTmpHoldScreenWindow = null;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 8c59548..8f40e79 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.WindowManager.INPUT_CONSUMER_PIP;
 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
 import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
@@ -296,8 +297,11 @@
                 // be applied using the SurfaceControl hierarchy from the Organizer. This means
                 // we need to make sure that these changes in crop are reflected in the input
                 // windows, and so ensure this flag is set so that the input crop always reflects
-                // the surface hierarchy.
-                useSurfaceBoundsAsTouchRegion = true;
+                // the surface hierarchy. However, we only want to set this when the client did
+                // not already provide a touchable region, so that we don't ignore the one provided.
+                if (w.mTouchableInsets != TOUCHABLE_INSETS_REGION) {
+                    useSurfaceBoundsAsTouchRegion = true;
+                }
 
                 if (w.mAttrs.isModal()) {
                     TaskFragment parent = w.getTaskFragment();
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 78ee6f9..7b10c63 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -116,7 +116,7 @@
     private boolean mShowingAlertWindowNotificationAllowed;
     private boolean mClientDead = false;
     private float mLastReportedAnimatorScale;
-    private String mPackageName;
+    protected String mPackageName;
     private String mRelayoutTag;
     private final InsetsSourceControl.Array mDummyControls =  new InsetsSourceControl.Array();
     final boolean mSetsUnrestrictedKeepClearAreas;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8c6de8e..68b2d0f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3361,6 +3361,8 @@
                 && info.pictureInPictureParams.isLaunchIntoPip()
                 && top.getLastParentBeforePip() != null)
                         ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID;
+        info.lastParentTaskIdBeforePip = top != null && top.getLastParentBeforePip() != null
+                ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID;
         info.shouldDockBigOverlays = top != null && top.shouldDockBigOverlays;
         info.mTopActivityLocusId = top != null ? top.getLocusId() : null;
 
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 93c8c36..184293e 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -390,7 +390,7 @@
             boolean taskAppearedSent = t.mTaskAppearedSent;
             if (taskAppearedSent) {
                 if (t.getSurfaceControl() != null) {
-                    t.migrateToNewSurfaceControl(t.getSyncTransaction());
+                    t.migrateToNewSurfaceControl(t.getPendingTransaction());
                 }
                 t.mTaskAppearedSent = false;
             }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 8b0ef6c..c74af82 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -439,7 +439,11 @@
                 // multiple sync at the same time because it may cause conflict.
                 // Create a new transition when there is no active sync to collect the changes.
                 final Transition transition = mTransitionController.createTransition(type);
-                applyTransaction(wct, -1 /* syncId */, transition, caller);
+                if (applyTransaction(wct, -1 /* syncId */, transition, caller)
+                        == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) {
+                    transition.abort();
+                    return;
+                }
                 mTransitionController.requestStartTransition(transition, null /* startTask */,
                         null /* remoteTransition */, null /* displayChange */);
                 transition.setAllReady();
@@ -476,24 +480,26 @@
                     // calls startSyncSet.
                     () -> mTransitionController.moveToCollecting(nextTransition),
                     () -> {
-                        if (mTaskFragmentOrganizerController.isValidTransaction(wct)) {
-                            applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
+                        if (mTaskFragmentOrganizerController.isValidTransaction(wct)
+                                && (applyTransaction(wct, -1 /* syncId */, nextTransition, caller)
+                                        != TRANSACT_EFFECTS_NONE
+                                || !nextTransition.mParticipants.isEmpty())) {
                             mTransitionController.requestStartTransition(nextTransition,
                                     null /* startTask */, null /* remoteTransition */,
                                     null /* displayChange */);
                             nextTransition.setAllReady();
-                        } else {
-                            nextTransition.abort();
+                            return;
                         }
+                        nextTransition.abort();
                     });
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
-    private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
+    private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
             @Nullable Transition transition, @NonNull CallerInfo caller) {
-        applyTransaction(t, syncId, transition, caller, null /* finishTransition */);
+        return applyTransaction(t, syncId, transition, caller, null /* finishTransition */);
     }
 
     /**
@@ -501,8 +507,9 @@
      * @param transition A transition to collect changes into.
      * @param caller Info about the calling process.
      * @param finishTransition The transition that is currently being finished.
+     * @return The effects of the window container transaction.
      */
-    private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
+    private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
             @Nullable Transition transition, @NonNull CallerInfo caller,
             @Nullable Transition finishTransition) {
         int effects = TRANSACT_EFFECTS_NONE;
@@ -639,6 +646,7 @@
             mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
             mService.continueWindowLayout();
         }
+        return effects;
     }
 
     private int applyChanges(@NonNull WindowContainer<?> container,
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 8f9b949..a15d6630 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.credentials;
 
+import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS;
 import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
 import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR;
 import static android.content.Context.CREDENTIAL_SERVICE;
@@ -124,7 +125,8 @@
         serviceInfos.forEach(
                 info -> {
                     services.add(
-                            new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info));
+                            new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
+                                    info));
                 });
         return services;
     }
@@ -418,6 +420,7 @@
                 // Check privileged permissions
                 mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
             }
+            enforcePermissionForAllowedProviders(request);
 
             final int userId = UserHandle.getCallingUserId();
             final int callingUid = Binder.getCallingUid();
@@ -447,6 +450,9 @@
             // TODO(b/273308895): implement
 
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+            enforcePermissionForAllowedProviders(request);
+
             return cancelTransport;
         }
 
@@ -823,6 +829,17 @@
         }
     }
 
+    private void enforcePermissionForAllowedProviders(GetCredentialRequest request) {
+        boolean containsAllowedProviders = request.getCredentialOptions()
+                .stream()
+                .anyMatch(option -> option.getAllowedProviders() != null
+                        && !option.getAllowedProviders().isEmpty());
+        if (containsAllowedProviders) {
+            mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS,
+                    null);
+        }
+    }
+
     private void enforceCallingPackage(String callingPackage, int callingUid) {
         int packageUid;
         PackageManager pm = mContext.createContextAsUser(
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 4e058a8..8082cdb 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -27,6 +27,7 @@
 import android.credentials.IGetCredentialCallback;
 import android.credentials.ui.ProviderData;
 import android.credentials.ui.RequestInfo;
+import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
@@ -84,11 +85,12 @@
     protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
         mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
         try {
-            mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
+            Binder.withCleanCallingIdentity(() ->
+                    mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newGetRequestInfo(
-                    mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
-                    providerDataList));
-        } catch (RemoteException e) {
+                            mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
+                    providerDataList)));
+        } catch (RuntimeException e) {
             mChosenProviderFinalPhaseMetric.setUiReturned(false);
             respondToClientWithErrorAndFinish(
                     GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 9bc5998..95b0ff0 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -94,7 +94,8 @@
             RemoteCredentialService remoteCredentialService) {
         android.credentials.GetCredentialRequest filteredRequest =
                 filterOptions(providerInfo.getCapabilities(),
-                        getRequestSession.mClientRequest);
+                        getRequestSession.mClientRequest,
+                        providerInfo.getComponentName());
         if (filteredRequest != null) {
             Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
                     new HashMap<>();
@@ -142,17 +143,19 @@
     @Nullable
     private static android.credentials.GetCredentialRequest filterOptions(
             List<String> providerCapabilities,
-            android.credentials.GetCredentialRequest clientRequest
+            android.credentials.GetCredentialRequest clientRequest,
+            ComponentName componentName
     ) {
         List<CredentialOption> filteredOptions = new ArrayList<>();
         for (CredentialOption option : clientRequest.getCredentialOptions()) {
-            if (providerCapabilities.contains(option.getType())) {
+            if (providerCapabilities.contains(option.getType())
+                    && isProviderAllowed(option, componentName)) {
                 Log.i(TAG, "In createProviderRequest - capability found : "
                         + option.getType());
                 filteredOptions.add(option);
             } else {
                 Log.i(TAG, "In createProviderRequest - capability not "
-                        + "found : " + option.getType());
+                        + "found, or provider not allowed : " + option.getType());
             }
         }
         if (!filteredOptions.isEmpty()) {
@@ -165,6 +168,16 @@
         return null;
     }
 
+    private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) {
+        if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains(
+                componentName)) {
+            Log.d(TAG, "Provider allow list specified but does not contain this provider: "
+                    + componentName.flattenToString());
+            return false;
+        }
+        return true;
+    }
+
     public ProviderGetSession(Context context,
             CredentialProviderInfo info,
             ProviderInternalCallback<GetCredentialResponse> callbacks,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e9c50b5..269c4ab 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -225,6 +225,7 @@
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
+import static android.provider.DeviceConfig.NAMESPACE_TELEPHONY;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
 import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -426,6 +427,7 @@
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.ParcelableKeyGenParameterSpec;
 import android.stats.devicepolicy.DevicePolicyEnums;
+import android.telecom.TelecomManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
@@ -3324,7 +3326,7 @@
                 onLockSettingsReady();
                 loadAdminDataAsync();
                 mOwners.systemReady();
-                if (isWorkProfileTelephonyFlagEnabled()) {
+                if (isWorkProfileTelephonyEnabled()) {
                     applyManagedSubscriptionsPolicyIfRequired();
                 }
                 break;
@@ -3534,26 +3536,21 @@
                 userId == UserHandle.USER_SYSTEM ? UserHandle.USER_ALL : userId);
         updatePermissionPolicyCache(userId);
         updateAdminCanGrantSensorsPermissionCache(userId);
-        final List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs;
-        boolean isManagedSubscription;
 
+        final List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs;
         synchronized (getLockObject()) {
             ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
             preferentialNetworkServiceConfigs = owner != null
                     ? owner.mPreferentialNetworkServiceConfigs
                     : List.of(PreferentialNetworkServiceConfig.DEFAULT);
-
-            isManagedSubscription = owner != null && owner.mManagedSubscriptionsPolicy != null
-                    && owner.mManagedSubscriptionsPolicy.getPolicyType()
-                    == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS;
         }
         updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfigs);
 
-        if (isManagedSubscription) {
-            String defaultDialerPackageName = getDefaultRoleHolderPackageName(
-                    com.android.internal.R.string.config_defaultDialer);
-            String defaultSmsPackageName = getDefaultRoleHolderPackageName(
-                    com.android.internal.R.string.config_defaultSms);
+        if (isProfileOwnerOfOrganizationOwnedDevice(userId)
+                && getManagedSubscriptionsPolicy().getPolicyType()
+                == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+            String defaultDialerPackageName = getOemDefaultDialerPackage();
+            String defaultSmsPackageName = getOemDefaultSmsPackage();
             updateDialerAndSmsManagedShortcutsOverrideCache(defaultDialerPackageName,
                     defaultSmsPackageName);
         }
@@ -7640,7 +7637,7 @@
         }
         mLockSettingsInternal.refreshStrongAuthTimeout(parentId);
 
-        if (isWorkProfileTelephonyFlagEnabled()) {
+        if (isWorkProfileTelephonyEnabled()) {
             clearManagedSubscriptionsPolicy();
             clearLauncherShortcutOverrides();
             updateTelephonyCrossProfileIntentFilters(parentId, UserHandle.USER_NULL, false);
@@ -10991,8 +10988,10 @@
             synchronized (mSubscriptionsChangedListenerLock) {
                 pw.println("Subscription changed listener : " + mSubscriptionsChangedListener);
             }
-            pw.println(
-                    "Flag enable_work_profile_telephony : " + isWorkProfileTelephonyFlagEnabled());
+            pw.println("DPM Flag enable_work_profile_telephony : "
+                    + isWorkProfileTelephonyDevicePolicyManagerFlagEnabled());
+            pw.println("Telephony Flag enable_work_profile_telephony : "
+                    + isWorkProfileTelephonySubscriptionManagerFlagEnabled());
 
             mHandler.post(() -> handleDump(pw));
             dumpResources(pw);
@@ -22705,11 +22704,24 @@
                 DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
     }
 
-    private static boolean isWorkProfileTelephonyFlagEnabled() {
-        return DeviceConfig.getBoolean(
-                NAMESPACE_DEVICE_POLICY_MANAGER,
-                ENABLE_WORK_PROFILE_TELEPHONY_FLAG,
-                DEFAULT_WORK_PROFILE_TELEPHONY_FLAG);
+    private boolean isWorkProfileTelephonyEnabled() {
+        return isWorkProfileTelephonyDevicePolicyManagerFlagEnabled()
+                && isWorkProfileTelephonySubscriptionManagerFlagEnabled();
+    }
+
+    private boolean isWorkProfileTelephonyDevicePolicyManagerFlagEnabled() {
+        return DeviceConfig.getBoolean(NAMESPACE_DEVICE_POLICY_MANAGER,
+                ENABLE_WORK_PROFILE_TELEPHONY_FLAG, DEFAULT_WORK_PROFILE_TELEPHONY_FLAG);
+    }
+
+    private boolean isWorkProfileTelephonySubscriptionManagerFlagEnabled() {
+        final long ident = mInjector.binderClearCallingIdentity();
+        try {
+            return DeviceConfig.getBoolean(NAMESPACE_TELEPHONY, ENABLE_WORK_PROFILE_TELEPHONY_FLAG,
+                    false);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
     }
 
     @Override
@@ -22822,7 +22834,7 @@
 
     @Override
     public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
-        if (isWorkProfileTelephonyFlagEnabled()) {
+        if (isWorkProfileTelephonyEnabled()) {
             synchronized (getLockObject()) {
                 ActiveAdmin admin = getProfileOwnerOfOrganizationOwnedDeviceLocked();
                 if (admin != null && admin.mManagedSubscriptionsPolicy != null) {
@@ -22836,7 +22848,7 @@
 
     @Override
     public void setManagedSubscriptionsPolicy(ManagedSubscriptionsPolicy policy) {
-        if (!isWorkProfileTelephonyFlagEnabled()) {
+        if (!isWorkProfileTelephonyEnabled()) {
             throw new UnsupportedOperationException("This api is not enabled");
         }
         CallerIdentity caller = getCallerIdentity();
@@ -22844,9 +22856,10 @@
                 "This policy can only be set by a profile owner on an organization-owned "
                         + "device.");
 
+        int parentUserId = getProfileParentId(caller.getUserId());
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
-            if (hasUserSetupCompleted(UserHandle.USER_SYSTEM) && !isAdminTestOnlyLocked(
+            if (hasUserSetupCompleted(parentUserId) && !isAdminTestOnlyLocked(
                     admin.info.getComponent(), caller.getUserId())) {
                 throw new IllegalStateException("Not allowed to apply this policy after setup");
             }
@@ -22868,7 +22881,6 @@
         if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
             final long id = mInjector.binderClearCallingIdentity();
             try {
-                int parentUserId = getProfileParentId(caller.getUserId());
                 installOemDefaultDialerAndSmsApp(caller.getUserId());
                 updateTelephonyCrossProfileIntentFilters(parentUserId, caller.getUserId(), true);
             } finally {
@@ -22879,10 +22891,8 @@
 
     private void installOemDefaultDialerAndSmsApp(int targetUserId) {
         try {
-            String defaultDialerPackageName = getDefaultRoleHolderPackageName(
-                    com.android.internal.R.string.config_defaultDialer);
-            String defaultSmsPackageName = getDefaultRoleHolderPackageName(
-                    com.android.internal.R.string.config_defaultSms);
+            String defaultDialerPackageName = getOemDefaultDialerPackage();
+            String defaultSmsPackageName = getOemDefaultSmsPackage();
 
             if (defaultDialerPackageName != null) {
                 mIPackageManager.installExistingPackageAsUser(defaultDialerPackageName,
@@ -22908,6 +22918,15 @@
         }
     }
 
+    private String getOemDefaultDialerPackage() {
+        TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+        return telecomManager.getSystemDialerPackage();
+    }
+
+    private String getOemDefaultSmsPackage() {
+        return mContext.getString(R.string.config_defaultSms);
+    }
+
     private void updateDialerAndSmsManagedShortcutsOverrideCache(
             String defaultDialerPackageName, String defaultSmsPackageName) {
 
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index fd31b22..d559b67 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -84,9 +84,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-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;
@@ -2904,108 +2901,6 @@
                 PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
     }
 
-    private static class TestDexModuleRegisterCallback
-            extends PackageManager.DexModuleRegisterCallback {
-        private String mDexModulePath = null;
-        private boolean mSuccess = false;
-        private String mMessage = null;
-        CountDownLatch doneSignal = new CountDownLatch(1);
-
-        @Override
-        public void onDexModuleRegistered(String dexModulePath, boolean success, String message) {
-            mDexModulePath = dexModulePath;
-            mSuccess = success;
-            mMessage = message;
-            doneSignal.countDown();
-        }
-
-        boolean waitTillDone() {
-            long startTime = System.currentTimeMillis();
-            while (System.currentTimeMillis() - startTime < MAX_WAIT_TIME) {
-                try {
-                    return doneSignal.await(MAX_WAIT_TIME, TimeUnit.MILLISECONDS);
-                } catch (InterruptedException e) {
-                    Log.i(TAG, "Interrupted during sleep", e);
-                }
-            }
-            return false;
-        }
-
-    }
-
-    // Verify that the base code path cannot be registered.
-    public void testRegisterDexModuleBaseCode() throws Exception {
-        PackageManager pm = getPm();
-        ApplicationInfo info = getContext().getApplicationInfo();
-        TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
-        pm.registerDexModule(info.getBaseCodePath(), callback);
-        assertTrue(callback.waitTillDone());
-        assertEquals(info.getBaseCodePath(), callback.mDexModulePath);
-        assertFalse("BaseCodePath should not be registered", callback.mSuccess);
-    }
-
-    // Verify that modules which are not own by the calling package are not registered.
-    public void testRegisterDexModuleNotOwningModule() throws Exception {
-        TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
-        String moduleBelongingToOtherPackage = "/data/user/0/com.google.android.gms/module.apk";
-        getPm().registerDexModule(moduleBelongingToOtherPackage, callback);
-        assertTrue(callback.waitTillDone());
-        assertEquals(moduleBelongingToOtherPackage, callback.mDexModulePath);
-        assertTrue(callback.waitTillDone());
-        assertFalse("Only modules belonging to the calling package can be registered",
-                callback.mSuccess);
-    }
-
-    // Verify that modules owned by the package are successfully registered.
-    public void testRegisterDexModuleSuccessfully() throws Exception {
-        ApplicationInfo info = getContext().getApplicationInfo();
-        // Copy the main apk into the data folder and use it as a "module".
-        File dexModuleDir = new File(info.dataDir, "module-dir");
-        File dexModule = new File(dexModuleDir, "module.apk");
-        try {
-            assertNotNull(FileUtils.createDir(
-                    dexModuleDir.getParentFile(), dexModuleDir.getName()));
-            Files.copy(Paths.get(info.getBaseCodePath()), dexModule.toPath(),
-                    StandardCopyOption.REPLACE_EXISTING);
-            TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
-            getPm().registerDexModule(dexModule.toString(), callback);
-            assertTrue(callback.waitTillDone());
-            assertEquals(dexModule.toString(), callback.mDexModulePath);
-            assertTrue(callback.waitTillDone());
-            assertTrue(callback.mMessage, callback.mSuccess);
-
-            // NOTE:
-            // This actually verifies internal behaviour which might change. It's not
-            // ideal but it's the best we can do since there's no other place we can currently
-            // write a better test.
-            for(String isa : getAppDexInstructionSets(info)) {
-                Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.odex"));
-                Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.vdex"));
-            }
-        } finally {
-            FileUtils.deleteContentsAndDir(dexModuleDir);
-        }
-    }
-
-    // If the module does not exist on disk we should get a failure.
-    public void testRegisterDexModuleNotExists() throws Exception {
-        ApplicationInfo info = getContext().getApplicationInfo();
-        String nonExistentApk = Paths.get(info.dataDir, "non-existent.apk").toString();
-        TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
-        getPm().registerDexModule(nonExistentApk, callback);
-        assertTrue(callback.waitTillDone());
-        assertEquals(nonExistentApk, callback.mDexModulePath);
-        assertTrue(callback.waitTillDone());
-        assertFalse("DexModule registration should fail", callback.mSuccess);
-    }
-
-    // If the module does not exist on disk we should get a failure.
-    public void testRegisterDexModuleNotExistsNoCallback() throws Exception {
-        ApplicationInfo info = getContext().getApplicationInfo();
-        String nonExistentApk = Paths.get(info.dataDir, "non-existent.apk").toString();
-        getPm().registerDexModule(nonExistentApk, null);
-    }
-
     @LargeTest
     public void testMinInstallableTargetSdkPass() throws Exception {
         // Test installing a package that meets the minimum installable sdk requirement
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy0/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy0/cpuinfo_cur_freq
deleted file mode 100644
index 80b164d..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy0/cpuinfo_cur_freq
+++ /dev/null
@@ -1 +0,0 @@
-"1.23
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy0/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy0/cpuinfo_max_freq
deleted file mode 100644
index 582d8c8..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy0/cpuinfo_max_freq
+++ /dev/null
@@ -1 +0,0 @@
-+2.5
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/cpuinfo_max_freq
deleted file mode 100644
index 749fce6..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/cpuinfo_max_freq
+++ /dev/null
@@ -1 +0,0 @@
-1000000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/scaling_cur_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/cpuinfo_cur_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/scaling_cur_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy2/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy2/cpuinfo_cur_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy2/cpuinfo_cur_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy2/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy2/cpuinfo_max_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy2/cpuinfo_max_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/cpuinfo_cur_freq
deleted file mode 100644
index dadd973..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/cpuinfo_cur_freq
+++ /dev/null
@@ -1 +0,0 @@
-1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/cpuinfo_max_freq
deleted file mode 100644
index a93d6f7..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/cpuinfo_max_freq
+++ /dev/null
@@ -1 +0,0 @@
-2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_cur_freq
index 573541a..dadd973 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_cur_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_cur_freq
@@ -1 +1 @@
-0
+1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_max_freq
index 573541a..a93d6f7 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_max_freq
@@ -1 +1 @@
-0
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/scaling_cur_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/cpuinfo_cur_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/scaling_cur_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/scaling_max_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/cpuinfo_max_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/scaling_max_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy2/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy2/cpuinfo_cur_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy2/cpuinfo_cur_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy2/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy2/cpuinfo_max_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy2/cpuinfo_max_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/cpuinfo_cur_freq
deleted file mode 100644
index 749fce6..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/cpuinfo_cur_freq
+++ /dev/null
@@ -1 +0,0 @@
-1000000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/cpuinfo_max_freq
deleted file mode 100644
index a93d6f7..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/cpuinfo_max_freq
+++ /dev/null
@@ -1 +0,0 @@
-2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_cur_freq
index 573541a..749fce6 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_cur_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_cur_freq
@@ -1 +1 @@
-0
+1000000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq
index 573541a..a93d6f7 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq
@@ -1 +1 @@
-0
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_cur_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/cpuinfo_cur_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_cur_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_max_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/cpuinfo_max_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_max_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/cpuinfo_cur_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/cpuinfo_cur_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/cpuinfo_max_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/cpuinfo_max_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/cpuinfo_cur_freq
deleted file mode 100644
index dadd973..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/cpuinfo_cur_freq
+++ /dev/null
@@ -1 +0,0 @@
-1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/cpuinfo_max_freq
deleted file mode 100644
index a93d6f7..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/cpuinfo_max_freq
+++ /dev/null
@@ -1 +0,0 @@
-2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_cur_freq
index 573541a..dadd973 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_cur_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_cur_freq
@@ -1 +1 @@
-0
+1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_max_freq
index 573541a..a93d6f7 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_max_freq
@@ -1 +1 @@
-0
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/scaling_cur_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/cpuinfo_cur_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/scaling_cur_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/scaling_max_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/cpuinfo_max_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/scaling_max_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy2/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy2/cpuinfo_cur_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy2/cpuinfo_cur_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy2/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy2/cpuinfo_max_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy2/cpuinfo_max_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/cpuinfo_cur_freq
deleted file mode 100644
index 749fce6..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/cpuinfo_cur_freq
+++ /dev/null
@@ -1 +0,0 @@
-1000000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/cpuinfo_max_freq
deleted file mode 100644
index a93d6f7..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/cpuinfo_max_freq
+++ /dev/null
@@ -1 +0,0 @@
-2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_cur_freq
index 573541a..749fce6 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_cur_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_cur_freq
@@ -1 +1 @@
-0
+1000000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_max_freq
index 573541a..a93d6f7 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_max_freq
@@ -1 +1 @@
-0
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/scaling_cur_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/cpuinfo_cur_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/scaling_cur_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/scaling_max_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/cpuinfo_max_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/scaling_max_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy2/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy2/cpuinfo_cur_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy2/cpuinfo_cur_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy2/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy2/cpuinfo_max_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy2/cpuinfo_max_freq
+++ /dev/null
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 f9f5325..dc31d53 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -2778,51 +2778,70 @@
             setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 1, getNewMockPendingIntent());
         }
 
-        final String otherUidPackage1 = "other.uid.package1";
-        final String otherUidPackage2 = "other.uid.package2";
-        final int otherUid = 1243;
+        final String otherPackage1 = "other.package1";
+        final String otherPackage2 = "other.package2";
+        final int otherAppId = 1243;
+        final int otherUser1 = 31;
+        final int otherUser2 = 8;
+        final int otherUid1 = UserHandle.getUid(otherUser1, otherAppId);
+        final int otherUid2 = UserHandle.getUid(otherUser2, otherAppId);
 
         registerAppIds(
-                new String[]{TEST_CALLING_PACKAGE, otherUidPackage1, otherUidPackage2},
-                new Integer[]{TEST_CALLING_UID, otherUid, otherUid}
+                new String[]{TEST_CALLING_PACKAGE, otherPackage1, otherPackage2},
+                new Integer[]{UserHandle.getAppId(TEST_CALLING_UID), otherAppId, otherAppId}
         );
 
         for (int i = 0; i < 9; i++) {
             setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 11, 0,
-                    getNewMockPendingIntent(otherUid, otherUidPackage1), 0, 0, otherUid,
-                    otherUidPackage1, null);
+                    getNewMockPendingIntent(otherUid1, otherPackage1), 0, 0, otherUid1,
+                    otherPackage1, null);
         }
 
         for (int i = 0; i < 8; i++) {
             setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 20, 0,
-                    getNewMockPendingIntent(otherUid, otherUidPackage2), 0, 0, otherUid,
-                    otherUidPackage2, null);
+                    getNewMockPendingIntent(otherUid1, otherPackage2), 0, 0, otherUid1,
+                    otherPackage2, null);
         }
 
-        assertEquals(27, mService.mAlarmStore.size());
+        for (int i = 0; i < 7; i++) {
+            setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 28, 0,
+                    getNewMockPendingIntent(otherUid2, otherPackage2), 0, 0, otherUid2,
+                    otherPackage2, null);
+        }
+
+        assertEquals(34, mService.mAlarmStore.size());
 
         try {
-            mBinder.removeAll(otherUidPackage1);
+            mBinder.removeAll(otherPackage1);
             fail("removeAll() for wrong package did not throw SecurityException");
         } catch (SecurityException se) {
             // Expected
         }
 
         try {
-            mBinder.removeAll(otherUidPackage2);
+            mBinder.removeAll(otherPackage2);
             fail("removeAll() for wrong package did not throw SecurityException");
         } catch (SecurityException se) {
             // Expected
         }
 
         mBinder.removeAll(TEST_CALLING_PACKAGE);
-        assertEquals(17, mService.mAlarmStore.size());
+        assertEquals(24, mService.mAlarmStore.size());
         assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(TEST_CALLING_PACKAGE)));
 
-        mTestCallingUid = otherUid;
-        mBinder.removeAll(otherUidPackage1);
-        assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(otherUidPackage1)));
-        assertEquals(8, mService.mAlarmStore.getCount(a -> a.matches(otherUidPackage2)));
+        mTestCallingUid = otherUid1;
+        mBinder.removeAll(otherPackage1);
+        assertEquals(15, mService.mAlarmStore.size());
+        assertEquals(15, mService.mAlarmStore.getCount(a -> a.matches(otherPackage2)));
+        assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(otherPackage1)));
+
+        mBinder.removeAll(otherPackage2);
+        assertEquals(7, mService.mAlarmStore.size());
+        assertEquals(7, mService.mAlarmStore.getCount(a -> a.matches(otherPackage2)));
+
+        mTestCallingUid = otherUid2;
+        mBinder.removeAll(otherPackage2);
+        assertEquals(0, mService.mAlarmStore.size());
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 8a5d3a6..3f84a20 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -50,7 +50,9 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -1078,7 +1080,8 @@
                 eq(getUidForPackage(PACKAGE_GREEN)), anyInt(), eq(Intent.ACTION_TIME_TICK),
                 eq(BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST),
                 eq(BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD),
-                anyLong(), anyLong(), anyLong(), anyInt()), times(1));
+                anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class), anyString()),
+                times(1));
     }
 
     private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 1c2dc34..0ab984b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -21,20 +21,26 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.res.Resources;
 import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
@@ -45,18 +51,18 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.provider.Settings;
-import android.testing.TestableContext;
 import android.util.FloatProperty;
 import android.view.Display;
 import android.view.DisplayInfo;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.ExtendedMockitoRule;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.RampAnimator.DualRampAnimator;
@@ -69,12 +75,12 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
@@ -92,9 +98,11 @@
     private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
     private static final float PROX_SENSOR_MAX_RANGE = 5;
 
+    private MockitoSession mSession;
     private OffsettableClock mClock;
     private TestLooper mTestLooper;
     private Handler mHandler;
+    private Context mContextSpy;
     private DisplayPowerControllerHolder mHolder;
     private Sensor mProxSensor;
 
@@ -111,38 +119,40 @@
     @Mock
     private PowerManager mPowerManagerMock;
     @Mock
+    private Resources mResourcesMock;
+    @Mock
     private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
 
     @Captor
     private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
 
-    @Rule
-    public final TestableContext mContext = new TestableContext(
-            InstrumentationRegistry.getInstrumentation().getContext());
-
-    @Rule
-    public final ExtendedMockitoRule mExtendedMockitoRule =
-            new ExtendedMockitoRule.Builder(this)
-                    .setStrictness(Strictness.LENIENT)
-                    .spyStatic(SystemProperties.class)
-                    .spyStatic(BatteryStatsService.class)
-                    .build();
-
     @Before
     public void setUp() throws Exception {
+        mSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .spyStatic(SystemProperties.class)
+                .spyStatic(LocalServices.class)
+                .spyStatic(BatteryStatsService.class)
+                .spyStatic(Settings.System.class)
+                .startMocking();
+        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mClock = new OffsettableClock.Stopped();
         mTestLooper = new TestLooper(mClock::now);
         mHandler = new Handler(mTestLooper.getLooper());
-
         addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
-        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
-                mCdsiMock);
 
-        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
+        when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
+        when(mContextSpy.getResources()).thenReturn(mResourcesMock);
 
         doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
                 SystemProperties.set(anyString(), any()));
+        doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
+                mCdsiMock).when(() -> LocalServices.getService(
+                ColorDisplayService.ColorDisplayServiceInternal.class));
         doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+        doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() ->
+                Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt()));
 
         setUpSensors();
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -150,8 +160,8 @@
 
     @After
     public void tearDown() {
+        mSession.finishMocking();
         LocalServices.removeServiceForTest(WindowManagerPolicy.class);
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
     }
 
     @Test
@@ -412,9 +422,11 @@
 
     @Test
     public void testDisplayBrightnessFollowers_AutomaticBrightness() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        doAnswer((Answer<Integer>) invocationOnMock ->
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
+                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
+                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                        eq(UserHandle.USER_CURRENT)));
         final float brightness = 0.4f;
         final float nits = 300;
         final float ambientLux = 3000;
@@ -531,9 +543,11 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        doAnswer((Answer<Integer>) invocationOnMock ->
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
+                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
+                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                        eq(UserHandle.USER_CURRENT)));
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
@@ -564,14 +578,17 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        doAnswer((Answer<Integer>) invocationOnMock ->
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
+                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
+                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                        eq(UserHandle.USER_CURRENT)));
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+        when(mResourcesMock.getBoolean(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing))
+                .thenReturn(true);
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
@@ -599,9 +616,11 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        doAnswer((Answer<Integer>) invocationOnMock ->
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)
+                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
+                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                        eq(UserHandle.USER_CURRENT)));
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
@@ -614,9 +633,11 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        doAnswer((Answer<Integer>) invocationOnMock ->
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
+                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
+                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                        eq(UserHandle.USER_CURRENT)));
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -654,6 +675,7 @@
 
     @Test
     public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
+        // New display device
         setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
                 mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
 
@@ -669,9 +691,9 @@
     public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
         float brightness = 0.3f;
         float nits = 500;
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
-                true);
+        when(mResourcesMock.getBoolean(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
+                .thenReturn(true);
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
 
@@ -693,6 +715,56 @@
         verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
     }
 
+    @Test
+    public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
+        float lux = 2000;
+        float brightness = 0.4f;
+        float nits = 500;
+        when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
+        when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
+        when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
+        when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1);
+        clearInvocations(mHolder.injector);
+
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        advanceTime(1);
+
+        verify(mHolder.injector).getAutomaticBrightnessController(
+                any(AutomaticBrightnessController.Callbacks.class),
+                any(Looper.class),
+                eq(mSensorManagerMock),
+                any(),
+                eq(mHolder.brightnessMappingStrategy),
+                anyInt(),
+                anyFloat(),
+                anyFloat(),
+                anyFloat(),
+                anyInt(),
+                anyInt(),
+                anyLong(),
+                anyLong(),
+                anyBoolean(),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                eq(mContextSpy),
+                any(HighBrightnessModeController.class),
+                any(BrightnessThrottler.class),
+                isNull(),
+                anyInt(),
+                anyInt(),
+                eq(lux),
+                eq(brightness)
+        );
+    }
+
     /**
      * Creates a mock and registers it to {@link LocalServices}.
      */
@@ -778,9 +850,9 @@
         final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
                 mock(ScreenOffBrightnessSensorController.class);
 
-        TestInjector injector = new TestInjector(displayPowerState, animator,
+        TestInjector injector = spy(new TestInjector(displayPowerState, animator,
                 automaticBrightnessController, wakelockController, brightnessMappingStrategy,
-                hysteresisLevels, screenOffBrightnessSensorController);
+                hysteresisLevels, screenOffBrightnessSensorController));
 
         final LogicalDisplay display = mock(LogicalDisplay.class);
         final DisplayDevice device = mock(DisplayDevice.class);
@@ -791,14 +863,15 @@
         setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
 
         final DisplayPowerController2 dpc = new DisplayPowerController2(
-                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
+                mContextSpy, injector, mDisplayPowerCallbacksMock, mHandler,
                 mSensorManagerMock, mDisplayBlankerMock, display,
                 mBrightnessTrackerMock, brightnessSetting, () -> {},
                 hbmMetadata, /* bootCompleted= */ false);
 
         return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
                 animator, automaticBrightnessController, wakelockController,
-                screenOffBrightnessSensorController, hbmMetadata);
+                screenOffBrightnessSensorController, hbmMetadata, brightnessMappingStrategy,
+                injector);
     }
 
     /**
@@ -815,6 +888,8 @@
         public final WakelockController wakelockController;
         public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
         public final HighBrightnessModeMetadata hbmMetadata;
+        public final BrightnessMappingStrategy brightnessMappingStrategy;
+        public final DisplayPowerController2.Injector injector;
 
         DisplayPowerControllerHolder(DisplayPowerController2 dpc, LogicalDisplay display,
                 DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
@@ -822,7 +897,9 @@
                 AutomaticBrightnessController automaticBrightnessController,
                 WakelockController wakelockController,
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
-                HighBrightnessModeMetadata hbmMetadata) {
+                HighBrightnessModeMetadata hbmMetadata,
+                BrightnessMappingStrategy brightnessMappingStrategy,
+                DisplayPowerController2.Injector injector) {
             this.dpc = dpc;
             this.display = display;
             this.displayPowerState = displayPowerState;
@@ -832,6 +909,8 @@
             this.wakelockController = wakelockController;
             this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
             this.hbmMetadata = hbmMetadata;
+            this.brightnessMappingStrategy = brightnessMappingStrategy;
+            this.injector = injector;
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 1bc1d10..c021ef6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -21,20 +21,26 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.res.Resources;
 import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
@@ -45,18 +51,18 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.provider.Settings;
-import android.testing.TestableContext;
 import android.util.FloatProperty;
 import android.view.Display;
 import android.view.DisplayInfo;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.ExtendedMockitoRule;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.RampAnimator.DualRampAnimator;
@@ -69,12 +75,12 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
@@ -92,9 +98,11 @@
     private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
     private static final float PROX_SENSOR_MAX_RANGE = 5;
 
+    private MockitoSession mSession;
     private OffsettableClock mClock;
     private TestLooper mTestLooper;
     private Handler mHandler;
+    private Context mContextSpy;
     private DisplayPowerControllerHolder mHolder;
     private Sensor mProxSensor;
 
@@ -111,38 +119,41 @@
     @Mock
     private PowerManager mPowerManagerMock;
     @Mock
+    private Resources mResourcesMock;
+    @Mock
     private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
 
     @Captor
     private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
 
-    @Rule
-    public final TestableContext mContext = new TestableContext(
-            InstrumentationRegistry.getInstrumentation().getContext());
-
-    @Rule
-    public final ExtendedMockitoRule mExtendedMockitoRule =
-            new ExtendedMockitoRule.Builder(this)
-                    .setStrictness(Strictness.LENIENT)
-                    .spyStatic(SystemProperties.class)
-                    .spyStatic(BatteryStatsService.class)
-                    .build();
-
     @Before
     public void setUp() throws Exception {
+        mSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .spyStatic(SystemProperties.class)
+                .spyStatic(LocalServices.class)
+                .spyStatic(BatteryStatsService.class)
+                .spyStatic(Settings.System.class)
+                .startMocking();
+        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mClock = new OffsettableClock.Stopped();
         mTestLooper = new TestLooper(mClock::now);
         mHandler = new Handler(mTestLooper.getLooper());
 
         addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
-        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
-                mCdsiMock);
 
-        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
+        when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
+        when(mContextSpy.getResources()).thenReturn(mResourcesMock);
 
         doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
                 SystemProperties.set(anyString(), any()));
+        doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
+                mCdsiMock).when(() -> LocalServices.getService(
+                ColorDisplayService.ColorDisplayServiceInternal.class));
         doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+        doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() ->
+                Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt()));
 
         setUpSensors();
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -150,8 +161,8 @@
 
     @After
     public void tearDown() {
+        mSession.finishMocking();
         LocalServices.removeServiceForTest(WindowManagerPolicy.class);
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
     }
 
     @Test
@@ -414,9 +425,11 @@
 
     @Test
     public void testDisplayBrightnessFollowers_AutomaticBrightness() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        doAnswer((Answer<Integer>) invocationOnMock ->
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
+                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
+                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                        eq(UserHandle.USER_CURRENT)));
         final float brightness = 0.4f;
         final float nits = 300;
         final float ambientLux = 3000;
@@ -534,9 +547,11 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        doAnswer((Answer<Integer>) invocationOnMock ->
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
+                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
+                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                        eq(UserHandle.USER_CURRENT)));
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
@@ -567,14 +582,17 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        doAnswer((Answer<Integer>) invocationOnMock ->
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
+                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
+                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                        eq(UserHandle.USER_CURRENT)));
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+        when(mResourcesMock.getBoolean(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing))
+                .thenReturn(true);
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
@@ -602,9 +620,11 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        doAnswer((Answer<Integer>) invocationOnMock ->
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)
+                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
+                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                        eq(UserHandle.USER_CURRENT)));
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
@@ -617,10 +637,11 @@
 
     @Test
     public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
+        doAnswer((Answer<Integer>) invocationOnMock ->
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
+                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
+                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                        eq(UserHandle.USER_CURRENT)));
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -658,6 +679,7 @@
 
     @Test
     public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
+        // New display device
         setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
                 mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
 
@@ -673,10 +695,9 @@
     public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
         float brightness = 0.3f;
         float nits = 500;
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
-                true);
-
+        when(mResourcesMock.getBoolean(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
+                .thenReturn(true);
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
 
@@ -698,6 +719,56 @@
         verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
     }
 
+    @Test
+    public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
+        float lux = 2000;
+        float brightness = 0.4f;
+        float nits = 500;
+        when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
+        when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
+        when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
+        when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1);
+        clearInvocations(mHolder.injector);
+
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        advanceTime(1);
+
+        verify(mHolder.injector).getAutomaticBrightnessController(
+                any(AutomaticBrightnessController.Callbacks.class),
+                any(Looper.class),
+                eq(mSensorManagerMock),
+                any(),
+                eq(mHolder.brightnessMappingStrategy),
+                anyInt(),
+                anyFloat(),
+                anyFloat(),
+                anyFloat(),
+                anyInt(),
+                anyInt(),
+                anyLong(),
+                anyLong(),
+                anyBoolean(),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                eq(mContextSpy),
+                any(HighBrightnessModeController.class),
+                any(BrightnessThrottler.class),
+                isNull(),
+                anyInt(),
+                anyInt(),
+                eq(lux),
+                eq(brightness)
+        );
+    }
+
     /**
      * Creates a mock and registers it to {@link LocalServices}.
      */
@@ -782,9 +853,9 @@
         final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
                 mock(ScreenOffBrightnessSensorController.class);
 
-        DisplayPowerController.Injector injector = new TestInjector(displayPowerState, animator,
+        DisplayPowerController.Injector injector = spy(new TestInjector(displayPowerState, animator,
                 automaticBrightnessController, brightnessMappingStrategy, hysteresisLevels,
-                screenOffBrightnessSensorController);
+                screenOffBrightnessSensorController));
 
         final LogicalDisplay display = mock(LogicalDisplay.class);
         final DisplayDevice device = mock(DisplayDevice.class);
@@ -795,14 +866,14 @@
         setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
 
         final DisplayPowerController dpc = new DisplayPowerController(
-                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
+                mContextSpy, injector, mDisplayPowerCallbacksMock, mHandler,
                 mSensorManagerMock, mDisplayBlankerMock, display,
                 mBrightnessTrackerMock, brightnessSetting, () -> {},
                 hbmMetadata, /* bootCompleted= */ false);
 
         return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
                 animator, automaticBrightnessController, screenOffBrightnessSensorController,
-                hbmMetadata);
+                hbmMetadata, brightnessMappingStrategy, injector);
     }
 
     /**
@@ -818,13 +889,17 @@
         public final AutomaticBrightnessController automaticBrightnessController;
         public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
         public final HighBrightnessModeMetadata hbmMetadata;
+        public final BrightnessMappingStrategy brightnessMappingStrategy;
+        public final DisplayPowerController.Injector injector;
 
         DisplayPowerControllerHolder(DisplayPowerController dpc, LogicalDisplay display,
                 DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
                 DualRampAnimator<DisplayPowerState> animator,
                 AutomaticBrightnessController automaticBrightnessController,
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
-                HighBrightnessModeMetadata hbmMetadata) {
+                HighBrightnessModeMetadata hbmMetadata,
+                BrightnessMappingStrategy brightnessMappingStrategy,
+                DisplayPowerController.Injector injector) {
             this.dpc = dpc;
             this.display = display;
             this.displayPowerState = displayPowerState;
@@ -833,6 +908,8 @@
             this.automaticBrightnessController = automaticBrightnessController;
             this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
             this.hbmMetadata = hbmMetadata;
+            this.brightnessMappingStrategy = brightnessMappingStrategy;
+            this.injector = injector;
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
index d5aa7fe..9a7ee4d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -47,6 +47,7 @@
 import android.os.HandlerThread;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.internal.util.IndentingPrintWriter;
@@ -56,6 +57,7 @@
 import com.android.server.pm.dex.DexoptOptions;
 
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -126,6 +128,10 @@
 
     @Before
     public void setUp() throws Exception {
+        // These tests are only applicable to the legacy BackgroundDexOptService and cannot be run
+        // when ART Service is enabled.
+        Assume.assumeFalse(SystemProperties.getBoolean("dalvik.vm.useartservice", false));
+
         when(mInjector.getCallingUid()).thenReturn(Process.FIRST_APPLICATION_UID);
         when(mInjector.getContext()).thenReturn(mContext);
         when(mInjector.getDexOptHelper()).thenReturn(mDexOptHelper);
diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
index 8f07238..3ad24de 100644
--- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
@@ -80,7 +80,9 @@
             asAdapter = mMockAudioSystem;
         }
         mSpatHelper = new SpatializerHelper(mMockAudioService, asAdapter,
-                false /*headTrackingEnabledByDefault*/);
+                true /*binauralEnabledDefault*/,
+                true /*transauralEnabledDefault*/,
+                false /*headTrackingEnabledDefault*/);
 
     }
 
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 aaabb28..4f74ef8 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5002,6 +5002,8 @@
         configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
                 FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "true", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_TELEPHONY,
+                FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "true", false);
         // Even if the caller is the managed profile, the current user is the user 0
         when(getServices().iactivityManager.getCurrentUser())
                 .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
@@ -5064,6 +5066,8 @@
         verify(getServices().subscriptionManager).setSubscriptionUserHandle(0, null);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
                 FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "false", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_TELEPHONY,
+                FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "false", false);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 94d30bb..6d2ce7f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -34,7 +34,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -65,12 +67,14 @@
 import android.hardware.display.IDisplayManagerCallback;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
+import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.MessageQueue;
 import android.os.Process;
+import android.os.RemoteException;
 import android.view.ContentRecordingSession;
 import android.view.Display;
 import android.view.DisplayCutout;
@@ -1024,11 +1028,14 @@
     }
 
     @Test
-    public void testCreateVirtualDisplay_setContentRecordingSessionSuccess() throws Exception {
+    public void testCreateVirtualDisplay_setContentRecordingSessionSuccess()
+            throws RemoteException {
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
         when(mMockWindowManagerInternal
                 .setContentRecordingSession(any(ContentRecordingSession.class)))
                 .thenReturn(true);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
 
         final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
                 VIRTUAL_DISPLAY_NAME, 600, 800, 320);
@@ -1042,17 +1049,19 @@
 
         DisplayManagerService.BinderService binderService = displayManager.new BinderService();
         final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
 
         assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
     }
 
     @Test
-    public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws Exception {
+    public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws RemoteException {
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
         when(mMockWindowManagerInternal
                 .setContentRecordingSession(any(ContentRecordingSession.class)))
                 .thenReturn(false);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
 
         final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
                 VIRTUAL_DISPLAY_NAME, 600, 800, 320);
@@ -1066,11 +1075,96 @@
 
         DisplayManagerService.BinderService binderService = displayManager.new BinderService();
         final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
 
         assertThat(displayId).isEqualTo(Display.INVALID_DISPLAY);
     }
 
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noFlags() {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        // Set no flags for the VirtualDisplay.
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+        builder.setContentRecordingSession(
+                ContentRecordingSession.createDisplaySession(new Binder("")));
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        // Pass in a null projection.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+        // VirtualDisplay is created but not for mirroring.
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockWindowManagerInternal, never()).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noMirroringFlag() {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        // Set a non-mirroring flag for the VirtualDisplay.
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+        builder.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+        builder.setContentRecordingSession(
+                ContentRecordingSession.createDisplaySession(new Binder("")));
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        // Pass in a null projection.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+        // VirtualDisplay is created but not for mirroring.
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockWindowManagerInternal, never()).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSession_projection_noMirroringFlag()
+            throws RemoteException {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        when(mMockWindowManagerInternal
+                .setContentRecordingSession(any(ContentRecordingSession.class)))
+                .thenReturn(true);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+        // Set no flags for the VirtualDisplay.
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+        builder.setContentRecordingSession(
+                ContentRecordingSession.createDisplaySession(new Binder("")));
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        // Pass in a non-null projection.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+        // VirtualDisplay is created for mirroring.
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+    }
+
     /**
      * Tests that the virtual display is created with
      * {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 0325ba6..f9b76f4 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.pm.ComponentInfo;
 import android.content.pm.PackageManager;
@@ -255,15 +256,26 @@
      * Show switch to managed profile dialog if subscription is associated with managed profile.
      *
      * @param context Context object
-     * @param subId   subscription id
+     * @param subId subscription id
+     * @param callingUid uid for the calling app
+     * @param callingPackage package name of the calling app
      */
-    public static void showErrorIfSubscriptionAssociatedWithManagedProfile(Context context,
-            int subId) {
+    public static void showSwitchToManagedProfileDialogIfAppropriate(Context context,
+            int subId, int callingUid, String callingPackage) {
         if (!isSwitchToManagedProfileDialogFlagEnabled()) {
             return;
         }
         final long token = Binder.clearCallingIdentity();
         try {
+            UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
+            // We only want to show this dialog, while user actually trying to send the message from
+            // a messaging app, in other cases this dialog don't make sense.
+            if (!TelephonyUtils.isUidForeground(context, callingUid)
+                    || !TelephonyUtils.isPackageSMSRoleHolderForUser(context, callingPackage,
+                    callingUserHandle)) {
+                return;
+            }
+
             SubscriptionManager subscriptionManager = context.getSystemService(
                     SubscriptionManager.class);
             UserHandle associatedUserHandle = subscriptionManager.getSubscriptionUserHandle(subId);
@@ -295,22 +307,25 @@
                 "enable_switch_to_managed_profile_dialog", false);
     }
 
-    /**
-     * Check if the process with given uid is foreground.
-     *
-     * @param context context
-     * @param uid     the caller uid
-     * @return true if the process with uid is foreground, false otherwise.
-     */
-    public static boolean isUidForeground(Context context, int uid) {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            ActivityManager am = context.getSystemService(ActivityManager.class);
-            boolean result = am != null && am.getUidImportance(uid)
-                    == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(token);
+    private static boolean isUidForeground(Context context, int uid) {
+        ActivityManager am = context.getSystemService(ActivityManager.class);
+        boolean result = am != null && am.getUidImportance(uid)
+                == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+        return result;
+    }
+
+    private static boolean isPackageSMSRoleHolderForUser(Context context, String callingPackage,
+            UserHandle user) {
+        RoleManager roleManager = context.getSystemService(RoleManager.class);
+        final List<String> smsRoleHolder = roleManager.getRoleHoldersAsUser(
+                RoleManager.ROLE_SMS, user);
+
+        // ROLE_SMS is an exclusive role per user, so there would just be one entry in the
+        // retuned list if not empty
+        if (!smsRoleHolder.isEmpty() && callingPackage.equals(smsRoleHolder.get(0))) {
+            return true;
         }
+        return false;
+
     }
 }
\ No newline at end of file
diff --git a/tests/componentalias/Android.bp b/tests/componentalias/Android.bp
index e5eb3c7..7af76e1 100644
--- a/tests/componentalias/Android.bp
+++ b/tests/componentalias/Android.bp
@@ -16,6 +16,9 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+// TODO: Delete this file. It's no longer needed, but removing it on udc-dev will cause
+// a conflict on master.
+
 java_defaults {
     name: "ComponentAliasTests_defaults",
     static_libs: [
@@ -34,54 +37,3 @@
     ],
     platform_apis: true, // We use hidden APIs in the test.
 }
-
-// We build three APKs from the exact same source files, so these APKs contain the exact same tests.
-// And we run the tests on each APK, so that we can test various situations:
-// - When the alias is in the same package, target in the same package.
-// - When the alias is in the same package, target in another package.
-// - When the alias is in another package, which also contains the target.
-// - When the alias is in another package, and the target is in yet another package.
-// etc etc...
-
-android_test {
-    name: "ComponentAliasTests",
-    defaults: [
-        "ComponentAliasTests_defaults",
-    ],
-    package_name: "android.content.componentalias.tests",
-    manifest: "AndroidManifest.xml",
-    additional_manifests: [
-        "AndroidManifest_main.xml",
-        "AndroidManifest_service_aliases.xml",
-        "AndroidManifest_service_targets.xml",
-    ],
-    test_config_template: "AndroidTest-template.xml",
-}
-
-android_test {
-    name: "ComponentAliasTests1",
-    defaults: [
-        "ComponentAliasTests_defaults",
-    ],
-    package_name: "android.content.componentalias.tests.sub1",
-    manifest: "AndroidManifest.xml",
-    additional_manifests: [
-        "AndroidManifest_sub1.xml",
-        "AndroidManifest_service_targets.xml",
-    ],
-    test_config_template: "AndroidTest-template.xml",
-}
-
-android_test {
-    name: "ComponentAliasTests2",
-    defaults: [
-        "ComponentAliasTests_defaults",
-    ],
-    package_name: "android.content.componentalias.tests.sub2",
-    manifest: "AndroidManifest.xml",
-    additional_manifests: [
-        "AndroidManifest_sub2.xml",
-        "AndroidManifest_service_targets.xml",
-    ],
-    test_config_template: "AndroidTest-template.xml",
-}
diff --git a/tests/componentalias/AndroidManifest.xml b/tests/componentalias/AndroidManifest.xml
deleted file mode 100755
index 7bb83a3..0000000
--- a/tests/componentalias/AndroidManifest.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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="android.content.componentalias.tests" >
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-        <property android:name="com.android.EXPERIMENTAL_COMPONENT_ALIAS_OPT_IN" android:value="true" />
-    </application>
-</manifest>
diff --git a/tests/componentalias/AndroidManifest_main.xml b/tests/componentalias/AndroidManifest_main.xml
deleted file mode 100755
index 70e817e..0000000
--- a/tests/componentalias/AndroidManifest_main.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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="android.content.componentalias.tests" >
-
-    <application>
-    </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.content.componentalias.tests" >
-    </instrumentation>
-</manifest>
diff --git a/tests/componentalias/AndroidManifest_service_aliases.xml b/tests/componentalias/AndroidManifest_service_aliases.xml
deleted file mode 100644
index c96f173..0000000
--- a/tests/componentalias/AndroidManifest_service_aliases.xml
+++ /dev/null
@@ -1,81 +0,0 @@
-<?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="android.content.componentalias.tests" >
-    <application>
-        <!--
-            Note the alias components are essentially just placeholders, so the APKs don't have to
-            have the implementation classes.
-        -->
-        <service android:name=".s.Alias00" android:exported="true" android:enabled="true" >
-            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests/android.content.componentalias.tests.s.Target00" />
-            <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_00" /></intent-filter>
-        </service>
-        <service android:name=".s.Alias01" android:exported="true" android:enabled="true" >
-            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.s.Target01" />
-            <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_01" /></intent-filter>
-        </service>
-        <service android:name=".s.Alias02" android:exported="true" android:enabled="true" >
-            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.s.Target02" />
-            <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_02" /></intent-filter>
-        </service>
-        <service android:name=".s.Alias03" android:exported="true" android:enabled="true" >
-            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.s.Target03" />
-            <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_03" /></intent-filter>
-        </service>
-        <service android:name=".s.Alias04" android:exported="true" android:enabled="true" >
-            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.s.Target04" />
-            <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_04" /></intent-filter>
-        </service>
-
-        <receiver android:name=".b.Alias00" android:exported="true" android:enabled="true" >
-            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests/android.content.componentalias.tests.b.Target00" />
-            <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_00" /></intent-filter>
-            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
-        </receiver>
-        <receiver android:name=".b.Alias01" android:exported="true" android:enabled="true" >
-            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.b.Target01" />
-            <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_01" /></intent-filter>
-            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
-        </receiver>
-        <receiver android:name=".b.Alias02" android:exported="true" android:enabled="true" >
-            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.b.Target02" />
-            <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_02" /></intent-filter>
-            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
-        </receiver>
-        <receiver android:name=".b.Alias03" android:exported="true" android:enabled="true" >
-            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.b.Target03" />
-            <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_03" /></intent-filter>
-            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
-        </receiver>
-        <receiver android:name=".b.Alias04" android:exported="true" android:enabled="true" >
-            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.b.Target04" />
-            <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_04" /></intent-filter>
-            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
-        </receiver>
-    </application>
-</manifest>
diff --git a/tests/componentalias/AndroidManifest_service_targets.xml b/tests/componentalias/AndroidManifest_service_targets.xml
deleted file mode 100644
index 24c0432..0000000
--- a/tests/componentalias/AndroidManifest_service_targets.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?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="android.content.componentalias.tests" >
-    <application>
-        <service android:name=".s.Target00" android:exported="true" android:enabled="true" >
-        </service>
-        <service android:name=".s.Target01" android:exported="true" android:enabled="true" >
-        </service>
-        <service android:name=".s.Target02" android:exported="true" android:enabled="true" >
-        </service>
-        <service android:name=".s.Target03" android:exported="true" android:enabled="true" >
-        </service>
-        <service android:name=".s.Target04" android:exported="true" android:enabled="true" >
-        </service>
-
-        <!--
-            Due to http://go/intents-match-intent-filters-guide, the target intent has to have
-            an intent filter that matches the original intent. (modulo the package name)
-            This restriction shouldn't exist in the final version.
-        -->
-        <receiver android:name=".b.Target00" android:exported="true" android:enabled="true" >
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_00" /></intent-filter>
-            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
-        </receiver>
-        <receiver android:name=".b.Target01" android:exported="true" android:enabled="true" >
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_01" /></intent-filter>
-            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
-        </receiver>
-        <receiver android:name=".b.Target02" android:exported="true" android:enabled="true" >
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_02" /></intent-filter>
-            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
-        </receiver>
-        <receiver android:name=".b.Target03" android:exported="true" android:enabled="true" >
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_03" /></intent-filter>
-            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
-        </receiver>
-        <receiver android:name=".b.Target04" android:exported="true" android:enabled="true" >
-            <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_04" /></intent-filter>
-            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
-        </receiver>
-    </application>
-</manifest>
diff --git a/tests/componentalias/AndroidManifest_sub1.xml b/tests/componentalias/AndroidManifest_sub1.xml
deleted file mode 100755
index 21616f5..0000000
--- a/tests/componentalias/AndroidManifest_sub1.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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="android.content.componentalias.tests" >
-
-    <application>
-    </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.content.componentalias.tests.sub1" >
-    </instrumentation>
-</manifest>
diff --git a/tests/componentalias/AndroidManifest_sub2.xml b/tests/componentalias/AndroidManifest_sub2.xml
deleted file mode 100755
index c11b0cd..0000000
--- a/tests/componentalias/AndroidManifest_sub2.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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="android.content.componentalias.tests" >
-
-    <application>
-    </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.content.componentalias.tests.sub2" >
-    </instrumentation>
-</manifest>
diff --git a/tests/componentalias/AndroidTest-template.xml b/tests/componentalias/AndroidTest-template.xml
deleted file mode 100644
index afdfe79..0000000
--- a/tests/componentalias/AndroidTest-template.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="ComponentAliasTests.apk" />
-        <option name="test-file-name" value="ComponentAliasTests1.apk" />
-        <option name="test-file-name" value="ComponentAliasTests2.apk" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <!-- Exempt the helper APKs from the BG restriction, so they can start BG services. -->
-        <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests" />
-        <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.sub1" />
-        <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.sub2" />
-
-        <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests" />
-        <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests.sub1" />
-        <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests.sub2" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="{PACKAGE}" />
-        <option name="runtime-hint" value="2m" />
-        <option name="isolated-storage" value="false" />
-    </test>
-</configuration>
diff --git a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java b/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java
deleted file mode 100644
index 99322ee..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java
+++ /dev/null
@@ -1,103 +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.content.componentalias.tests;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Build;
-import android.provider.DeviceConfig;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.DeviceConfigStateHelper;
-import com.android.compatibility.common.util.ShellUtils;
-import com.android.compatibility.common.util.TestUtils;
-
-import org.junit.AfterClass;
-import org.junit.Assume;
-import org.junit.Before;
-
-import java.util.function.Consumer;
-
-public class BaseComponentAliasTest {
-    protected static final Context sContext = InstrumentationRegistry.getTargetContext();
-
-    protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper(
-            DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS);
-    @Before
-    public void enableComponentAliasWithCompatFlag() throws Exception {
-        Assume.assumeTrue(Build.isDebuggable());
-        ShellUtils.runShellCommand(
-                "am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android");
-        sDeviceConfig.set("enable_experimental_component_alias", "");
-        sDeviceConfig.set("component_alias_overrides", "");
-
-        // Make sure the feature is actually enabled, and the aliases are loaded.
-        TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
-            String out = ShellUtils.runShellCommand("dumpsys activity component-alias");
-
-            return out.contains("Enabled: true")
-                    && out.contains("android.content.componentalias.tests/.b.Alias04")
-                    && out.contains("android.content.componentalias.tests/.s.Alias04");
-        });
-        ShellUtils.runShellCommand("am wait-for-broadcast-idle");
-    }
-
-    @AfterClass
-    public static void restoreDeviceConfig() throws Exception {
-        ShellUtils.runShellCommand(
-                "am compat disable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android");
-        sDeviceConfig.close();
-    }
-
-    protected static void log(String message) {
-        Log.i(ComponentAliasTestCommon.TAG, "[" + sContext.getPackageName() + "] " + message);
-    }
-
-    /**
-     * Defines a test target.
-     */
-    public static class Combo {
-        public final ComponentName alias;
-        public final ComponentName target;
-        public final String action;
-
-        public Combo(ComponentName alias, ComponentName target, String action) {
-            this.alias = alias;
-            this.target = target;
-            this.action = action;
-        }
-
-        @Override
-        public String toString() {
-            return "Combo{"
-                    + "alias=" + toString(alias)
-                    + ", target=" + toString(target)
-                    + ", action='" + action + '\''
-                    + '}';
-        }
-
-        private static String toString(ComponentName cn) {
-            return cn == null ? "[null]" : cn.flattenToShortString();
-        }
-
-        public void apply(Consumer<Combo> callback) {
-            log("Testing for: " + this);
-            callback.accept(this);
-        }
-    }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java
deleted file mode 100644
index 7d5e0b9..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java
+++ /dev/null
@@ -1,110 +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.content.componentalias.tests;
-
-import static android.content.componentalias.tests.ComponentAliasTestCommon.MAIN_PACKAGE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB1_PACKAGE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB2_PACKAGE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.ComponentName;
-import android.content.Intent;
-
-import com.android.compatibility.common.util.BroadcastMessenger.Receiver;
-
-import org.junit.Test;
-
-import java.util.function.Consumer;
-
-public class ComponentAliasBroadcastTest extends BaseComponentAliasTest {
-    private void forEachCombo(Consumer<Combo> callback) {
-        new Combo(
-                new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias00"),
-                new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Target00"),
-                MAIN_PACKAGE + ".IS_RECEIVER_00").apply(callback);
-
-        new Combo(
-                new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias01"),
-                new ComponentName(SUB1_PACKAGE, MAIN_PACKAGE + ".b.Target01"),
-                MAIN_PACKAGE + ".IS_RECEIVER_01").apply(callback);
-        new Combo(
-                new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias02"),
-                new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".b.Target02"),
-                MAIN_PACKAGE + ".IS_RECEIVER_02").apply(callback);
-    }
-
-    @Test
-    public void testBroadcast_explicitComponentName() {
-        forEachCombo((c) -> {
-            Intent i = new Intent().setComponent(c.alias);
-            i.setAction("ACTION_BROADCAST");
-            ComponentAliasMessage m;
-
-            try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) {
-                log("Sending: " + i);
-                sContext.sendBroadcast(i);
-
-                m = receiver.waitForNextMessage();
-
-                assertThat(m.getMethodName()).isEqualTo("onReceive");
-                assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString());
-
-                // The broadcast intent will always have the receiving component name set.
-                assertThat(m.getIntent().getComponent()).isEqualTo(c.target);
-
-                receiver.ensureNoMoreMessages();
-            }
-        });
-    }
-
-    @Test
-    public void testBroadcast_explicitPackageName() {
-        forEachCombo((c) -> {
-            // In this test, we only set the package name to the intent.
-            // If the alias and target are the same package, the intent will be sent to both of them
-            // *and* the one to the alias is redirected to the target, so the target will receive
-            // the intent twice. This case is haled at *1 below.
-
-
-            Intent i = new Intent().setPackage(c.alias.getPackageName());
-            i.setAction(c.action);
-            ComponentAliasMessage m;
-
-            try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) {
-                log("Sending broadcast: " + i);
-                sContext.sendBroadcast(i);
-
-                m = receiver.waitForNextMessage();
-
-                assertThat(m.getMethodName()).isEqualTo("onReceive");
-                assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString());
-                assertThat(m.getIntent().getComponent()).isEqualTo(c.target);
-
-                // *1 -- if the alias and target are in the same package, we expect one more
-                // message.
-                if (c.alias.getPackageName().equals(c.target.getPackageName())) {
-                    m = receiver.waitForNextMessage();
-                    assertThat(m.getMethodName()).isEqualTo("onReceive");
-                    assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString());
-                    assertThat(m.getIntent().getComponent()).isEqualTo(c.target);
-                }
-                receiver.ensureNoMoreMessages();
-            }
-        });
-    }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java
deleted file mode 100644
index ee20379..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests;
-
-import android.os.Build;
-import android.provider.DeviceConfig;
-
-import com.android.compatibility.common.util.DeviceConfigStateHelper;
-import com.android.compatibility.common.util.ShellUtils;
-import com.android.compatibility.common.util.TestUtils;
-
-import org.junit.AfterClass;
-import org.junit.Assume;
-import org.junit.Test;
-
-public class ComponentAliasEnableWithDeviceConfigTest {
-    protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper(
-            DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS);
-
-    @AfterClass
-    public static void restoreDeviceConfig() throws Exception {
-        sDeviceConfig.close();
-    }
-
-    @Test
-    public void enableComponentAliasWithCompatFlag() throws Exception {
-        Assume.assumeTrue(Build.isDebuggable());
-
-        sDeviceConfig.set("component_alias_overrides", "");
-
-        // First, disable with both compat-id and device config.
-        ShellUtils.runShellCommand(
-                "am compat disable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android");
-        sDeviceConfig.set("enable_experimental_component_alias", "");
-
-        TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
-            return ShellUtils.runShellCommand("dumpsys activity component-alias")
-                    .indexOf("Enabled: false") > 0;
-        });
-
-        // Then, enable by device config.
-        sDeviceConfig.set("enable_experimental_component_alias", "true");
-
-        // Make sure the feature is actually enabled.
-        TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
-            return ShellUtils.runShellCommand("dumpsys activity component-alias")
-                    .indexOf("Enabled: true") > 0;
-        });
-    }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java
deleted file mode 100644
index d41696f..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java
+++ /dev/null
@@ -1,215 +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.content.componentalias.tests;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.DataClass;
-
-/**
- * Parcelabe containing a "message" that's meant to be delivered via BroadcastMessenger.
- *
- * To add a new field, just add a private member field, and run:
- * codegen $ANDROID_BUILD_TOP/frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java
- */
-@DataClass(
-        genConstructor = false,
-        genSetters = true,
-        genToString = true,
-        genAidl = false)
-public final class ComponentAliasMessage implements Parcelable {
-    public ComponentAliasMessage() {
-    }
-
-    @Nullable
-    private String mMessage;
-
-    @Nullable
-    private String mMethodName;
-
-    @Nullable
-    private String mSenderIdentity;
-
-    @Nullable
-    private Intent mIntent;
-
-    @Nullable
-    private ComponentName mComponent;
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    @DataClass.Generated.Member
-    public @Nullable String getMessage() {
-        return mMessage;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable String getMethodName() {
-        return mMethodName;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable String getSenderIdentity() {
-        return mSenderIdentity;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable Intent getIntent() {
-        return mIntent;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable ComponentName getComponent() {
-        return mComponent;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull ComponentAliasMessage setMessage(@NonNull String value) {
-        mMessage = value;
-        return this;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull ComponentAliasMessage setMethodName(@NonNull String value) {
-        mMethodName = value;
-        return this;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull ComponentAliasMessage setSenderIdentity(@NonNull String value) {
-        mSenderIdentity = value;
-        return this;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull ComponentAliasMessage setIntent(@NonNull Intent value) {
-        mIntent = value;
-        return this;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull ComponentAliasMessage setComponent(@NonNull ComponentName value) {
-        mComponent = value;
-        return this;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public String toString() {
-        // You can override field toString logic by defining methods like:
-        // String fieldNameToString() { ... }
-
-        return "ComponentAliasMessage { " +
-                "message = " + mMessage + ", " +
-                "methodName = " + mMethodName + ", " +
-                "senderIdentity = " + mSenderIdentity + ", " +
-                "intent = " + mIntent + ", " +
-                "component = " + mComponent +
-        " }";
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        byte flg = 0;
-        if (mMessage != null) flg |= 0x1;
-        if (mMethodName != null) flg |= 0x2;
-        if (mSenderIdentity != null) flg |= 0x4;
-        if (mIntent != null) flg |= 0x8;
-        if (mComponent != null) flg |= 0x10;
-        dest.writeByte(flg);
-        if (mMessage != null) dest.writeString(mMessage);
-        if (mMethodName != null) dest.writeString(mMethodName);
-        if (mSenderIdentity != null) dest.writeString(mSenderIdentity);
-        if (mIntent != null) dest.writeTypedObject(mIntent, flags);
-        if (mComponent != null) dest.writeTypedObject(mComponent, flags);
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int describeContents() { return 0; }
-
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    @DataClass.Generated.Member
-    /* package-private */ ComponentAliasMessage(@NonNull Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        byte flg = in.readByte();
-        String message = (flg & 0x1) == 0 ? null : in.readString();
-        String methodName = (flg & 0x2) == 0 ? null : in.readString();
-        String senderIdentity = (flg & 0x4) == 0 ? null : in.readString();
-        Intent intent = (flg & 0x8) == 0 ? null : (Intent) in.readTypedObject(Intent.CREATOR);
-        ComponentName component = (flg & 0x10) == 0 ? null : (ComponentName) in.readTypedObject(ComponentName.CREATOR);
-
-        this.mMessage = message;
-        this.mMethodName = methodName;
-        this.mSenderIdentity = senderIdentity;
-        this.mIntent = intent;
-        this.mComponent = component;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<ComponentAliasMessage> CREATOR
-            = new Parcelable.Creator<ComponentAliasMessage>() {
-        @Override
-        public ComponentAliasMessage[] newArray(int size) {
-            return new ComponentAliasMessage[size];
-        }
-
-        @Override
-        public ComponentAliasMessage createFromParcel(@NonNull Parcel in) {
-            return new ComponentAliasMessage(in);
-        }
-    };
-
-    @DataClass.Generated(
-            time = 1630098801203L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java",
-            inputSignatures = "private @android.annotation.Nullable java.lang.String mMessage\nprivate @android.annotation.Nullable java.lang.String mMethodName\nprivate @android.annotation.Nullable java.lang.String mSenderIdentity\nprivate @android.annotation.Nullable android.content.Intent mIntent\nprivate @android.annotation.Nullable android.content.ComponentName mComponent\nclass ComponentAliasMessage extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true, genToString=true, genAidl=false)")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java
deleted file mode 100644
index 0899886..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.Build;
-import android.provider.DeviceConfig;
-
-import com.android.compatibility.common.util.DeviceConfigStateHelper;
-import com.android.compatibility.common.util.ShellUtils;
-
-import org.junit.AfterClass;
-import org.junit.Assume;
-import org.junit.Test;
-
-/**
- * Test to make sure component-alias can't be enabled on user builds.
- */
-public class ComponentAliasNotSupportedOnUserBuildTest {
-    protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper(
-            DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS);
-
-    @AfterClass
-    public static void restoreDeviceConfig() throws Exception {
-        sDeviceConfig.close();
-    }
-
-    @Test
-    public void enableComponentAliasWithCompatFlag() throws Exception {
-        Assume.assumeFalse(Build.isDebuggable());
-
-        // Try to enable it by both the device config and compat-id.
-        sDeviceConfig.set("enable_experimental_component_alias", "true");
-        ShellUtils.runShellCommand(
-                "am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android");
-
-        // Sleep for an arbitrary amount of time, so the config would sink in, if there was
-        // no "not on user builds" check.
-
-        Thread.sleep(5000);
-
-        // Make sure the feature is still disabled.
-        assertThat(ShellUtils.runShellCommand("dumpsys activity component-alias")
-                .indexOf("Enabled: false") > 0).isTrue();
-    }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java
deleted file mode 100644
index f0ff088..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java
+++ /dev/null
@@ -1,315 +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.content.componentalias.tests;
-
-import static android.content.Context.BIND_AUTO_CREATE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.MAIN_PACKAGE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB1_PACKAGE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB2_PACKAGE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.hamcrest.core.IsNot.not;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-
-import com.android.compatibility.common.util.BroadcastMessenger;
-import com.android.compatibility.common.util.BroadcastMessenger.Receiver;
-import com.android.compatibility.common.util.ShellUtils;
-import com.android.compatibility.common.util.TestUtils;
-
-import org.junit.Assume;
-import org.junit.Test;
-
-import java.util.function.Consumer;
-
-/**
- * Test for the experimental "Component alias" feature.
- *
- * Note this test exercises the relevant APIs, but don't actually check if the aliases are
- * resolved.
- *
- * Note all the helper APKs are battery-exempted (via AndroidTest.xml), so they can run
- * BG services.
- */
-public class ComponentAliasServiceTest extends BaseComponentAliasTest {
-    /**
-     * Service connection used throughout the tests. It sends a message for each callback via
-     * the messenger.
-     */
-    private static final ServiceConnection sServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            log("onServiceConnected: " + name);
-
-            ComponentAliasMessage m = new ComponentAliasMessage()
-                    .setSenderIdentity("sServiceConnection")
-                    .setMethodName("onServiceConnected")
-                    .setComponent(name);
-
-            BroadcastMessenger.send(sContext, TAG, m);
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            log("onServiceDisconnected: " + name);
-
-            ComponentAliasMessage m = new ComponentAliasMessage()
-                    .setSenderIdentity("sServiceConnection")
-                    .setMethodName("onServiceDisconnected")
-                    .setComponent(name);
-
-            BroadcastMessenger.send(sContext, TAG, m);
-        }
-
-        @Override
-        public void onBindingDied(ComponentName name) {
-            log("onBindingDied: " + name);
-
-            ComponentAliasMessage m = new ComponentAliasMessage()
-                    .setSenderIdentity("sServiceConnection")
-                    .setMethodName("onBindingDied");
-
-            BroadcastMessenger.send(sContext, TAG, m);
-        }
-
-        @Override
-        public void onNullBinding(ComponentName name) {
-            log("onNullBinding: " + name);
-
-            ComponentAliasMessage m = new ComponentAliasMessage()
-                    .setSenderIdentity("sServiceConnection")
-                    .setMethodName("onNullBinding");
-
-            BroadcastMessenger.send(sContext, TAG, m);
-        }
-    };
-
-    private void testStartAndStopService_common(
-            Intent originalIntent,
-            ComponentName componentNameForClient,
-            ComponentName componentNameForTarget) {
-
-        ComponentAliasMessage m;
-
-        try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) {
-            // Start the service.
-            ComponentName result = sContext.startService(originalIntent);
-            assertThat(result).isEqualTo(componentNameForClient);
-
-            // Check
-            m = receiver.waitForNextMessage();
-
-            assertThat(m.getMethodName()).isEqualTo("onStartCommand");
-            // The app sees the rewritten intent.
-            assertThat(m.getIntent().getComponent()).isEqualTo(componentNameForTarget);
-
-            // Verify the original intent.
-            assertThat(m.getIntent().getOriginalIntent().getComponent())
-                    .isEqualTo(originalIntent.getComponent());
-            assertThat(m.getIntent().getOriginalIntent().getPackage())
-                    .isEqualTo(originalIntent.getPackage());
-
-            // Stop the service.
-            sContext.stopService(originalIntent);
-
-            // Check
-            m = receiver.waitForNextMessage();
-
-            assertThat(m.getMethodName()).isEqualTo("onDestroy");
-
-            receiver.ensureNoMoreMessages();
-        }
-    }
-
-    private void forEachCombo(Consumer<Combo> callback) {
-        new Combo(
-                new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias00"),
-                new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target00"),
-                MAIN_PACKAGE + ".IS_ALIAS_00").apply(callback);
-        new Combo(
-                new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias01"),
-                new ComponentName(SUB1_PACKAGE, MAIN_PACKAGE + ".s.Target01"),
-                MAIN_PACKAGE + ".IS_ALIAS_01").apply(callback);
-        new Combo(
-                new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02"),
-                new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".s.Target02"),
-                MAIN_PACKAGE + ".IS_ALIAS_02").apply(callback);
-    }
-
-    @Test
-    public void testStartAndStopService_explicitComponentName() {
-        forEachCombo((c) -> {
-            Intent i = new Intent().setComponent(c.alias);
-            testStartAndStopService_common(i, c.alias, c.target);
-        });
-    }
-
-    @Test
-    public void testStartAndStopService_explicitPackageName() {
-        forEachCombo((c) -> {
-            Intent i = new Intent().setPackage(c.alias.getPackageName());
-            i.setAction(c.action);
-
-            testStartAndStopService_common(i, c.alias, c.target);
-        });
-    }
-
-    @Test
-    public void testStartAndStopService_override() throws Exception {
-        Intent i = new Intent().setPackage(MAIN_PACKAGE);
-        i.setAction(MAIN_PACKAGE + ".IS_ALIAS_01");
-
-        // Change some of the aliases from what's defined in <meta-data>.
-
-        ComponentName aliasA = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias01");
-        ComponentName targetA = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target02");
-
-        ComponentName aliasB = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02");
-        ComponentName targetB = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target01");
-
-        sDeviceConfig.set("component_alias_overrides",
-                aliasA.flattenToShortString() + ":" + targetA.flattenToShortString()
-                + ","
-                + aliasB.flattenToShortString() + ":" + targetB.flattenToShortString());
-
-        TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
-            return ShellUtils.runShellCommand("dumpsys activity component-alias")
-                    .indexOf(aliasA.flattenToShortString()
-                            + " -> " + targetA.flattenToShortString()) > 0;
-        });
-
-
-        testStartAndStopService_common(i, aliasA, targetA);
-    }
-
-    private void testBindAndUnbindService_common(
-            Intent originalIntent,
-            ComponentName componentNameForClient,
-            ComponentName componentNameForTarget) {
-        ComponentAliasMessage m;
-
-        try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) {
-            // Bind to the service.
-            assertThat(sContext.bindService(
-                    originalIntent, sServiceConnection, BIND_AUTO_CREATE)).isTrue();
-
-            // Check the target side behavior.
-            m = receiver.waitForNextMessage();
-
-            assertThat(m.getMethodName()).isEqualTo("onBind");
-            // The app sees the rewritten intent.
-            assertThat(m.getIntent().getComponent()).isEqualTo(componentNameForTarget);
-
-            // Verify the original intent.
-            assertThat(m.getIntent().getOriginalIntent().getComponent())
-                    .isEqualTo(originalIntent.getComponent());
-            assertThat(m.getIntent().getOriginalIntent().getPackage())
-                    .isEqualTo(originalIntent.getPackage());
-
-            // Check the client side behavior.
-            m = receiver.waitForNextMessage();
-
-            assertThat(m.getMethodName()).isEqualTo("onServiceConnected");
-            // The app sees the rewritten intent.
-            assertThat(m.getComponent()).isEqualTo(componentNameForClient);
-
-            // Unbind.
-            sContext.unbindService(sServiceConnection);
-
-            // Check the target side behavior.
-            m = receiver.waitForNextMessage();
-
-            assertThat(m.getMethodName()).isEqualTo("onDestroy");
-
-            // Note onServiceDisconnected() won't be called in this case.
-            receiver.ensureNoMoreMessages();
-        }
-    }
-
-    @Test
-    public void testBindService_explicitComponentName() {
-        forEachCombo((c) -> {
-            Intent i = new Intent().setComponent(c.alias);
-
-            testBindAndUnbindService_common(i, c.alias, c.target);
-        });
-
-    }
-
-    @Test
-    public void testBindService_explicitPackageName() {
-        forEachCombo((c) -> {
-            Intent i = new Intent().setPackage(c.alias.getPackageName());
-            i.setAction(c.action);
-
-            testBindAndUnbindService_common(i, c.alias, c.target);
-        });
-    }
-
-    /**
-     * Make sure, when the service process is killed, the client will get a callback with the
-     * right component name.
-     */
-    @Test
-    public void testBindService_serviceKilled() {
-
-        // We need to kill SUB2_PACKAGE, don't run it for this package.
-        Assume.assumeThat(sContext.getPackageName(), not(SUB2_PACKAGE));
-
-        Intent originalIntent = new Intent().setPackage(MAIN_PACKAGE);
-        originalIntent.setAction(MAIN_PACKAGE + ".IS_ALIAS_02");
-
-        final ComponentName componentNameForClient =
-                new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02");
-        final ComponentName componentNameForTarget =
-                new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".s.Target02");
-
-        ComponentAliasMessage m;
-
-        try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) {
-            // Bind to the service.
-            assertThat(sContext.bindService(
-                    originalIntent, sServiceConnection, BIND_AUTO_CREATE)).isTrue();
-
-            // Check the target side behavior.
-            m = receiver.waitForNextMessage();
-
-            assertThat(m.getMethodName()).isEqualTo("onBind");
-
-            m = receiver.waitForNextMessage();
-            assertThat(m.getMethodName()).isEqualTo("onServiceConnected");
-            assertThat(m.getComponent()).isEqualTo(componentNameForClient);
-            // We don't need to check all the fields because these are tested else where.
-
-            // Now kill the service process.
-            ShellUtils.runShellCommand("su 0 killall %s", SUB2_PACKAGE);
-
-            // Check the target side behavior.
-            m = receiver.waitForNextMessage();
-
-            assertThat(m.getMethodName()).isEqualTo("onServiceDisconnected");
-            assertThat(m.getComponent()).isEqualTo(componentNameForClient);
-
-            receiver.ensureNoMoreMessages();
-        }
-    }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java
deleted file mode 100644
index 165d728..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java
+++ /dev/null
@@ -1,28 +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.content.componentalias.tests;
-
-public final class ComponentAliasTestCommon {
-    private ComponentAliasTestCommon() {
-    }
-
-    public static final String TAG = "ComponentAliasTest";
-
-    public static final String MAIN_PACKAGE = "android.content.componentalias.tests";
-
-    public static final String SUB1_PACKAGE = "android.content.componentalias.tests.sub1";
-    public static final String SUB2_PACKAGE = "android.content.componentalias.tests.sub2";
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java b/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java
deleted file mode 100644
index 1d05e72..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.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.content.componentalias.tests.b;
-
-import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.componentalias.tests.ComponentAliasMessage;
-import android.util.Log;
-
-import com.android.compatibility.common.util.BroadcastMessenger;
-
-public class BaseReceiver extends BroadcastReceiver {
-    private String getMyIdentity(Context context) {
-        return (new ComponentName(context.getPackageName(), this.getClass().getCanonicalName()))
-                .flattenToShortString();
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Log.i(TAG, "onReceive: on " + getMyIdentity(context) + " intent=" + intent);
-        ComponentAliasMessage m = new ComponentAliasMessage()
-                .setSenderIdentity(getMyIdentity(context))
-                .setMethodName("onReceive")
-                .setIntent(intent);
-        BroadcastMessenger.send(context, TAG, m);
-    }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java
deleted file mode 100644
index 8fb4e91..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java
+++ /dev/null
@@ -1,21 +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.content.componentalias.tests.b;
-
-import android.content.componentalias.tests.s.BaseService;
-
-public class Target00 extends BaseReceiver {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java
deleted file mode 100644
index 06f7a13..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java
+++ /dev/null
@@ -1,19 +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.content.componentalias.tests.b;
-
-public class Target01 extends BaseReceiver {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java
deleted file mode 100644
index df7579d..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java
+++ /dev/null
@@ -1,19 +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.content.componentalias.tests.b;
-
-public class Target02 extends BaseReceiver {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java
deleted file mode 100644
index 5ae5521..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java
+++ /dev/null
@@ -1,19 +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.content.componentalias.tests.b;
-
-public class Target03 extends BaseReceiver {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java
deleted file mode 100644
index f9b9886..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java
+++ /dev/null
@@ -1,19 +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.content.componentalias.tests.b;
-
-public class Target04 extends BaseReceiver {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java b/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java
deleted file mode 100644
index 535d9b8..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.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 android.content.componentalias.tests.s;
-
-import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG;
-
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.componentalias.tests.ComponentAliasMessage;
-import android.os.Binder;
-import android.os.IBinder;
-import android.util.Log;
-
-import com.android.compatibility.common.util.BroadcastMessenger;
-
-public class BaseService extends Service {
-    private String getMyIdentity() {
-        return (new ComponentName(this.getPackageName(), this.getClass().getCanonicalName()))
-                .flattenToShortString();
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        Log.i(TAG, "onStartCommand: on " + getMyIdentity() + " intent=" + intent);
-        ComponentAliasMessage m = new ComponentAliasMessage()
-                .setSenderIdentity(getMyIdentity())
-                .setMethodName("onStartCommand")
-                .setIntent(intent);
-        BroadcastMessenger.send(this, TAG, m);
-
-        return START_NOT_STICKY;
-    }
-
-    @Override
-    public void onDestroy() {
-        Log.i(TAG, "onDestroy: on " + getMyIdentity());
-
-        ComponentAliasMessage m = new ComponentAliasMessage()
-                .setSenderIdentity(getMyIdentity())
-                .setMethodName("onDestroy");
-        BroadcastMessenger.send(this, TAG, m);
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        Log.i(TAG, "onBind: on " + getMyIdentity() + " intent=" + intent);
-
-        ComponentAliasMessage m = new ComponentAliasMessage()
-                .setSenderIdentity(getMyIdentity())
-                .setMethodName("onBind")
-                .setIntent(intent);
-        BroadcastMessenger.send(this, TAG, m);
-
-        return new Binder();
-    }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java
deleted file mode 100644
index 64b91f5..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java
+++ /dev/null
@@ -1,19 +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.content.componentalias.tests.s;
-
-public class Target00 extends BaseService {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java
deleted file mode 100644
index bd58999..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java
+++ /dev/null
@@ -1,19 +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.content.componentalias.tests.s;
-
-public class Target01 extends BaseService {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java
deleted file mode 100644
index 0ddf818..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java
+++ /dev/null
@@ -1,19 +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.content.componentalias.tests.s;
-
-public class Target02 extends BaseService {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java
deleted file mode 100644
index 0dbc050..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java
+++ /dev/null
@@ -1,19 +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.content.componentalias.tests.s;
-
-public class Target03 extends BaseService {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java
deleted file mode 100644
index 0994258..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java
+++ /dev/null
@@ -1,19 +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.content.componentalias.tests.s;
-
-public class Target04 extends BaseService {
-}
diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp
index 7375c16..43f2122 100644
--- a/tools/lint/fix/Android.bp
+++ b/tools/lint/fix/Android.bp
@@ -23,9 +23,8 @@
 
 python_binary_host {
     name: "lint_fix",
-    main: "lint_fix.py",
-    srcs: ["lint_fix.py"],
-    libs: ["soong_lint_fix"],
+    main: "soong_lint_fix.py",
+    srcs: ["soong_lint_fix.py"],
 }
 
 python_library_host {
diff --git a/tools/lint/fix/README.md b/tools/lint/fix/README.md
index 367d0bc..a5ac2be 100644
--- a/tools/lint/fix/README.md
+++ b/tools/lint/fix/README.md
@@ -5,9 +5,12 @@
 ## What is this?
 
 It's a python script that runs the framework linter,
-and then copies modified files back into the source tree.\
+and then (optionally) copies modified files back into the source tree.\
 Why python, you ask?  Because python is cool ¯\_(ツ)_/¯.
 
+Incidentally, this exposes a much simpler way to run individual lint checks
+against individual modules, so it's useful beyond applying fixes.
+
 ## Why?
 
 Lint is not allowed to modify source files directly via lint's `--apply-suggestions` flag.
@@ -17,30 +20,11 @@
 ## How do I run it?
 **WARNING: You probably want to commit/stash any changes to your working tree before doing this...**
 
-From this directory, run `python lint_fix.py -h`.
-The script's help output explains things that are omitted here.
-
-Alternatively, there is a python binary target you can build to make this
-available anywhere in your tree:
 ```
+source build/envsetup.sh
+lunch cf_x86_64_phone-userdebug # or any lunch target
 m lint_fix
 lint_fix -h
 ```
 
-**Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first.
-
-Example: `lint_fix frameworks/base/services/core/services.core.unboosted UseEnforcePermissionAnnotation --dry-run`
-```shell
-(
-export ANDROID_LINT_CHECK=UseEnforcePermissionAnnotation;
-cd $ANDROID_BUILD_TOP;
-source build/envsetup.sh;
-rm out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint/lint-report.html;
-m out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint/lint-report.html;
-cd out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint;
-unzip suggested-fixes.zip -d suggested-fixes;
-cd suggested-fixes;
-find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1' --;
-rm -rf suggested-fixes
-)
-```
+The script's help output explains things that are omitted here.
diff --git a/tools/lint/fix/lint_fix.py b/tools/lint/fix/lint_fix.py
deleted file mode 100644
index 1c83f7b..0000000
--- a/tools/lint/fix/lint_fix.py
+++ /dev/null
@@ -1,29 +0,0 @@
-#  Copyright (C) 2023 The Android Open Source Project
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-from soong_lint_fix import SoongLintFix
-
-SoongLintFix().run()
diff --git a/tools/lint/fix/soong_lint_fix.py b/tools/lint/fix/soong_lint_fix.py
index 3308df6..cd4d778d 100644
--- a/tools/lint/fix/soong_lint_fix.py
+++ b/tools/lint/fix/soong_lint_fix.py
@@ -13,14 +13,21 @@
 #  limitations under the License.
 
 import argparse
+import json
 import os
+import shutil
 import subprocess
 import sys
+import zipfile
 
 ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP")
+ANDROID_PRODUCT_OUT = os.environ.get("ANDROID_PRODUCT_OUT")
+PRODUCT_OUT = ANDROID_PRODUCT_OUT.removeprefix(f"{ANDROID_BUILD_TOP}/")
+
+SOONG_UI = "build/soong/soong_ui.bash"
 PATH_PREFIX = "out/soong/.intermediates"
 PATH_SUFFIX = "android_common/lint"
-FIX_DIR = "suggested-fixes"
+FIX_ZIP = "suggested-fixes.zip"
 
 class SoongLintFix:
     """
@@ -28,14 +35,12 @@
     apply lint fixes to the platform via the necessary
     combination of soong and shell commands.
 
-    It provides some basic hooks for experimental code
-    to tweak the generation of the resulting shell script.
+    It breaks up these operations into a few "private" methods
+    that are intentionally exposed so experimental code can tweak behavior.
 
-    By default, it will apply lint fixes using the intermediate `suggested-fixes`
-    directory that soong creates during its invocation of lint.
-
-    The default argument parser configures a number of command line arguments to
-    facilitate running lint via soong.
+    The entry point, `run`, will apply lint fixes using the
+    intermediate `suggested-fixes` directory that soong creates during its
+    invocation of lint.
 
     Basic usage:
     ```
@@ -45,99 +50,95 @@
     ```
     """
     def __init__(self):
-        self._commands = None
+        self._parser = _setup_parser()
         self._args = None
+        self._kwargs = None
         self._path = None
         self._target = None
-        self._parser = _setup_parser()
 
 
-    def add_argument(self, *args, **kwargs):
-        """
-        If necessary, add arguments to the underlying argparse.ArgumentParser before running
-        """
-        self._parser.add_argument(*args, **kwargs)
-
-
-    def run(self, add_setup_commands=None, override_fix_commands=None):
+    def run(self, additional_setup=None, custom_fix=None):
         """
         Run the script
-        :param add_setup_commands: OPTIONAL function to add additional setup commands
-            passed the command line arguments, path, and build target
-            must return a list of strings (the additional commands)
-        :param override_fix_commands: OPTIONAL function to override the fix commands
-            passed the command line arguments, path, and build target
-            must return a list of strings (the fix commands)
         """
         self._setup()
-        if add_setup_commands:
-            self._commands += add_setup_commands(self._args, self._path, self._target)
-
-        self._add_lint_report_commands()
+        self._find_module()
+        self._lint()
 
         if not self._args.no_fix:
-            if override_fix_commands:
-                self._commands += override_fix_commands(self._args, self._path, self._target)
-            else:
-                self._commands += [
-                    f"cd {self._path}",
-                    f"unzip {FIX_DIR}.zip -d {FIX_DIR}",
-                    f"cd {FIX_DIR}",
-                    # Find all the java files in the fix directory, excluding the ./out subdirectory,
-                    # and copy them back into the same path within the tree.
-                    f"find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1 || exit 255' --",
-                    f"rm -rf {FIX_DIR}"
-                ]
+            self._fix()
 
-
-        if self._args.dry_run:
-            print(self._get_commands_str())
-        else:
-            self._execute()
-
+        if self._args.print:
+            self._print()
 
     def _setup(self):
         self._args = self._parser.parse_args()
-        self._commands = []
-        self._path = f"{PATH_PREFIX}/{self._args.build_path}/{PATH_SUFFIX}"
-        self._target = f"{self._path}/lint-report.html"
-
-        if not self._args.dry_run:
-            self._commands += [f"export ANDROID_BUILD_TOP={ANDROID_BUILD_TOP}"]
-
+        env = os.environ.copy()
         if self._args.check:
-            self._commands += [f"export ANDROID_LINT_CHECK={self._args.check}"]
+            env["ANDROID_LINT_CHECK"] = self._args.check
+        if self._args.lint_module:
+            env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._args.lint_module
+
+        self._kwargs = {
+            "env": env,
+            "executable": "/bin/bash",
+            "shell": True,
+        }
+
+        os.chdir(ANDROID_BUILD_TOP)
 
 
-    def _add_lint_report_commands(self):
-        self._commands += [
-            "cd $ANDROID_BUILD_TOP",
-            "source build/envsetup.sh",
-            # remove the file first so soong doesn't think there is no work to do
-            f"rm {self._target}",
-            # remove in case there are fixes from a prior run,
-            # that we don't want applied if this run fails
-            f"rm {self._path}/{FIX_DIR}.zip",
-            f"m {self._target}",
-        ]
+    def _find_module(self):
+        print("Refreshing soong modules...")
+        try:
+            os.mkdir(ANDROID_PRODUCT_OUT)
+        except OSError:
+            pass
+        subprocess.call(f"{SOONG_UI} --make-mode {PRODUCT_OUT}/module-info.json", **self._kwargs)
+        print("done.")
+
+        with open(f"{ANDROID_PRODUCT_OUT}/module-info.json") as f:
+            module_info = json.load(f)
+
+        if self._args.module not in module_info:
+            sys.exit(f"Module {self._args.module} not found!")
+
+        module_path = module_info[self._args.module]["path"][0]
+        print(f"Found module {module_path}/{self._args.module}.")
+
+        self._path = f"{PATH_PREFIX}/{module_path}/{self._args.module}/{PATH_SUFFIX}"
+        self._target = f"{self._path}/lint-report.txt"
 
 
-    def _get_commands_str(self):
-        prefix = "(\n"
-        delimiter = ";\n"
-        suffix = "\n)"
-        return f"{prefix}{delimiter.join(self._commands)}{suffix}"
+    def _lint(self):
+        print("Cleaning up any old lint results...")
+        try:
+            os.remove(f"{self._target}")
+            os.remove(f"{self._path}/{FIX_ZIP}")
+        except FileNotFoundError:
+            pass
+        print("done.")
+
+        print(f"Generating {self._target}")
+        subprocess.call(f"{SOONG_UI} --make-mode {self._target}", **self._kwargs)
+        print("done.")
 
 
-    def _execute(self, with_echo=True):
-        if with_echo:
-            exec_commands = []
-            for c in self._commands:
-                exec_commands.append(f'echo "{c}"')
-                exec_commands.append(c)
-            self._commands = exec_commands
+    def _fix(self):
+        print("Copying suggested fixes to the tree...")
+        with zipfile.ZipFile(f"{self._path}/{FIX_ZIP}") as zip:
+            for name in zip.namelist():
+                if name.startswith("out") or not name.endswith(".java"):
+                    continue
+                with zip.open(name) as src, open(f"{ANDROID_BUILD_TOP}/{name}", "wb") as dst:
+                    shutil.copyfileobj(src, dst)
+            print("done.")
 
-        subprocess.call(self._get_commands_str(), executable='/bin/bash', shell=True)
+
+    def _print(self):
+        print("### lint-report.txt ###", end="\n\n")
+        with open(self._target, "r") as f:
+            print(f.read())
 
 
 def _setup_parser():
@@ -147,23 +148,26 @@
         2. Run lint on the specified target.
         3. Copy the modified files, from soong's intermediate directory, back into the tree.
 
-        **Gotcha**: You must have run `source build/envsetup.sh` and `lunch`
-        so that the `ANDROID_BUILD_TOP` environment variable has been set.
-        Alternatively, set it manually in your shell.
+        **Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first.
         """, formatter_class=argparse.RawTextHelpFormatter)
 
-    parser.add_argument('build_path', metavar='build_path', type=str,
-                        help='The build module to run '
-                             '(e.g. frameworks/base/framework-minus-apex or '
-                             'frameworks/base/services/core/services.core.unboosted)')
+    parser.add_argument('module',
+                        help='The soong build module to run '
+                             '(e.g. framework-minus-apex or services.core.unboosted)')
 
-    parser.add_argument('--check', metavar='check', type=str,
+    parser.add_argument('--check',
                         help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.')
 
-    parser.add_argument('--dry-run', dest='dry_run', action='store_true',
-                        help='Just print the resulting shell script instead of running it.')
+    parser.add_argument('--lint-module',
+                            help='Specific lint module to run. Passed to the ANDROID_LINT_CHECK_EXTRA_MODULES environment variable.')
 
-    parser.add_argument('--no-fix', dest='no_fix', action='store_true',
+    parser.add_argument('--no-fix', action='store_true',
                         help='Just build and run the lint, do NOT apply the fixes.')
 
+    parser.add_argument('--print', action='store_true',
+                        help='Print the contents of the generated lint-report.txt at the end.')
+
     return parser
+
+if __name__ == "__main__":
+    SoongLintFix().run()
\ No newline at end of file
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index f7560a7..75b0073 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -321,6 +321,34 @@
             )
     }
 
+    fun testDoesDetectIssuesShortStringsNotAllowed() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass121 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
+                public void testMethodAnyLiteral() {}
+            }
+            """).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass121.java:6: Error: The method \
+                TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) \
+                which differs from the overridden method Stub.testMethodAnyLiteral: \
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAnyLiteral() {}
+                                ~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation()
+            )
+    }
+
     /* Stubs */
 
     // A service with permission annotation on the method.