Merge "Add ramp speed configurations to idle mode" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 7005349..16170d9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -18,10 +18,12 @@
 
     // Add java_aconfig_libraries to here to add them to the core framework
     srcs: [
+        ":android.os.flags-aconfig-java{.generated_srcjars}",
         ":android.security.flags-aconfig-java{.generated_srcjars}",
         ":camera_platform_flags_core_java_lib{.generated_srcjars}",
         ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
         ":com.android.text.flags-aconfig-java{.generated_srcjars}",
+        ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
     ],
 }
 
@@ -78,3 +80,37 @@
     aconfig_declarations: "android.security.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+java_aconfig_library {
+    name: "android.security.flags-aconfig-java-host",
+    aconfig_declarations: "android.security.flags-aconfig",
+    host_supported: true,
+    test: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// OS
+aconfig_declarations {
+    name: "android.os.flags-aconfig",
+    package: "android.os",
+    srcs: ["core/java/android/os/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.os.flags-aconfig-java",
+    aconfig_declarations: "android.os.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// VirtualDeviceManager
+java_aconfig_library {
+    name: "android.companion.virtual.flags-aconfig-java",
+    aconfig_declarations: "android.companion.virtual.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+aconfig_declarations {
+    name: "android.companion.virtual.flags-aconfig",
+    package: "android.companion.virtual.flags",
+    srcs: ["core/java/android/companion/virtual/*.aconfig"],
+}
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index 2a9375c..19d6f04 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -12682,7 +12682,6 @@
 com.android.internal.util.LatencyTracker$Action
 com.android.internal.util.LatencyTracker$ActionProperties
 com.android.internal.util.LatencyTracker$FrameworkStatsLogEvent
-com.android.internal.util.LatencyTracker$SLatencyTrackerHolder
 com.android.internal.util.LatencyTracker$Session$$ExternalSyntheticLambda0
 com.android.internal.util.LatencyTracker$Session
 com.android.internal.util.LatencyTracker
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index 28db61f..a040b57 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -49,7 +49,7 @@
 
     virtual void onVmCreated(JNIEnv* env)
     {
-        if (mClassName.isEmpty()) {
+        if (mClassName.empty()) {
             return; // Zygote. Nothing to do here.
         }
 
@@ -66,10 +66,10 @@
          * executing boot class Java code and thereby deny ourselves access to
          * non-boot classes.
          */
-        char* slashClassName = toSlashClassName(mClassName.string());
+        char* slashClassName = toSlashClassName(mClassName.c_str());
         mClass = env->FindClass(slashClassName);
         if (mClass == NULL) {
-            ALOGE("ERROR: could not find class '%s'\n", mClassName.string());
+            ALOGE("ERROR: could not find class '%s'\n", mClassName.c_str());
         }
         free(slashClassName);
 
@@ -98,7 +98,7 @@
 
     virtual void onExit(int code)
     {
-        if (mClassName.isEmpty()) {
+        if (mClassName.empty()) {
             // if zygote
             IPCThreadState::self()->stopProcess();
             hardware::IPCThreadState::self()->stopProcess();
@@ -179,7 +179,7 @@
         argv_String.append(argv[i]);
         argv_String.append("\" ");
       }
-      ALOGV("app_process main with argv: %s", argv_String.string());
+      ALOGV("app_process main with argv: %s", argv_String.c_str());
     }
 
     AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
@@ -282,7 +282,7 @@
     }
 
     Vector<String8> args;
-    if (!className.isEmpty()) {
+    if (!className.empty()) {
         // We're not in zygote mode, the only argument we need to pass
         // to RuntimeInit is the application argument.
         //
@@ -300,7 +300,7 @@
             restOfArgs.append(argv_new[k]);
             restOfArgs.append("\" ");
           }
-          ALOGV("Class name = %s, args = %s", className.string(), restOfArgs.string());
+          ALOGV("Class name = %s, args = %s", className.c_str(), restOfArgs.c_str());
         }
     } else {
         // We're in zygote mode.
@@ -328,13 +328,13 @@
         }
     }
 
-    if (!niceName.isEmpty()) {
-        runtime.setArgv0(niceName.string(), true /* setProcName */);
+    if (!niceName.empty()) {
+        runtime.setArgv0(niceName.c_str(), true /* setProcName */);
     }
 
     if (zygote) {
         runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
-    } else if (!className.isEmpty()) {
+    } else if (!className.empty()) {
         runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
     } else {
         fprintf(stderr, "Error: no class name or --zygote supplied.\n");
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 2cbd665..69c161a 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -1171,7 +1171,7 @@
     if (!readFile(animation.zip, "desc.txt", desString)) {
         return false;
     }
-    char const* s = desString.string();
+    char const* s = desString.c_str();
     std::string dynamicColoringPartName = "";
     bool postDynamicColoring = false;
 
@@ -1180,7 +1180,7 @@
         const char* endl = strstr(s, "\n");
         if (endl == nullptr) break;
         String8 line(s, endl - s);
-        const char* l = line.string();
+        const char* l = line.c_str();
         int fps = 0;
         int width = 0;
         int height = 0;
@@ -1365,7 +1365,7 @@
 
     // If there is trimData present, override the positioning defaults.
     for (Animation::Part& part : animation.parts) {
-        const char* trimDataStr = part.trimData.string();
+        const char* trimDataStr = part.trimData.c_str();
         for (size_t frameIdx = 0; frameIdx < part.frames.size(); frameIdx++) {
             const char* endl = strstr(trimDataStr, "\n");
             // No more trimData for this part.
@@ -1373,7 +1373,7 @@
                 break;
             }
             String8 line(trimDataStr, endl - trimDataStr);
-            const char* lineStr = line.string();
+            const char* lineStr = line.c_str();
             trimDataStr = ++endl;
             int width = 0, height = 0, x = 0, y = 0;
             if (sscanf(lineStr, "%dx%d+%d+%d", &width, &height, &x, &y) == 4) {
@@ -1606,7 +1606,7 @@
                     1.0f);
 
             ALOGD("Playing files = %s/%s, Requested repeat = %d, playUntilComplete = %s",
-                    animation.fileName.string(), part.path.string(), part.count,
+                    animation.fileName.c_str(), part.path.c_str(), part.count,
                     part.playUntilComplete ? "true" : "false");
 
             // For the last animation, if we have progress indicator from
@@ -1831,17 +1831,17 @@
     ATRACE_CALL();
     if (mLoadedFiles.indexOf(fn) >= 0) {
         SLOGE("File \"%s\" is already loaded. Cyclic ref is not allowed",
-            fn.string());
+            fn.c_str());
         return nullptr;
     }
     ZipFileRO *zip = ZipFileRO::open(fn);
     if (zip == nullptr) {
         SLOGE("Failed to open animation zip \"%s\": %s",
-            fn.string(), strerror(errno));
+            fn.c_str(), strerror(errno));
         return nullptr;
     }
 
-    ALOGD("%s is loaded successfully", fn.string());
+    ALOGD("%s is loaded successfully", fn.c_str());
 
     Animation *animation =  new Animation;
     animation->fileName = fn;
diff --git a/cmds/idmap2/libidmap2/XmlParser.cpp b/cmds/idmap2/libidmap2/XmlParser.cpp
index 1d78460..7807155 100644
--- a/cmds/idmap2/libidmap2/XmlParser.cpp
+++ b/cmds/idmap2/libidmap2/XmlParser.cpp
@@ -130,11 +130,14 @@
 }
 
 Result<Res_value> XmlParser::Node::GetAttributeValue(const std::string& name) const {
+  String16 name16;
   return FindAttribute(parser_, name, [&](size_t index) -> bool {
-    size_t len;
-    const String16 key16(parser_.getAttributeName(index, &len));
-    std::string key = String8(key16).c_str();
-    return key == name;
+    if (name16.empty()) {
+        name16 = String16(name.c_str(), name.size());
+    }
+    size_t key_len;
+    const auto key16 = parser_.getAttributeName(index, &key_len);
+    return key16 && name16.size() == key_len && name16 == key16;
   });
 }
 
diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp
index 6e0bd06..0d9f4e9 100644
--- a/cmds/incident/main.cpp
+++ b/cmds/incident/main.cpp
@@ -83,8 +83,8 @@
 Status
 StatusListener::onReportServiceStatus(const String16& service, int32_t status)
 {
-    fprintf(stderr, "service '%s' status %d\n", String8(service).string(), status);
-    ALOGD("service '%s' status %d\n", String8(service).string(), status);
+    fprintf(stderr, "service '%s' status %d\n", String8(service).c_str(), status);
+    ALOGD("service '%s' status %d\n", String8(service).c_str(), status);
     return Status::ok();
 }
 
@@ -384,7 +384,7 @@
         status = service->reportIncidentToStream(args, listener, std::move(writeEnd));
 
         if (!status.isOk()) {
-            fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
+            fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().c_str());
             return 1;
         }
 
@@ -396,14 +396,14 @@
         sp<StatusListener> listener(new StatusListener());
         status = service->reportIncidentToDumpstate(std::move(writeEnd), listener);
         if (!status.isOk()) {
-            fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
+            fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().c_str());
             return 1;
         }
         return listener->getExitCodeOrElse(stream_output(fds[0], STDOUT_FILENO));
     } else {
         status = service->reportIncident(args);
         if (!status.isOk()) {
-            fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
+            fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().c_str());
             return 1;
         } else {
             return 0;
diff --git a/cmds/incident_helper/src/TextParserBase.cpp b/cmds/incident_helper/src/TextParserBase.cpp
index e9bc70f..e625afa 100644
--- a/cmds/incident_helper/src/TextParserBase.cpp
+++ b/cmds/incident_helper/src/TextParserBase.cpp
@@ -27,11 +27,11 @@
 {
     string content;
     if (!ReadFdToString(in, &content)) {
-        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.c_str());
         return -1;
     }
     if (!WriteStringToFd(content, out)) {
-        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.c_str());
         return -1;
     }
     return NO_ERROR;
@@ -42,13 +42,13 @@
 {
     string content;
     if (!ReadFdToString(in, &content)) {
-        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.c_str());
         return -1;
     }
     // reverse the content
     reverse(content.begin(), content.end());
     if (!WriteStringToFd(content, out)) {
-        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+        fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.c_str());
         return -1;
     }
     return NO_ERROR;
diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp
index ff5fd86..cc03d4a 100644
--- a/cmds/incident_helper/src/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -101,7 +101,7 @@
     fprintf(stderr, "Pasring section %d...\n", sectionID);
     TextParserBase* parser = selectParser(sectionID);
     if (parser != nullptr) {
-        fprintf(stderr, "Running parser: %s\n", parser->name.string());
+        fprintf(stderr, "Running parser: %s\n", parser->name.c_str());
         status_t err = parser->Parse(STDIN_FILENO, STDOUT_FILENO);
         if (err != NO_ERROR) {
             fprintf(stderr, "Parse error in section %d: %s\n", sectionID, strerror(-err));
diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
index ced6cf8..2a032fb 100644
--- a/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
+++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
@@ -52,9 +52,9 @@
     }
 
     if (!proto.flush(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
         return -1;
     }
-    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
     return NO_ERROR;
 }
diff --git a/cmds/incident_helper/src/parsers/CpuFreqParser.cpp b/cmds/incident_helper/src/parsers/CpuFreqParser.cpp
index 43a12f6..c9bf4c5 100644
--- a/cmds/incident_helper/src/parsers/CpuFreqParser.cpp
+++ b/cmds/incident_helper/src/parsers/CpuFreqParser.cpp
@@ -82,9 +82,9 @@
     }
 
     if (!proto.flush(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
         return -1;
     }
-    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
     return NO_ERROR;
 }
diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
index 5d525e6..77751a2f 100644
--- a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
+++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
@@ -130,11 +130,11 @@
         record = parseRecordByColumns(line, columnIndices);
         diff = record.size() - header.size();
         if (diff < 0) {
-            fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
+            fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.c_str(), nline, -diff, line.c_str());
             printRecord(record);
             continue;
         } else if (diff > 0) {
-            fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
+            fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.c_str(), nline, diff, line.c_str());
             printRecord(record);
             continue;
         }
@@ -143,7 +143,7 @@
         for (int i=0; i<(int)record.size(); i++) {
             if (!table.insertField(&proto, header[i], record[i])) {
                 fprintf(stderr, "[%s]Line %d fails to insert field %s with value %s\n",
-                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
+                        this->name.c_str(), nline, header[i].c_str(), record[i].c_str());
             }
         }
         proto.end(token);
@@ -155,9 +155,9 @@
     }
 
     if (!proto.flush(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
         return -1;
     }
-    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
     return NO_ERROR;
 }
diff --git a/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp b/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp
index 4fd6b06..0474a50 100644
--- a/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp
+++ b/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp
@@ -76,9 +76,9 @@
     }
 
     if (!proto.flush(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
         return -1;
     }
-    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
     return NO_ERROR;
 }
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
index 85beaf0..d16c23c 100644
--- a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
@@ -51,11 +51,11 @@
 
         if (record.size() < header.size()) {
             // TODO: log this to incident report!
-            fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
+            fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.c_str(), nline, line.c_str());
             continue;
         } else if (record.size() > header.size()) {
             // TODO: log this to incident report!
-            fprintf(stderr, "[%s]Line %d has extra fields\n%s\n", this->name.string(), nline, line.c_str());
+            fprintf(stderr, "[%s]Line %d has extra fields\n%s\n", this->name.c_str(), nline, line.c_str());
             continue;
         }
 
@@ -63,7 +63,7 @@
         for (int i=0; i<(int)record.size(); i++) {
             if (!table.insertField(&proto, header[i], record[i])) {
                 fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
-                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
+                        this->name.c_str(), nline, header[i].c_str(), record[i].c_str());
             }
         }
         proto.end(token);
@@ -75,9 +75,9 @@
     }
 
     if (!proto.flush(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
         return -1;
     }
-    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
     return NO_ERROR;
 }
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
index 2a89c920..36710df 100644
--- a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
@@ -114,10 +114,10 @@
     }
 
     if (!proto.flush(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
         return -1;
     }
 
-    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
     return NO_ERROR;
 }
diff --git a/cmds/incident_helper/src/parsers/ProcrankParser.cpp b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
index 4763b48..997d2e5 100644
--- a/cmds/incident_helper/src/parsers/ProcrankParser.cpp
+++ b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
@@ -60,7 +60,7 @@
             if (record[record.size() - 1] == "TOTAL") { // TOTAL record
                 total = line;
             } else {
-                fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
+                fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.c_str(), nline,
                     line.c_str());
             }
             continue;
@@ -70,7 +70,7 @@
         for (int i=0; i<(int)record.size(); i++) {
             if (!table.insertField(&proto, header[i], record[i])) {
                 fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
-                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
+                        this->name.c_str(), nline, header[i].c_str(), record[i].c_str());
             }
         }
         proto.end(token);
@@ -104,9 +104,9 @@
     }
 
     if (!proto.flush(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
         return -1;
     }
-    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
     return NO_ERROR;
 }
diff --git a/cmds/incident_helper/src/parsers/PsParser.cpp b/cmds/incident_helper/src/parsers/PsParser.cpp
index d3cb4be..55aa555 100644
--- a/cmds/incident_helper/src/parsers/PsParser.cpp
+++ b/cmds/incident_helper/src/parsers/PsParser.cpp
@@ -61,12 +61,12 @@
         diff = record.size() - header.size();
         if (diff < 0) {
             // TODO: log this to incident report!
-            fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
+            fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.c_str(), nline, -diff, line.c_str());
             printRecord(record);
             continue;
         } else if (diff > 0) {
             // TODO: log this to incident report!
-            fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
+            fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.c_str(), nline, diff, line.c_str());
             printRecord(record);
             continue;
         }
@@ -75,7 +75,7 @@
         for (int i=0; i<(int)record.size(); i++) {
             if (!table.insertField(&proto, header[i], record[i])) {
                 fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
-                        this->name.string(), nline, header[i].c_str(), record[i].c_str());
+                        this->name.c_str(), nline, header[i].c_str(), record[i].c_str());
             }
         }
         proto.end(token);
@@ -87,9 +87,9 @@
     }
 
     if (!proto.flush(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
         return -1;
     }
-    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
     return NO_ERROR;
 }
diff --git a/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp b/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
index eba536b..86c34bc 100644
--- a/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
+++ b/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
@@ -219,9 +219,9 @@
     }
 
     if (!proto.flush(out)) {
-        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
         return -1;
     }
-    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
     return NO_ERROR;
 }
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 4f9059f..e70748e8 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -407,8 +407,8 @@
 Status IncidentService::getIncidentReportList(const String16& pkg16, const String16& cls16,
             vector<String16>* result) {
     status_t err;
-    const string pkg(String8(pkg16).string());
-    const string cls(String8(cls16).string());
+    const string pkg(String8(pkg16).c_str());
+    const string cls(String8(cls16).c_str());
 
     // List the reports
     vector<sp<ReportFile>> all;
@@ -441,9 +441,9 @@
             const String16& id16, IncidentManager::IncidentReport* result) {
     status_t err;
 
-    const string pkg(String8(pkg16).string());
-    const string cls(String8(cls16).string());
-    const string id(String8(id16).string());
+    const string pkg(String8(pkg16).c_str());
+    const string cls(String8(cls16).c_str());
+    const string id(String8(id16).c_str());
 
     IncidentReportArgs args;
     sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, &args);
@@ -470,9 +470,9 @@
 
 Status IncidentService::deleteIncidentReports(const String16& pkg16, const String16& cls16,
             const String16& id16) {
-    const string pkg(String8(pkg16).string());
-    const string cls(String8(cls16).string());
-    const string id(String8(id16).string());
+    const string pkg(String8(pkg16).c_str());
+    const string cls(String8(cls16).c_str());
+    const string id(String8(id16).c_str());
 
     sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, nullptr);
     if (file != nullptr) {
@@ -484,7 +484,7 @@
 }
 
 Status IncidentService::deleteAllIncidentReports(const String16& pkg16) {
-    const string pkg(String8(pkg16).string());
+    const string pkg(String8(pkg16).c_str());
 
     mWorkDirectory->commitAll(pkg);
     mBroadcaster->clearPackageBroadcasts(pkg);
@@ -572,7 +572,7 @@
             while (SECTION_LIST[idx] != NULL) {
                 const Section* section = SECTION_LIST[idx];
                 if (section->id == id) {
-                    fprintf(out, "Section[%d] %s\n", id, section->name.string());
+                    fprintf(out, "Section[%d] %s\n", id, section->name.c_str());
                     break;
                 }
                 idx++;
@@ -596,7 +596,7 @@
 
 static void printPrivacy(const Privacy* p, FILE* out, String8 indent) {
     if (p == NULL) return;
-    fprintf(out, "%sid:%d, type:%d, dest:%d\n", indent.string(), p->field_id, p->type, p->policy);
+    fprintf(out, "%sid:%d, type:%d, dest:%d\n", indent.c_str(), p->field_id, p->type, p->policy);
     if (p->children == NULL) return;
     for (int i = 0; p->children[i] != NULL; i++) {  // NULL-terminated.
         printPrivacy(p->children[i], out, indent + "  ");
@@ -609,7 +609,7 @@
     const int argCount = args.size();
     if (argCount >= 3) {
         String8 opt = args[1];
-        int sectionId = atoi(args[2].string());
+        int sectionId = atoi(args[2].c_str());
 
         const Privacy* p = get_privacy_of_section(sectionId);
         if (p == NULL) {
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 86a78f09..c9cf727 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -711,7 +711,7 @@
         return NO_ERROR;
     }
 
-    ALOGD("Start incident report section %d '%s'", sectionId, section->name.string());
+    ALOGD("Start incident report section %d '%s'", sectionId, section->name.c_str());
     IncidentMetadata::SectionStats* sectionMetadata = metadata->add_sections();
 
     // Notify listener of starting
@@ -747,7 +747,7 @@
                     sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
     });
 
-    ALOGD("Finish incident report section %d '%s'", sectionId, section->name.string());
+    ALOGD("Finish incident report section %d '%s'", sectionId, section->name.c_str());
     return NO_ERROR;
 }
 
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 581367a..c2aa269 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -60,7 +60,7 @@
 const char* GZIP[] = {"/system/bin/gzip", NULL};
 
 static pid_t fork_execute_incident_helper(const int id, Fpipe* p2cPipe, Fpipe* c2pPipe) {
-    const char* ihArgs[]{INCIDENT_HELPER, "-s", String8::format("%d", id).string(), NULL};
+    const char* ihArgs[]{INCIDENT_HELPER, "-s", String8::format("%d", id).c_str(), NULL};
     return fork_execute_cmd(const_cast<char**>(ihArgs), p2cPipe, c2pPipe);
 }
 
@@ -100,7 +100,7 @@
     // add O_CLOEXEC to make sure it is closed when exec incident helper
     unique_fd fd(open(mFilename, O_RDONLY | O_CLOEXEC));
     if (fd.get() == -1) {
-        ALOGW("[%s] failed to open file", this->name.string());
+        ALOGW("[%s] failed to open file", this->name.c_str());
         // There may be some devices/architectures that won't have the file.
         // Just return here without an error.
         return NO_ERROR;
@@ -110,13 +110,13 @@
     Fpipe c2pPipe;
     // initiate pipes to pass data to/from incident_helper
     if (!p2cPipe.init() || !c2pPipe.init()) {
-        ALOGW("[%s] failed to setup pipes", this->name.string());
+        ALOGW("[%s] failed to setup pipes", this->name.c_str());
         return -errno;
     }
 
     pid_t pid = fork_execute_incident_helper(this->id, &p2cPipe, &c2pPipe);
     if (pid == -1) {
-        ALOGW("[%s] failed to fork", this->name.string());
+        ALOGW("[%s] failed to fork", this->name.c_str());
         return -errno;
     }
 
@@ -128,14 +128,14 @@
     writer->setSectionStats(buffer);
     if (readStatus != NO_ERROR || buffer.timedOut()) {
         ALOGW("[%s] failed to read data from incident helper: %s, timedout: %s",
-              this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
+              this->name.c_str(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
         kill_child(pid);
         return readStatus;
     }
 
     status_t ihStatus = wait_child(pid);
     if (ihStatus != NO_ERROR) {
-        ALOGW("[%s] abnormal child process: %s", this->name.string(), strerror(-ihStatus));
+        ALOGW("[%s] abnormal child process: %s", this->name.c_str(), strerror(-ihStatus));
         return OK; // Not a fatal error.
     }
 
@@ -169,7 +169,7 @@
         index++;  // look at the next file.
     }
     if (fd.get() == -1) {
-        ALOGW("[%s] can't open all the files", this->name.string());
+        ALOGW("[%s] can't open all the files", this->name.c_str());
         return NO_ERROR;  // e.g. LAST_KMSG will reach here in user build.
     }
     FdBuffer buffer;
@@ -177,13 +177,13 @@
     Fpipe c2pPipe;
     // initiate pipes to pass data to/from gzip
     if (!p2cPipe.init() || !c2pPipe.init()) {
-        ALOGW("[%s] failed to setup pipes", this->name.string());
+        ALOGW("[%s] failed to setup pipes", this->name.c_str());
         return -errno;
     }
 
     pid_t pid = fork_execute_cmd((char* const*)GZIP, &p2cPipe, &c2pPipe);
     if (pid == -1) {
-        ALOGW("[%s] failed to fork", this->name.string());
+        ALOGW("[%s] failed to fork", this->name.c_str());
         return -errno;
     }
     // parent process
@@ -202,14 +202,14 @@
     size_t editPos = internalBuffer->wp()->pos();
     internalBuffer->wp()->move(8);  // reserve 8 bytes for the varint of the data size.
     size_t dataBeginAt = internalBuffer->wp()->pos();
-    VLOG("[%s] editPos=%zu, dataBeginAt=%zu", this->name.string(), editPos, dataBeginAt);
+    VLOG("[%s] editPos=%zu, dataBeginAt=%zu", this->name.c_str(), editPos, dataBeginAt);
 
     status_t readStatus = buffer.readProcessedDataInStream(
             fd.get(), std::move(p2cPipe.writeFd()), std::move(c2pPipe.readFd()), this->timeoutMs,
             isSysfs(mFilenames[index]));
     writer->setSectionStats(buffer);
     if (readStatus != NO_ERROR || buffer.timedOut()) {
-        ALOGW("[%s] failed to read data from gzip: %s, timedout: %s", this->name.string(),
+        ALOGW("[%s] failed to read data from gzip: %s, timedout: %s", this->name.c_str(),
               strerror(-readStatus), buffer.timedOut() ? "true" : "false");
         kill_child(pid);
         return readStatus;
@@ -217,7 +217,7 @@
 
     status_t gzipStatus = wait_child(pid);
     if (gzipStatus != NO_ERROR) {
-        ALOGW("[%s] abnormal child process: %s", this->name.string(), strerror(-gzipStatus));
+        ALOGW("[%s] abnormal child process: %s", this->name.c_str(), strerror(-gzipStatus));
         return gzipStatus;
     }
     // Revisit the actual size from gzip result and edit the internal buffer accordingly.
@@ -290,7 +290,7 @@
     FdBuffer buffer;
     err = buffer.read(data->pipe.readFd().get(), this->timeoutMs);
     if (err != NO_ERROR) {
-        ALOGE("[%s] reader failed with error '%s'", this->name.string(), strerror(-err));
+        ALOGE("[%s] reader failed with error '%s'", this->name.c_str(), strerror(-err));
     }
 
     // If the worker side is finished, then return its error (which may overwrite
@@ -300,7 +300,7 @@
         data->pipe.close();
         if (data->workerError != NO_ERROR) {
             err = data->workerError;
-            ALOGE("[%s] worker failed with error '%s'", this->name.string(), strerror(-err));
+            ALOGE("[%s] worker failed with error '%s'", this->name.c_str(), strerror(-err));
         }
         workerDone = data->workerDone;
     }
@@ -309,17 +309,17 @@
     if (err != NO_ERROR) {
         char errMsg[128];
         snprintf(errMsg, 128, "[%s] failed with error '%s'",
-            this->name.string(), strerror(-err));
+            this->name.c_str(), strerror(-err));
         writer->error(this, err, "WorkerThreadSection failed.");
         return NO_ERROR;
     }
     if (buffer.truncated()) {
-        ALOGW("[%s] too large, truncating", this->name.string());
+        ALOGW("[%s] too large, truncating", this->name.c_str());
         // Do not write a truncated section. It won't pass through the PrivacyFilter.
         return NO_ERROR;
     }
     if (!workerDone || buffer.timedOut()) {
-        ALOGW("[%s] timed out", this->name.string());
+        ALOGW("[%s] timed out", this->name.c_str());
         return NO_ERROR;
     }
 
@@ -360,18 +360,18 @@
     Fpipe ihPipe;
 
     if (!cmdPipe.init() || !ihPipe.init()) {
-        ALOGW("[%s] failed to setup pipes", this->name.string());
+        ALOGW("[%s] failed to setup pipes", this->name.c_str());
         return -errno;
     }
 
     pid_t cmdPid = fork_execute_cmd((char* const*)mCommand, NULL, &cmdPipe);
     if (cmdPid == -1) {
-        ALOGW("[%s] failed to fork", this->name.string());
+        ALOGW("[%s] failed to fork", this->name.c_str());
         return -errno;
     }
     pid_t ihPid = fork_execute_incident_helper(this->id, &cmdPipe, &ihPipe);
     if (ihPid == -1) {
-        ALOGW("[%s] failed to fork", this->name.string());
+        ALOGW("[%s] failed to fork", this->name.c_str());
         return -errno;
     }
 
@@ -381,7 +381,7 @@
     writer->setSectionStats(buffer);
     if (readStatus != NO_ERROR || buffer.timedOut()) {
         ALOGW("[%s] failed to read data from incident helper: %s, timedout: %s",
-              this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
+              this->name.c_str(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
         kill_child(cmdPid);
         kill_child(ihPid);
         return readStatus;
@@ -393,7 +393,7 @@
     status_t ihStatus = wait_child(ihPid);
     if (cmdStatus != NO_ERROR || ihStatus != NO_ERROR) {
         ALOGW("[%s] abnormal child processes, return status: command: %s, incident helper: %s",
-              this->name.string(), strerror(-cmdStatus), strerror(-ihStatus));
+              this->name.c_str(), strerror(-cmdStatus), strerror(-ihStatus));
         // Not a fatal error.
         return NO_ERROR;
     }
@@ -428,7 +428,7 @@
     sp<IBinder> service = defaultServiceManager()->checkService(mService);
 
     if (service == NULL) {
-        ALOGW("DumpsysSection: Can't lookup service: %s", String8(mService).string());
+        ALOGW("DumpsysSection: Can't lookup service: %s", String8(mService).c_str());
         return NAME_NOT_FOUND;
     }
 
@@ -463,14 +463,14 @@
     // checkService won't wait for the service to show up like getService will.
     sp<IBinder> service = defaultServiceManager()->checkService(mService);
     if (service == NULL) {
-        ALOGW("TextDumpsysSection: Can't lookup service: %s", String8(mService).string());
+        ALOGW("TextDumpsysSection: Can't lookup service: %s", String8(mService).c_str());
         return NAME_NOT_FOUND;
     }
 
     // Create pipe
     Fpipe dumpPipe;
     if (!dumpPipe.init()) {
-        ALOGW("[%s] failed to setup pipe", this->name.string());
+        ALOGW("[%s] failed to setup pipe", this->name.c_str());
         return -errno;
     }
 
@@ -482,7 +482,7 @@
         signal(SIGPIPE, sigpipe_handler);
         status_t err = service->dump(write_fd.get(), this->mArgs);
         if (err != OK) {
-            ALOGW("[%s] dump thread failed. Error: %s", this->name.string(), strerror(-err));
+            ALOGW("[%s] dump thread failed. Error: %s", this->name.c_str(), strerror(-err));
         }
         write_fd.reset();
     });
@@ -490,7 +490,7 @@
     // Collect dump content
     FdBuffer buffer;
     ProtoOutputStream proto;
-    proto.write(TextDumpProto::COMMAND, std::string(name.string()));
+    proto.write(TextDumpProto::COMMAND, std::string(name.c_str()));
     proto.write(TextDumpProto::DUMP_DURATION_NS, int64_t(Nanotime() - start));
     buffer.write(proto.data());
 
@@ -504,7 +504,7 @@
     dumpPipe.readFd().reset();
     writer->setSectionStats(buffer);
     if (readStatus != OK || buffer.timedOut()) {
-        ALOGW("[%s] failed to read from dumpsys: %s, timedout: %s", this->name.string(),
+        ALOGW("[%s] failed to read from dumpsys: %s, timedout: %s", this->name.c_str(),
               strerror(-readStatus), buffer.timedOut() ? "true" : "false");
         worker.detach();
         return readStatus;
@@ -579,7 +579,7 @@
     // Hence forking a new process to prevent memory fragmentation.
     pid_t pid = fork();
     if (pid < 0) {
-        ALOGW("[%s] failed to fork", this->name.string());
+        ALOGW("[%s] failed to fork", this->name.c_str());
         return errno;
     }
     if (pid > 0) {
@@ -593,7 +593,7 @@
             android_logger_list_free);
 
     if (android_logger_open(loggers.get(), mLogID) == NULL) {
-        ALOGE("[%s] Can't get logger.", this->name.string());
+        ALOGE("[%s] Can't get logger.", this->name.c_str());
         _exit(EXIT_FAILURE);
     }
 
@@ -610,7 +610,7 @@
         // status = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end.
         if (status <= 0) {
             if (status != -EAGAIN) {
-                ALOGW("[%s] fails to read a log_msg.\n", this->name.string());
+                ALOGW("[%s] fails to read a log_msg.\n", this->name.c_str());
                 err = -status;
             }
             break;
@@ -680,7 +680,7 @@
             AndroidLogEntry entry;
             status = android_log_processLogBuffer(&msg.entry, &entry);
             if (status != OK) {
-                ALOGW("[%s] fails to process to an entry.\n", this->name.string());
+                ALOGW("[%s] fails to process to an entry.\n", this->name.c_str());
                 err = status;
                 break;
             }
@@ -702,7 +702,7 @@
         }
         if (!proto.flush(pipeWriteFd.get())) {
             if (errno == EPIPE) {
-                ALOGW("[%s] wrote to a broken pipe\n", this->name.string());
+                ALOGW("[%s] wrote to a broken pipe\n", this->name.c_str());
             }
             err = errno;
             break;
@@ -757,7 +757,7 @@
         }
         ssize_t exe_name_len = readlink(link_name, exe_name, EXE_NAME_LEN);
         if (exe_name_len < 0 || exe_name_len >= EXE_NAME_LEN) {
-            ALOGE("[%s] Can't read '%s': %s", name.string(), link_name, strerror(errno));
+            ALOGE("[%s] Can't read '%s': %s", name.c_str(), link_name, strerror(errno));
             continue;
         }
         // readlink(2) does not put a null terminator at the end
@@ -788,7 +788,7 @@
 
         Fpipe dumpPipe;
         if (!dumpPipe.init()) {
-            ALOGW("[%s] failed to setup dump pipe", this->name.string());
+            ALOGW("[%s] failed to setup dump pipe", this->name.c_str());
             err = -errno;
             break;
         }
@@ -822,12 +822,12 @@
         // Wait on the child to avoid it becoming a zombie process.
         status_t cStatus = wait_child(child);
         if (err != NO_ERROR) {
-            ALOGW("[%s] failed to read stack dump: %d", this->name.string(), err);
+            ALOGW("[%s] failed to read stack dump: %d", this->name.c_str(), err);
             dumpPipe.readFd().reset();
             break;
         }
         if (cStatus != NO_ERROR) {
-            ALOGE("[%s] child had an issue: %s\n", this->name.string(), strerror(-cStatus));
+            ALOGE("[%s] child had an issue: %s\n", this->name.c_str(), strerror(-cStatus));
         }
 
         // Resize dump buffer
@@ -852,7 +852,7 @@
         dumpPipe.readFd().reset();
         if (!proto.flush(pipeWriteFd.get())) {
             if (errno == EPIPE) {
-                ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
+                ALOGE("[%s] wrote to a broken pipe\n", this->name.c_str());
             }
             err = errno;
             break;
diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp
index 7d20a74..6b2fb8e 100644
--- a/cmds/incidentd/src/report_directory.cpp
+++ b/cmds/incidentd/src/report_directory.cpp
@@ -62,8 +62,8 @@
             continue;
         }
         String8 filename = dirbase + entry->d_name;
-        if (stat(filename.string(), &st) != 0) {
-            ALOGE("Unable to stat file %s", filename.string());
+        if (stat(filename.c_str(), &st) != 0) {
+            ALOGE("Unable to stat file %s", filename.c_str());
             continue;
         }
         if (!S_ISREG(st.st_mode)) {
@@ -88,7 +88,7 @@
     // Remove files until we're under our limits.
     for (std::vector<std::pair<String8, struct stat>>::iterator it = files.begin();
          it != files.end() && totalSize >= maxSize && totalCount >= maxCount; it++) {
-        remove(it->first.string());
+        remove(it->first.c_str());
         totalSize -= it->second.st_size;
         totalCount--;
     }
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 214b12c..0351a00 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -12713,7 +12713,6 @@
 com.android.internal.util.LatencyTracker$Action
 com.android.internal.util.LatencyTracker$ActionProperties
 com.android.internal.util.LatencyTracker$FrameworkStatsLogEvent
-com.android.internal.util.LatencyTracker$SLatencyTrackerHolder
 com.android.internal.util.LatencyTracker$Session$$ExternalSyntheticLambda0
 com.android.internal.util.LatencyTracker$Session
 com.android.internal.util.LatencyTracker
diff --git a/core/api/current.txt b/core/api/current.txt
index d26cf2e..e003624 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -39351,8 +39351,10 @@
   }
 
   public final class FileIntegrityManager {
+    method @FlaggedApi(Flags.FLAG_FSVERITY_API) @Nullable public byte[] getFsverityDigest(@NonNull java.io.File) throws java.io.IOException;
     method public boolean isApkVeritySupported();
     method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException;
+    method @FlaggedApi(Flags.FLAG_FSVERITY_API) public void setupFsverity(@NonNull java.io.File) throws java.io.IOException;
   }
 
   public final class KeyChain {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b1feb41..7f1e289 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -6,7 +6,9 @@
     field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
     field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
     field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE";
+    field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH";
     field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
+    field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH";
   }
 
 }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index adbd06c..adf1da9 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -17321,7 +17321,6 @@
     field public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
     field public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
     field public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
-    field public static final int SATELLITE_ACCESS_BARRED = 16; // 0x10
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6
@@ -17331,13 +17330,6 @@
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff
-    field public static final int SATELLITE_ERROR = 1; // 0x1
-    field public static final int SATELLITE_ERROR_NONE = 0; // 0x0
-    field public static final int SATELLITE_INVALID_ARGUMENTS = 8; // 0x8
-    field public static final int SATELLITE_INVALID_MODEM_STATE = 7; // 0x7
-    field public static final int SATELLITE_INVALID_TELEPHONY_STATE = 6; // 0x6
-    field public static final int SATELLITE_MODEM_BUSY = 22; // 0x16
-    field public static final int SATELLITE_MODEM_ERROR = 4; // 0x4
     field public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3
     field public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2
     field public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0
@@ -17345,21 +17337,29 @@
     field public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4
     field public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5
     field public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff
-    field public static final int SATELLITE_NETWORK_ERROR = 5; // 0x5
-    field public static final int SATELLITE_NETWORK_TIMEOUT = 17; // 0x11
-    field public static final int SATELLITE_NOT_AUTHORIZED = 19; // 0x13
-    field public static final int SATELLITE_NOT_REACHABLE = 18; // 0x12
-    field public static final int SATELLITE_NOT_SUPPORTED = 20; // 0x14
-    field public static final int SATELLITE_NO_RESOURCES = 12; // 0xc
-    field public static final int SATELLITE_RADIO_NOT_AVAILABLE = 10; // 0xa
-    field public static final int SATELLITE_REQUEST_ABORTED = 15; // 0xf
-    field public static final int SATELLITE_REQUEST_FAILED = 9; // 0x9
-    field public static final int SATELLITE_REQUEST_IN_PROGRESS = 21; // 0x15
-    field public static final int SATELLITE_REQUEST_NOT_SUPPORTED = 11; // 0xb
-    field public static final int SATELLITE_SERVER_ERROR = 2; // 0x2
-    field public static final int SATELLITE_SERVICE_ERROR = 3; // 0x3
-    field public static final int SATELLITE_SERVICE_NOT_PROVISIONED = 13; // 0xd
-    field public static final int SATELLITE_SERVICE_PROVISION_IN_PROGRESS = 14; // 0xe
+    field public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10
+    field public static final int SATELLITE_RESULT_ERROR = 1; // 0x1
+    field public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8
+    field public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7
+    field public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6
+    field public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16
+    field public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4
+    field public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5
+    field public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11
+    field public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13
+    field public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12
+    field public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14
+    field public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc
+    field public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa
+    field public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf
+    field public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9
+    field public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; // 0x15
+    field public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; // 0xb
+    field public static final int SATELLITE_RESULT_SERVER_ERROR = 2; // 0x2
+    field public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; // 0x3
+    field public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; // 0xd
+    field public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; // 0xe
+    field public static final int SATELLITE_RESULT_SUCCESS = 0; // 0x0
   }
 
   public static class SatelliteManager.SatelliteException extends java.lang.Exception {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d43d785..22d2999 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -28,6 +28,7 @@
     field public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES";
     field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
     field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
+    field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH";
     field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
     field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING";
     field public static final String MODIFY_HDR_CONVERSION_MODE = "android.permission.MODIFY_HDR_CONVERSION_MODE";
@@ -54,6 +55,7 @@
     field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD";
     field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
+    field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH";
     field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG";
     field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
     field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 021f932..32c40df 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -35,6 +35,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityPresentationInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDataObserver;
 import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.Bundle;
@@ -1224,4 +1225,13 @@
      */
     @NonNull
     public abstract StatsEvent getCachedAppsHighWatermarkStats(int atomTag, boolean resetAfterPull);
+
+    /**
+     * Internal method for clearing app data, with the extra param that is used to indicate restore.
+     * Used by Backup service during restore operation.
+     *
+     * @hide
+     */
+    public abstract boolean clearApplicationUserData(String packageName, boolean keepState,
+            boolean isRestore, IPackageDataObserver observer, int userId);
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1665cc1..39589fa 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -29,6 +29,8 @@
 import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
 import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
 import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
+import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
+import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
@@ -206,6 +208,7 @@
 import android.window.WindowProviderService;
 import android.window.WindowTokenClientController;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
@@ -6112,9 +6115,21 @@
         final boolean shouldUpdateResources = hasPublicResConfigChange
                 || shouldUpdateResources(activityToken, currentResConfig, newConfig,
                 amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
-        final boolean shouldReportChange = shouldReportChange(
-                activity.mCurrentConfig, newConfig, r.mSizeConfigurations,
-                activity.mActivityInfo.getRealConfigChanged(), alwaysReportChange);
+
+        // TODO(b/274944389): remove once a longer-term solution is implemented.
+        boolean skipActivityRelaunchWhenDocking = activity.getResources().getBoolean(
+                R.bool.config_skipActivityRelaunchWhenDocking);
+        int handledConfigChanges = activity.mActivityInfo.getRealConfigChanged();
+        if (skipActivityRelaunchWhenDocking && onlyDeskInUiModeChanged(activity.mCurrentConfig,
+                newConfig)) {
+            // If we're not relaunching this activity when docking, we should send the configuration
+            // changed event. Pretend as if the activity is handling uiMode config changes in its
+            // manifest so that we'll report any dock changes.
+            handledConfigChanges |= ActivityInfo.CONFIG_UI_MODE;
+        }
+
+        final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig,
+                r.mSizeConfigurations, handledConfigChanges, alwaysReportChange);
         // Nothing significant, don't proceed with updating and reporting.
         if (!shouldUpdateResources && !shouldReportChange) {
             return null;
@@ -6161,6 +6176,25 @@
     }
 
     /**
+     * Returns true if the uiMode configuration changed, and desk mode
+     * ({@link android.content.res.Configuration#UI_MODE_TYPE_DESK}) was the only change to uiMode.
+     */
+    private boolean onlyDeskInUiModeChanged(Configuration oldConfig, Configuration newConfig) {
+        boolean deskModeChanged = isInDeskUiMode(oldConfig) != isInDeskUiMode(newConfig);
+
+        // UI mode contains fields other than the UI mode type, so determine if any other fields
+        // changed.
+        boolean uiModeOtherFieldsChanged =
+                (oldConfig.uiMode & ~UI_MODE_TYPE_MASK) != (newConfig.uiMode & ~UI_MODE_TYPE_MASK);
+
+        return deskModeChanged && !uiModeOtherFieldsChanged;
+    }
+
+    private static boolean isInDeskUiMode(Configuration config) {
+        return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_DESK;
+    }
+
+    /**
      * Returns {@code true} if {@link Activity#onConfigurationChanged(Configuration)} should be
      * dispatched.
      *
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 060a5c8..af41370 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -32,6 +32,7 @@
 import android.companion.AssociationInfo;
 import android.companion.virtual.audio.VirtualAudioDevice;
 import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
+import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.content.ComponentName;
 import android.content.Context;
@@ -173,6 +174,9 @@
             int associationId,
             @NonNull VirtualDeviceParams params) {
         Objects.requireNonNull(params, "params must not be null");
+        if (Flags.moreLogs()) {
+            Log.i(TAG, "Creating VirtualDevice");
+        }
         try {
             return new VirtualDevice(mService, mContext, associationId, params);
         } catch (RemoteException e) {
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
new file mode 100644
index 0000000..39b99c6
--- /dev/null
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.companion.virtual.flags"
+
+flag {
+  name: "more_logs"
+  namespace: "virtual_devices"
+  description: "More logs to test flags with"
+  bug: "291725823"
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index afeb3d29..31f6418 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6705,6 +6705,15 @@
     public static final String EXTRA_VISIBILITY_ALLOW_LIST =
             "android.intent.extra.VISIBILITY_ALLOW_LIST";
 
+    /**
+     * A boolean extra used with {@link #ACTION_PACKAGE_DATA_CLEARED} which indicates if the intent
+     * is broadcast as part of a restore operation.
+     *
+     * @hide
+     */
+    public static final String EXTRA_IS_RESTORE =
+            "android.intent.extra.IS_RESTORE";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Intent flags (see mFlags variable).
diff --git a/core/java/android/content/pm/ArchivedPackageParcel.aidl b/core/java/android/content/pm/ArchivedPackageParcel.aidl
new file mode 100644
index 0000000..b34b708
--- /dev/null
+++ b/core/java/android/content/pm/ArchivedPackageParcel.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.content.pm;
+
+import android.content.pm.SigningDetails;
+
+/**
+ * Contains fields required for archived package installation,
+ * i.e. installation without an APK.
+ * @hide
+ */
+parcelable ArchivedPackageParcel {
+    String packageName;
+    SigningDetails signingDetails;
+    int versionCode;
+    int versionCodeMajor;
+    int targetSdkVersion;
+    boolean clearUserDataAllowed;
+    boolean backupAllowed;
+    boolean defaultToDeviceProtectedStorage;
+    boolean requestLegacyExternalStorage;
+    boolean userDataFragile;
+    boolean clearUserDataOnFailedRestoreAllowed;
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 4ed4dd3..916c249 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -22,6 +22,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.ChangedPackages;
 import android.content.pm.InstantAppInfo;
 import android.content.pm.FeatureInfo;
@@ -833,4 +834,6 @@
     void registerPackageMonitorCallback(IRemoteCallback callback, int userId);
 
     void unregisterPackageMonitorCallback(IRemoteCallback callback);
+
+    ArchivedPackageParcel getArchivedPackage(in String apkPath);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d2173a6..c384389 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1682,6 +1682,13 @@
     public static final int INSTALL_FROM_MANAGED_USER_OR_PROFILE = 1 << 26;
 
     /**
+     * Flag parameter for {@link PackageInstaller.SessionParams} to indicate that this
+     * session is for archived package installation.
+     * @hide
+     */
+    public static final int INSTALL_ARCHIVED = 1 << 27;
+
+    /**
      * Flag parameter for {@link #installPackage} to force a non-staged update of an APEX. This is
      * a development-only feature and should not be used on end user devices.
      *
diff --git a/core/java/android/content/pm/SigningDetails.aidl b/core/java/android/content/pm/SigningDetails.aidl
new file mode 100644
index 0000000..95f3ca7
--- /dev/null
+++ b/core/java/android/content/pm/SigningDetails.aidl
@@ -0,0 +1,18 @@
+/*
+ * 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.content.pm;
+
+parcelable SigningDetails;
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 269bec2..71cfd1b 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.SigningDetails;
@@ -138,6 +139,39 @@
      */
     private final boolean mIsSdkLibrary;
 
+    /**
+     * Indicates whether this application's data will be cleared on a failed restore.
+     */
+    private final boolean mClearUserDataAllowed;
+
+    /**
+     *  Set to <code>false</code> if the application does not wish to permit any OS-driven
+     *  backups of its data; <code>true</code> otherwise.
+     */
+    private final boolean mBackupAllowed;
+
+    /**
+     * When set, the default data storage directory for this app is pointed at
+     * the device-protected location.
+     */
+    private final boolean mDefaultToDeviceProtectedStorage;
+
+    /**
+     * If {@code true} this app requests full external storage access.
+     */
+    private final boolean mRequestLegacyExternalStorage;
+
+    /**
+     * Indicates whether this application has declared its user data as fragile, causing the
+     * system to prompt the user on whether to keep the user data on uninstall.
+     */
+    private final boolean mUserDataFragile;
+
+    /**
+     * Indicates whether this application's data will be cleared on a failed restore.
+     */
+    private final boolean mClearUserDataOnFailedRestoreAllowed;
+
     public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit,
             String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode,
             int versionCodeMajor, int revisionCode, int installLocation,
@@ -148,7 +182,10 @@
             String requiredSystemPropertyName, String requiredSystemPropertyValue,
             int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
             Set<String> requiredSplitTypes, Set<String> splitTypes,
-            boolean hasDeviceAdminReceiver, boolean isSdkLibrary) {
+            boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean clearUserDataAllowed,
+            boolean backupAllowed, boolean defaultToDeviceProtectedStorage,
+            boolean requestLegacyExternalStorage, boolean userDataFragile,
+            boolean clearUserDataOnFailedRestoreAllowed) {
         mPath = path;
         mPackageName = packageName;
         mSplitName = splitName;
@@ -182,6 +219,54 @@
         mRollbackDataPolicy = rollbackDataPolicy;
         mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
         mIsSdkLibrary = isSdkLibrary;
+        mClearUserDataAllowed = clearUserDataAllowed;
+        mBackupAllowed = backupAllowed;
+        mDefaultToDeviceProtectedStorage = defaultToDeviceProtectedStorage;
+        mRequestLegacyExternalStorage = requestLegacyExternalStorage;
+        mUserDataFragile = userDataFragile;
+        mClearUserDataOnFailedRestoreAllowed = clearUserDataOnFailedRestoreAllowed;
+    }
+
+    public ApkLite(String path, ArchivedPackageParcel archivedPackage) {
+        mPath = path;
+        mPackageName = archivedPackage.packageName;
+        mSplitName = null; // base.apk
+        mSplitTypes = null;
+        mFeatureSplit = false;
+        mConfigForSplit = null;
+        mUsesSplitName = null;
+        mRequiredSplitTypes = null;
+        mSplitRequired = hasAnyRequiredSplitTypes();
+        mVersionCode = archivedPackage.versionCode;
+        mVersionCodeMajor = archivedPackage.versionCodeMajor;
+        mRevisionCode = 0;
+        mInstallLocation = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+        mVerifiers = new VerifierInfo[]{};
+        mSigningDetails = archivedPackage.signingDetails;
+        mCoreApp = false;
+        mDebuggable = false;
+        mProfileableByShell = false;
+        mMultiArch = false;
+        mUse32bitAbi = false;
+        mUseEmbeddedDex = false;
+        mExtractNativeLibs = false;
+        mIsolatedSplits = false;
+        mTargetPackageName = null;
+        mOverlayIsStatic = false;
+        mOverlayPriority = 0;
+        mRequiredSystemPropertyName = null;
+        mRequiredSystemPropertyValue = null;
+        mMinSdkVersion = ApkLiteParseUtils.DEFAULT_MIN_SDK_VERSION;
+        mTargetSdkVersion = archivedPackage.targetSdkVersion;
+        mRollbackDataPolicy = 0;
+        mHasDeviceAdminReceiver = false;
+        mIsSdkLibrary = false;
+        mClearUserDataAllowed = archivedPackage.clearUserDataAllowed;
+        mBackupAllowed = archivedPackage.backupAllowed;
+        mDefaultToDeviceProtectedStorage = archivedPackage.defaultToDeviceProtectedStorage;
+        mRequestLegacyExternalStorage = archivedPackage.requestLegacyExternalStorage;
+        mUserDataFragile = archivedPackage.userDataFragile;
+        mClearUserDataOnFailedRestoreAllowed = archivedPackage.clearUserDataOnFailedRestoreAllowed;
     }
 
     /**
@@ -474,6 +559,9 @@
         return mRollbackDataPolicy;
     }
 
+    /**
+     * Indicates if this app contains a {@link android.app.admin.DeviceAdminReceiver}.
+     */
     @DataClass.Generated.Member
     public boolean isHasDeviceAdminReceiver() {
         return mHasDeviceAdminReceiver;
@@ -487,11 +575,62 @@
         return mIsSdkLibrary;
     }
 
+    /**
+     * Indicates whether this application's data will be cleared on a failed restore.
+     */
+    @DataClass.Generated.Member
+    public boolean isClearUserDataAllowed() {
+        return mClearUserDataAllowed;
+    }
+
+    /**
+     *  Set to <code>false</code> if the application does not wish to permit any OS-driven
+     *  backups of its data; <code>true</code> otherwise.
+     */
+    @DataClass.Generated.Member
+    public boolean isBackupAllowed() {
+        return mBackupAllowed;
+    }
+
+    /**
+     * When set, the default data storage directory for this app is pointed at
+     * the device-protected location.
+     */
+    @DataClass.Generated.Member
+    public boolean isDefaultToDeviceProtectedStorage() {
+        return mDefaultToDeviceProtectedStorage;
+    }
+
+    /**
+     * If {@code true} this app requests full external storage access.
+     */
+    @DataClass.Generated.Member
+    public boolean isRequestLegacyExternalStorage() {
+        return mRequestLegacyExternalStorage;
+    }
+
+    /**
+     * Indicates whether this application has declared its user data as fragile, causing the
+     * system to prompt the user on whether to keep the user data on uninstall.
+     */
+    @DataClass.Generated.Member
+    public boolean isUserDataFragile() {
+        return mUserDataFragile;
+    }
+
+    /**
+     * Indicates whether this application's data will be cleared on a failed restore.
+     */
+    @DataClass.Generated.Member
+    public boolean isClearUserDataOnFailedRestoreAllowed() {
+        return mClearUserDataOnFailedRestoreAllowed;
+    }
+
     @DataClass.Generated(
-            time = 1643063342990L,
+            time = 1693422809896L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\nprivate final  boolean mHasDeviceAdminReceiver\nprivate final  boolean mIsSdkLibrary\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\nprivate final  boolean mHasDeviceAdminReceiver\nprivate final  boolean mIsSdkLibrary\nprivate final  boolean mClearUserDataAllowed\nprivate final  boolean mBackupAllowed\nprivate final  boolean mDefaultToDeviceProtectedStorage\nprivate final  boolean mRequestLegacyExternalStorage\nprivate final  boolean mUserDataFragile\nprivate final  boolean mClearUserDataOnFailedRestoreAllowed\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 4f6bcb6..066ff689 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -40,6 +40,7 @@
 import android.util.Slog;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
 
 import libcore.io.IoUtils;
 
@@ -73,7 +74,7 @@
     // Constants copied from services.jar side since they're not accessible
     private static final String ANDROID_RES_NAMESPACE =
             "http://schemas.android.com/apk/res/android";
-    private static final int DEFAULT_MIN_SDK_VERSION = 1;
+    public static final int DEFAULT_MIN_SDK_VERSION = 1;
     private static final int DEFAULT_TARGET_SDK_VERSION = 0;
     public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
     private static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
@@ -446,6 +447,13 @@
         int overlayPriority = 0;
         int rollbackDataPolicy = 0;
 
+        boolean clearUserDataAllowed = true;
+        boolean backupAllowed = true;
+        boolean defaultToDeviceProtectedStorage = false;
+        String requestLegacyExternalStorage = null;
+        boolean userDataFragile = false;
+        boolean clearUserDataOnFailedRestoreAllowed = true;
+
         String requiredSystemPropertyName = null;
         String requiredSystemPropertyValue = null;
 
@@ -484,6 +492,23 @@
                         "extractNativeLibs", true);
                 useEmbeddedDex = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
                         "useEmbeddedDex", false);
+
+                clearUserDataAllowed = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+                        "allowClearUserDataOnFailedRestore", true);
+                backupAllowed = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+                        "allowBackup", true);
+                defaultToDeviceProtectedStorage = parser.getAttributeBooleanValue(
+                        ANDROID_RES_NAMESPACE,
+                        "defaultToDeviceProtectedStorage", false);
+                userDataFragile = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+                        "hasFragileUserData", false);
+                clearUserDataOnFailedRestoreAllowed = parser.getAttributeBooleanValue(
+                        ANDROID_RES_NAMESPACE,
+                        "allowClearUserDataOnFailedRestore", true);
+
+                requestLegacyExternalStorage = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
+                        "requestLegacyExternalStorage");
+
                 rollbackDataPolicy = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
                         "rollbackDataPolicy", 0);
                 String permission = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
@@ -604,6 +629,9 @@
             return input.skip(message);
         }
 
+        boolean isRequestLegacyExternalStorage = XmlUtils.convertValueToBoolean(
+                requestLegacyExternalStorage, targetSdkVersion < Build.VERSION_CODES.Q);
+
         return input.success(
                 new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
                         configForSplit, usesSplitName, isSplitRequired, versionCode,
@@ -613,7 +641,9 @@
                         overlayIsStatic, overlayPriority, requiredSystemPropertyName,
                         requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
                         rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
-                        hasDeviceAdminReceiver, isSdkLibrary));
+                        hasDeviceAdminReceiver, isSdkLibrary, clearUserDataAllowed, backupAllowed,
+                        defaultToDeviceProtectedStorage, isRequestLegacyExternalStorage,
+                        userDataFragile, clearUserDataOnFailedRestoreAllowed));
     }
 
     private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java
index e2789c9..4638af7 100644
--- a/core/java/android/content/pm/parsing/PackageLite.java
+++ b/core/java/android/content/pm/parsing/PackageLite.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.PackageInfo;
+import android.content.pm.SigningDetails;
 import android.content.pm.VerifierInfo;
 
 import com.android.internal.util.ArrayUtils;
@@ -78,6 +79,8 @@
     private final int mInstallLocation;
     /** Information about a package verifiers as used during package verification */
     private final @NonNull VerifierInfo[] mVerifiers;
+    /** Signing-related data of an application package */
+    private final @NonNull SigningDetails mSigningDetails;
 
     /** Indicate whether any split APKs that are features. Ordered by splitName */
     private final @Nullable boolean[] mIsFeatureSplits;
@@ -109,6 +112,33 @@
      * Indicates if this package is a sdk.
      */
     private final boolean mIsSdkLibrary;
+    /**
+     * Indicates whether this application's data will be cleared on a failed restore.
+     */
+    private final boolean mClearUserDataAllowed;
+    /**
+     *  Set to <code>false</code> if the application does not wish to permit any OS-driven
+     *  backups of its data; <code>true</code> otherwise.
+     */
+    private final boolean mBackupAllowed;
+    /**
+     * When set, the default data storage directory for this app is pointed at
+     * the device-protected location.
+     */
+    private final boolean mDefaultToDeviceProtectedStorage;
+    /**
+     * If {@code true} this app requests full external storage access.
+     */
+    private final boolean mRequestLegacyExternalStorage;
+    /**
+     * Indicates whether this application has declared its user data as fragile, causing the
+     * system to prompt the user on whether to keep the user data on uninstall.
+     */
+    private final boolean mUserDataFragile;
+    /**
+     * Indicates whether this application's data will be cleared on a failed restore.
+     */
+    private final boolean mClearUserDataOnFailedRestoreAllowed;
 
     public PackageLite(String path, String baseApkPath, ApkLite baseApk,
             String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames,
@@ -123,6 +153,7 @@
         mVersionCodeMajor = baseApk.getVersionCodeMajor();
         mInstallLocation = baseApk.getInstallLocation();
         mVerifiers = baseApk.getVerifiers();
+        mSigningDetails = baseApk.getSigningDetails();
         mBaseRevisionCode = baseApk.getRevisionCode();
         mCoreApp = baseApk.isCoreApp();
         mDebuggable = baseApk.isDebuggable();
@@ -144,6 +175,12 @@
         mSplitApkPaths = splitApkPaths;
         mSplitRevisionCodes = splitRevisionCodes;
         mTargetSdk = targetSdk;
+        mClearUserDataAllowed = baseApk.isClearUserDataAllowed();
+        mBackupAllowed = baseApk.isBackupAllowed();
+        mDefaultToDeviceProtectedStorage = baseApk.isDefaultToDeviceProtectedStorage();
+        mRequestLegacyExternalStorage = baseApk.isRequestLegacyExternalStorage();
+        mUserDataFragile = baseApk.isUserDataFragile();
+        mClearUserDataOnFailedRestoreAllowed = baseApk.isClearUserDataOnFailedRestoreAllowed();
     }
 
     /**
@@ -325,6 +362,14 @@
     }
 
     /**
+     * Signing-related data of an application package
+     */
+    @DataClass.Generated.Member
+    public @NonNull SigningDetails getSigningDetails() {
+        return mSigningDetails;
+    }
+
+    /**
      * Indicate whether any split APKs that are features. Ordered by splitName
      */
     @DataClass.Generated.Member
@@ -414,12 +459,62 @@
         return mIsSdkLibrary;
     }
 
+    /**
+     * Indicates whether this application's data will be cleared on a failed restore.
+     */
+    @DataClass.Generated.Member
+    public boolean isClearUserDataAllowed() {
+        return mClearUserDataAllowed;
+    }
+
+    /**
+     *  Set to <code>false</code> if the application does not wish to permit any OS-driven
+     *  backups of its data; <code>true</code> otherwise.
+     */
+    @DataClass.Generated.Member
+    public boolean isBackupAllowed() {
+        return mBackupAllowed;
+    }
+
+    /**
+     * When set, the default data storage directory for this app is pointed at
+     * the device-protected location.
+     */
+    @DataClass.Generated.Member
+    public boolean isDefaultToDeviceProtectedStorage() {
+        return mDefaultToDeviceProtectedStorage;
+    }
+
+    /**
+     * If {@code true} this app requests full external storage access.
+     */
+    @DataClass.Generated.Member
+    public boolean isRequestLegacyExternalStorage() {
+        return mRequestLegacyExternalStorage;
+    }
+
+    /**
+     * Indicates whether this application has declared its user data as fragile, causing the
+     * system to prompt the user on whether to keep the user data on uninstall.
+     */
+    @DataClass.Generated.Member
+    public boolean isUserDataFragile() {
+        return mUserDataFragile;
+    }
+
+    /**
+     * Indicates whether this application's data will be cleared on a failed restore.
+     */
+    @DataClass.Generated.Member
+    public boolean isClearUserDataOnFailedRestoreAllowed() {
+        return mClearUserDataOnFailedRestoreAllowed;
+    }
+
     @DataClass.Generated(
-            time = 1643132127068L,
+            time = 1693423910860L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
-            inputSignatures =
-                    "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mTargetSdk\nprivate final  int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final  int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mProfileableByShell\nprivate final  boolean mUseEmbeddedDex\nprivate final  boolean mIsSdkLibrary\npublic  java.util.List<java.lang.String> getAllApkPaths()\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mTargetSdk\nprivate final  int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final  int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mProfileableByShell\nprivate final  boolean mUseEmbeddedDex\nprivate final  boolean mIsSdkLibrary\nprivate final  boolean mClearUserDataAllowed\nprivate final  boolean mBackupAllowed\nprivate final  boolean mDefaultToDeviceProtectedStorage\nprivate final  boolean mRequestLegacyExternalStorage\nprivate final  boolean mUserDataFragile\nprivate final  boolean mClearUserDataOnFailedRestoreAllowed\npublic  java.util.List<java.lang.String> getAllApkPaths()\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 76b29e6..5cc3b92 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,7 +27,6 @@
 import android.annotation.RawRes;
 import android.annotation.StyleRes;
 import android.annotation.StyleableRes;
-import android.app.ResourcesManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
@@ -430,49 +429,35 @@
                 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
                     if (locales.size() > 1) {
                         String[] availableLocales;
-                        if (ResourcesManager.getInstance().getLocaleList().isEmpty()) {
-                            // The LocaleList has changed. We must query the AssetManager's
-                            // available Locales and figure out the best matching Locale in the new
-                            // LocaleList.
-                            availableLocales = mAssets.getNonSystemLocales();
+                        // The LocaleList has changed. We must query the AssetManager's
+                        // available Locales and figure out the best matching Locale in the new
+                        // LocaleList.
+                        availableLocales = mAssets.getNonSystemLocales();
+                        if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+                            // No app defined locales, so grab the system locales.
+                            availableLocales = mAssets.getLocales();
                             if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
-                                // No app defined locales, so grab the system locales.
-                                availableLocales = mAssets.getLocales();
-                                if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
-                                    availableLocales = null;
-                                }
+                                availableLocales = null;
                             }
+                        }
 
-                            if (availableLocales != null) {
-                                final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
-                                        availableLocales);
-                                if (bestLocale != null) {
-                                    selectedLocales = new String[]{
-                                            adjustLanguageTag(bestLocale.toLanguageTag())};
-                                    if (!bestLocale.equals(locales.get(0))) {
-                                        mConfiguration.setLocales(
-                                                new LocaleList(bestLocale, locales));
-                                    }
+                        if (availableLocales != null) {
+                            final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+                                    availableLocales);
+                            if (bestLocale != null) {
+                                selectedLocales = new String[]{
+                                        adjustLanguageTag(bestLocale.toLanguageTag())};
+                                if (!bestLocale.equals(locales.get(0))) {
+                                    mConfiguration.setLocales(
+                                            new LocaleList(bestLocale, locales));
                                 }
                             }
-                        } else {
-                            selectedLocales = locales.getIntersection(
-                                    ResourcesManager.getInstance().getLocaleList());
-                            defaultLocale = ResourcesManager.getInstance()
-                                    .getLocaleList().get(0).toLanguageTag();
                         }
                     }
                 }
                 if (selectedLocales == null) {
-                    if (ResourcesManager.getInstance().getLocaleList().isEmpty()) {
-                        selectedLocales = new String[]{
-                                adjustLanguageTag(locales.get(0).toLanguageTag())};
-                    } else {
-                        selectedLocales = new String[locales.size()];
-                        for (int i = 0; i < locales.size(); i++) {
-                            selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
-                        }
-                    }
+                    selectedLocales = new String[]{
+                            adjustLanguageTag(locales.get(0).toLanguageTag())};
                 }
 
                 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
diff --git a/core/java/android/hardware/SensorAdditionalInfo.java b/core/java/android/hardware/SensorAdditionalInfo.java
index 59def9f..d1e101d 100644
--- a/core/java/android/hardware/SensorAdditionalInfo.java
+++ b/core/java/android/hardware/SensorAdditionalInfo.java
@@ -257,7 +257,7 @@
     public static SensorAdditionalInfo createLocalGeomagneticField(
             float strength, float declination, float inclination) {
         if (strength < 10 || strength > 100 // much beyond extreme values on earth
-                || declination < 0 || declination > Math.PI
+                || declination < -Math.PI / 2 || declination > Math.PI / 2
                 || inclination < -Math.PI / 2 || inclination > Math.PI / 2) {
             throw new IllegalArgumentException("Geomagnetic field info out of range");
         }
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 181ab2c..994037b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1149,11 +1149,7 @@
                             "remove holder for requestId %d, "
                             + "because lastFrame is %d.",
                             requestId, lastFrameNumber));
-                }
-            }
 
-            if (holder != null) {
-                if (DEBUG) {
                     Log.v(TAG, "immediately trigger onCaptureSequenceAborted because"
                             + " request did not reach HAL");
                 }
@@ -2180,11 +2176,9 @@
 
                 final CaptureCallbackHolder holder =
                         CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
-                final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
 
                 boolean isPartialResult =
                         (resultExtras.getPartialResultCount() < mTotalPartialCount);
-                int requestType = request.getRequestType();
 
                 // Check if we have a callback for this
                 if (holder == null) {
@@ -2194,12 +2188,11 @@
                                         + frameNumber);
                     }
 
-                    updateTracker(requestId, frameNumber, requestType, /*result*/null,
-                            isPartialResult);
-
                     return;
                 }
 
+                final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
+                int requestType = request.getRequestType();
                 if (isClosed()) {
                     if (DEBUG) {
                         Log.d(TAG,
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index c0a44b1..f1ae9be 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -890,10 +890,13 @@
             mSystemCallingHideSoftInput = true;
             mCurHideInputToken = hideInputToken;
             mCurStatsToken = statsToken;
-            hideSoftInput(flags, resultReceiver);
-            mCurStatsToken = null;
-            mCurHideInputToken = null;
-            mSystemCallingHideSoftInput = false;
+            try {
+                hideSoftInput(flags, resultReceiver);
+            } finally {
+                mCurStatsToken = null;
+                mCurHideInputToken = null;
+                mSystemCallingHideSoftInput = false;
+            }
         }
 
         /**
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index ed14652..1a3dcee 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -126,50 +126,65 @@
     /**
      * Returns true if IP forwarding is enabled
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+            publicAlternatives = "Use {@code android.net.INetd#ipfwdEnabled}")
     boolean getIpForwardingEnabled();
 
     /**
      * Enables/Disables IP Forwarding
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+            publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
+            + "{@code android.net.TetheringManager#startTethering}. See also "
+            + "{@code INetd#ipfwdEnableForwarding(String)}.")
     void setIpForwardingEnabled(boolean enabled);
 
     /**
      * Start tethering services with the specified dhcp server range
      * arg is a set of start end pairs defining the ranges.
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+            publicAlternatives = "{@code android.net.TetheringManager#startTethering}")
     void startTethering(in String[] dhcpRanges);
 
     /**
      * Stop currently running tethering services
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+            publicAlternatives = "{@code android.net.TetheringManager#stopTethering(int)}")
     void stopTethering();
 
     /**
      * Returns true if tethering services are started
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+            publicAlternatives = "Generally track your own tethering requests. "
+            + "See also {@code android.net.INetd#tetherIsEnabled()}")
     boolean isTetheringStarted();
 
     /**
      * Tethers the specified interface
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+            publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
+            + "{@code android.net.TetheringManager#startTethering}. See also "
+            + "{@code com.android.net.module.util.NetdUtils#tetherInterface}.")
     void tetherInterface(String iface);
 
     /**
      * Untethers the specified interface
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+            publicAlternatives = "Avoid using this directly. Instead, disable "
+            + "tethering with {@code android.net.TetheringManager#stopTethering(int)}. "
+            + "See also {@code NetdUtils#untetherInterface}.")
     void untetherInterface(String iface);
 
     /**
      * Returns a list of currently tethered interfaces
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+            publicAlternatives = "{@code android.net.TetheringManager#getTetheredIfaces()}")
     String[] listTetheredInterfaces();
 
     /**
@@ -177,13 +192,17 @@
      *  The address and netmask of the external interface is used for
      *  the NAT'ed network.
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+            publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
+            + "{@code android.net.TetheringManager#startTethering}.")
     void enableNat(String internalInterface, String externalInterface);
 
     /**
      *  Disables Network Address Translation between two interfaces.
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+            publicAlternatives = "Avoid using this directly. Instead, disable tethering with "
+            + "{@code android.net.TetheringManager#stopTethering(int)}.")
     void disableNat(String internalInterface, String externalInterface);
 
     /**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bcde31a..c6cb604 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1002,6 +1002,24 @@
     public static final String DISALLOW_ADD_CLONE_PROFILE = "no_add_clone_profile";
 
     /**
+     * Specifies if a user is disallowed from creating a private profile.
+     * <p>The default value for an unmanaged user is <code>false</code>.
+     * For users with a device owner set, the default is <code>true</code>.
+     *
+     * <p>Holders of the permission
+     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILES}
+     * can set this restriction using the DevicePolicyManager APIs mentioned below.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     * @hide
+     */
+    public static final String DISALLOW_ADD_PRIVATE_PROFILE = "no_add_private_profile";
+
+    /**
      * Specifies if a user is disallowed from disabling application verification. The default
      * value is <code>false</code>.
      *
@@ -1895,6 +1913,7 @@
             DISALLOW_ADD_USER,
             DISALLOW_ADD_MANAGED_PROFILE,
             DISALLOW_ADD_CLONE_PROFILE,
+            DISALLOW_ADD_PRIVATE_PROFILE,
             ENSURE_VERIFY_APPS,
             DISALLOW_CONFIG_CELL_BROADCASTS,
             DISALLOW_CONFIG_MOBILE_NETWORKS,
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
new file mode 100644
index 0000000..851aa6d
--- /dev/null
+++ b/core/java/android/os/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.os"
+
+flag {
+    name: "disallow_cellular_null_ciphers_restriction"
+    namespace: "cellular_security"
+    description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices."
+    bug: "276752881"
+}
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 22e8251..8961846 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -20,8 +20,11 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.pm.UserInfo;
+import android.os.IInstalld;
 import android.os.IVold;
+import android.os.ParcelFileDescriptor;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Set;
 
@@ -185,4 +188,17 @@
     public abstract void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
             List<UserInfo> users);
 
+    /**
+     * A proxy call to the corresponding method in Installer.
+     * @see com.android.server.pm.Installer#createFsveritySetupAuthToken()
+     */
+    public abstract IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken(
+            ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId) throws IOException;
+
+    /**
+     * A proxy call to the corresponding method in Installer.
+     * @see com.android.server.pm.Installer#enableFsverity()
+     */
+    public abstract int enableFsverity(IInstalld.IFsveritySetupAuthToken authToken, String filePath,
+            String packageName) throws IOException;
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 522caac..abc0c7a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5885,15 +5885,6 @@
         public static final String MULTI_AUDIO_FOCUS_ENABLED = "multi_audio_focus_enabled";
 
         /**
-         * Whether desktop mode is enabled or not.
-         * 0 = off
-         * 1 = on
-         * @hide
-         */
-        @Readable
-        public static final String DESKTOP_MODE = "desktop_mode";
-
-        /**
          * The information of locale preference. This records user's preference to avoid
          * unsynchronized and existing locale preference in
          * {@link Locale#getDefault(Locale.Category)}.
@@ -6066,7 +6057,6 @@
             PRIVATE_SETTINGS.add(SHOW_BATTERY_PERCENT);
             PRIVATE_SETTINGS.add(DISPLAY_COLOR_MODE);
             PRIVATE_SETTINGS.add(DISPLAY_COLOR_MODE_VENDOR_HINT);
-            PRIVATE_SETTINGS.add(DESKTOP_MODE);
             PRIVATE_SETTINGS.add(LOCALE_PREFERENCES);
             PRIVATE_SETTINGS.add(TOUCHPAD_POINTER_SPEED);
             PRIVATE_SETTINGS.add(TOUCHPAD_NATURAL_SCROLLING);
@@ -18370,24 +18360,28 @@
              * If hotword detection should be enabled.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String HOTWORD_DETECTION_ENABLED = "hotword_detection_enabled";
 
             /**
              * Whether Smart Replies are enabled within Wear.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String SMART_REPLIES_ENABLED = "smart_replies_enabled";
 
             /**
              * The default vibration pattern.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String DEFAULT_VIBRATION = "default_vibration";
 
             /**
              * If FLP should obtain location data from the paired device.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String OBTAIN_PAIRED_DEVICE_LOCATION =
                     "obtain_paired_device_location";
 
@@ -18395,6 +18389,7 @@
              * The play store availability on companion phone.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String PHONE_PLAY_STORE_AVAILABILITY =
                     "phone_play_store_availability";
 
@@ -18410,6 +18405,7 @@
              * Whether the bug report is enabled.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String BUG_REPORT = "bug_report";
 
             // Possible bug report states
@@ -18422,12 +18418,14 @@
              * The enabled/disabled state of the SmartIlluminate.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String SMART_ILLUMINATE_ENABLED = "smart_illuminate_enabled";
 
             /**
              * Whether automatic time is enabled on the watch.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String CLOCKWORK_AUTO_TIME = "clockwork_auto_time";
 
             // Possible clockwork auto time states
@@ -18445,6 +18443,7 @@
              * Whether automatic time zone is enabled on the watch.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String CLOCKWORK_AUTO_TIME_ZONE = "clockwork_auto_time_zone";
 
             // Possible clockwork auto time zone states
@@ -18461,12 +18460,14 @@
              * Whether 24 hour time format is enabled on the watch.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String CLOCKWORK_24HR_TIME = "clockwork_24hr_time";
 
             /**
              * Whether the auto wifi toggle setting is enabled.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String AUTO_WIFI = "auto_wifi";
 
             // Possible force wifi on states
@@ -18486,6 +18487,7 @@
              * wifi requirement until this time). The time is in millis since epoch.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String ALT_BYPASS_WIFI_REQUIREMENT_TIME_MILLIS =
                     "alt_bypass_wifi_requirement_time_millis";
 
@@ -18493,6 +18495,7 @@
              * Whether the setup was skipped.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String SETUP_SKIPPED = "setup_skipped";
 
             // Possible setup_skipped states
@@ -18507,6 +18510,7 @@
              * The last requested call forwarding action.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String LAST_CALL_FORWARD_ACTION = "last_call_forward_action";
 
             // Possible call forwarding actions
@@ -18519,22 +18523,31 @@
 
             // Stem button settings.
             /** @hide */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String STEM_1_TYPE = "STEM_1_TYPE";
             /** @hide */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String STEM_1_DATA = "STEM_1_DATA";
             /** @hide */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String STEM_1_DEFAULT_DATA = "STEM_1_DEFAULT_DATA";
             /** @hide */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String STEM_2_TYPE = "STEM_2_TYPE";
             /** @hide */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String STEM_2_DATA = "STEM_2_DATA";
             /** @hide */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String STEM_2_DEFAULT_DATA = "STEM_2_DEFAULT_DATA";
             /** @hide */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String STEM_3_TYPE = "STEM_3_TYPE";
             /** @hide */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String STEM_3_DATA = "STEM_3_DATA";
             /** @hide */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String STEM_3_DEFAULT_DATA = "STEM_3_DEFAULT_DATA";
 
             // Stem types
@@ -18549,12 +18562,14 @@
              * If the device should be muted when off body.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String MUTE_WHEN_OFF_BODY_ENABLED = "obtain_mute_when_off_body";
 
             /**
              * Wear OS version string.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String WEAR_OS_VERSION_STRING = "wear_os_version_string";
 
             /**
@@ -18567,24 +18582,28 @@
              * The android wear system version.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String ANDROID_WEAR_VERSION = "android_wear_version";
 
             /**
              * The wear system capabiltiies.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String SYSTEM_CAPABILITIES = "system_capabilities";
 
             /**
              * The android wear system edition.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String SYSTEM_EDITION = "android_wear_system_edition";
 
             /**
              * The Wear platform MR number.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String WEAR_PLATFORM_MR_NUMBER = "wear_platform_mr_number";
 
             /**
@@ -18598,36 +18617,42 @@
              * Whether ambient is currently enabled.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String AMBIENT_ENABLED = "ambient_enabled";
 
             /**
              * Whether ambient tilt to wake is enabled.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String AMBIENT_TILT_TO_WAKE = "ambient_tilt_to_wake";
 
             /**
              * Whether ambient low bit mode is enabled by developer options.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String AMBIENT_LOW_BIT_ENABLED_DEV = "ambient_low_bit_enabled_dev";
 
             /**
              * Whether ambient touch to wake is enabled.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String AMBIENT_TOUCH_TO_WAKE = "ambient_touch_to_wake";
 
             /**
              * Whether ambient tilt to bright is enabled.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String AMBIENT_TILT_TO_BRIGHT = "ambient_tilt_to_bright";
 
             /**
              * Whether touch and hold to edit WF is enabled
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String GESTURE_TOUCH_AND_HOLD_WATCH_FACE_ENABLED =
                     "gesture_touch_and_hold_watchface_enabled";
 
@@ -18641,6 +18666,7 @@
              * Whether bedtime mode is enabled.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String BEDTIME_MODE = "bedtime_mode";
 
             /**
@@ -18652,31 +18678,35 @@
              * Whether the current watchface is decomposable.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String DECOMPOSABLE_WATCHFACE = "current_watchface_decomposable";
 
             /**
              * Whether to force ambient when docked.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String AMBIENT_FORCE_WHEN_DOCKED = "ambient_force_when_docked";
 
             /**
              * Whether the ambient low bit mode is enabled.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String AMBIENT_LOW_BIT_ENABLED = "ambient_low_bit_enabled";
 
             /**
              * The timeout duration in minutes of ambient mode when plugged in.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String AMBIENT_PLUGGED_TIMEOUT_MIN = "ambient_plugged_timeout_min";
 
             /**
              * What OS does paired device has.
              * @hide
              */
-            @Readable
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String PAIRED_DEVICE_OS_TYPE = "paired_device_os_type";
 
             // Possible values of PAIRED_DEVICE_OS_TYPE
@@ -18709,6 +18739,7 @@
              * The user's last setting for hfp client.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String USER_HFP_CLIENT_SETTING = "user_hfp_client_setting";
 
             // Possible hfp client user setting values
@@ -18733,6 +18764,7 @@
              * The companion App name.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String COMPANION_APP_NAME = "wear_companion_app_name";
 
             /**
@@ -18740,18 +18772,21 @@
              * wear. 1 for supporting, 0 for not supporting.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String ENABLE_ALL_LANGUAGES = "enable_all_languages";
 
             /**
              * The Locale (as language tag) the user chose at startup.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String SETUP_LOCALE = "setup_locale";
 
             /**
              * The version of oem setup present.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String OEM_SETUP_VERSION = "oem_setup_version";
 
             /**
@@ -18797,6 +18832,7 @@
              * -{@link BATTERY_SAVER_MODE_CUSTOM}
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String BATTERY_SAVER_MODE = "battery_saver_mode";
 
             /**
@@ -18829,6 +18865,7 @@
              * The maximum ambient mode duration when an activity is allowed to auto resume.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MS =
                     "wear_activity_auto_resume_timeout_ms";
 
@@ -18844,6 +18881,7 @@
              * If burn in protection is enabled.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String BURN_IN_PROTECTION_ENABLED = "burn_in_protection";
 
             /**
@@ -18862,6 +18900,7 @@
              *          RIGHT_WRIST_ROTATION_0 = "2", RIGHT_WRIST_ROTATION_180 = "3"
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String WRIST_ORIENTATION_MODE = "wear_wrist_orientation_mode";
 
             /**
@@ -18900,6 +18939,7 @@
              *
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String CLOCKWORK_SYSUI_PACKAGE = "clockwork_sysui_package";
 
             /**
@@ -18929,6 +18969,7 @@
              * Whether the device has Wet Mode/ Touch Lock Mode enabled.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String WET_MODE_ON = "wet_mode_on";
 
             /**
@@ -18947,6 +18988,7 @@
              * Whether charging sounds are enabled.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String CHARGING_SOUNDS_ENABLED = "wear_charging_sounds_enabled";
 
             /**
@@ -18955,6 +18997,7 @@
              *
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String DYNAMIC_COLOR_THEME_ENABLED = "dynamic_color_theme_enabled";
 
             /**
@@ -19046,6 +19089,7 @@
              * The key to indicate the data migration status on device upgrade in Wear Services.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String UPGRADE_DATA_MIGRATION_STATUS =
                     "upgrade_data_migration_status";
 
@@ -19096,17 +19140,20 @@
              * The custom foreground color.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String CUSTOM_COLOR_FOREGROUND = "custom_foreground_color";
 
             /**
              * The custom background color.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String CUSTOM_COLOR_BACKGROUND = "custom_background_color";
 
             /** The status of the phone switching process.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String PHONE_SWITCHING_STATUS = "phone_switching_status";
 
             /**
@@ -19187,6 +19234,7 @@
              * (0 = false, 1 = true)
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String REDUCE_MOTION = "reduce_motion";
 
             /**
@@ -19248,6 +19296,7 @@
              * Controls the launcher ui mode on wearable devices.
              * @hide
              */
+            @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
             public static final String WEAR_LAUNCHER_UI_MODE = "wear_launcher_ui_mode";
 
             /** Whether Wear Power Anomaly Service is enabled.
diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java
index 266046e..7869404 100644
--- a/core/java/android/security/FileIntegrityManager.java
+++ b/core/java/android/security/FileIntegrityManager.java
@@ -16,12 +16,21 @@
 
 package android.security;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.os.IInstalld.IFsveritySetupAuthToken;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.system.ErrnoException;
 
+import com.android.internal.security.VerityUtils;
+
+import java.io.File;
+import java.io.IOException;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 
@@ -55,6 +64,67 @@
     }
 
     /**
+     * Enables fs-verity to the owned file under the calling app's private directory. It always uses
+     * the common configuration, i.e. SHA-256 digest algorithm, 4K block size, and without salt.
+     *
+     * The operation can only succeed when the file is not opened as writable by any process.
+     *
+     * It takes O(file size) time to build the underlying data structure for continuous
+     * verification. The operation is atomic, i.e. it's either enabled or not, even in case of
+     * power failure during or after the call.
+     *
+     * Note for the API users: When the file's authenticity is crucial, the app typical needs to
+     * perform a signature check by itself before using the file. The signature is often delivered
+     * as a separate file and stored next to the targeting file in the filesystem. The public key of
+     * the signer (normally the same app developer) can be put in the APK, and the app can use the
+     * public key to verify the signature to the file's actual fs-verity digest (from {@link
+     * #getFsverityDigest}) before using the file. The exact format is not prescribed by the
+     * framework. App developers may choose to use common practices like JCA for the signing and
+     * verification, or their own preferred approach.
+     *
+     * @param file The file to enable fs-verity. It should be an absolute path.
+     *
+     * @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a>
+     */
+    @FlaggedApi(Flags.FLAG_FSVERITY_API)
+    public void setupFsverity(@NonNull File file) throws IOException {
+        if (!file.isAbsolute()) {
+            throw new IllegalArgumentException("Expect an absolute path");
+        }
+        IFsveritySetupAuthToken authToken;
+        // fs-verity setup requires no writable fd to the file. Make sure it's closed before
+        // continue.
+        try (var authFd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)) {
+            authToken = mService.createAuthToken(authFd);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        try {
+            int errno = mService.setupFsverity(authToken, file.getPath(),
+                    mContext.getPackageName());
+            if (errno != 0) {
+                new ErrnoException("setupFsverity", errno).rethrowAsIOException();
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the fs-verity digest for the owned file under the calling app's
+     * private directory, or null when the file does not have fs-verity enabled.
+     *
+     * @param file The file to measure the fs-verity digest.
+     * @return The fs-verity digeset in byte[], null if none.
+     * @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a>
+     */
+    @FlaggedApi(Flags.FLAG_FSVERITY_API)
+    public @Nullable byte[] getFsverityDigest(@NonNull File file) throws IOException {
+        return VerityUtils.getFsverityDigest(file.getPath());
+    }
+
+    /**
      * Returns whether the given certificate can be used to prove app's install source. Always
      * return false if the feature is not supported.
      *
diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl
index dff347e..1a6cf88 100644
--- a/core/java/android/security/IFileIntegrityService.aidl
+++ b/core/java/android/security/IFileIntegrityService.aidl
@@ -16,6 +16,9 @@
 
 package android.security;
 
+import android.os.ParcelFileDescriptor;
+import android.os.IInstalld;
+
 /**
  * Binder interface to communicate with FileIntegrityService.
  * @hide
@@ -23,4 +26,8 @@
 interface IFileIntegrityService {
     boolean isApkVeritySupported();
     boolean isAppSourceCertificateTrusted(in byte[] certificateBytes, in String packageName);
+
+    IInstalld.IFsveritySetupAuthToken createAuthToken(in ParcelFileDescriptor authFd);
+    int setupFsverity(IInstalld.IFsveritySetupAuthToken authToken, in String filePath,
+            in String packageName);
 }
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 22b1f02..96c0be7 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -6,4 +6,5 @@
 
 per-file *NetworkSecurityPolicy.java = file:net/OWNERS
 per-file Confirmation*.java = file:/keystore/OWNERS
-per-file FileIntegrityManager.java = victorhsieh@google.com
+per-file FileIntegrityManager.java = file:platform/system/security:/fsverity/OWNERS
+per-file IFileIntegrityService.aidl = file:platform/system/security:/fsverity/OWNERS
diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java
index 55133ae..9b1132a 100644
--- a/core/java/android/service/credentials/Action.java
+++ b/core/java/android/service/credentials/Action.java
@@ -46,7 +46,14 @@
      * <p> See details on usage of {@code Action} for various actionable entries in
      * {@link BeginCreateCredentialResponse} and {@link BeginGetCredentialResponse}.
      *
-     * @param slice the display content to be displayed on the UI, along with this action
+     * @param slice the slice containing the metadata to be shown on the UI, must be constructed
+     *              through the {@link androidx.credentials.provider} Jetpack library;
+     *              If constructed manually, the {@code slice} object must
+     *              contain the non-null properties of the
+     *              {@link androidx.credentials.provider.Action} class populated as slice items
+     *              against specific hints as used in the class's {@code toSlice} method,
+     *              since the Android System uses this library to parse the {@code slice} and
+     *              extract the required attributes
      */
     public Action(@NonNull Slice slice) {
         Objects.requireNonNull(slice, "slice must not be null");
diff --git a/core/java/android/service/credentials/CreateEntry.java b/core/java/android/service/credentials/CreateEntry.java
index 6a9f09f..2495c7d 100644
--- a/core/java/android/service/credentials/CreateEntry.java
+++ b/core/java/android/service/credentials/CreateEntry.java
@@ -66,7 +66,14 @@
     /**
      * Constructs a CreateEntry to be displayed on the UI.
      *
-     * @param slice the display content to be displayed on the UI, along with this entry
+     * @param slice the slice containing the metadata to be shown on the UI, must be constructed
+     *              through the {@link androidx.credentials.provider} Jetpack library;
+     *              If constructed manually, the {@code slice} object must
+     *              contain the non-null properties of the
+     *              {@link androidx.credentials.provider.CreateEntry} class populated as slice items
+     *              against specific hints as used in the class's {@code toSlice} method,
+     *              since the Android System uses this library to parse the {@code slice} and
+     *              extract the required attributes
      */
     public CreateEntry(
             @NonNull Slice slice) {
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 512d833..53094e8 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -69,8 +69,14 @@
      *                                   receive the complete corresponding
      *                                   {@link GetCredentialRequest}.
      * @param type the type of the credential for which this credential entry is being created
-     * @param slice the slice containing the metadata to be shown on the UI. Must be
-     *              constructed through the androidx.credentials jetpack library.
+     * @param slice the slice containing the metadata to be shown on the UI, must be constructed
+     *              through the {@link androidx.credentials.provider} Jetpack library;
+     *              If constructed manually, the {@code slice} object must
+     *              contain the non-null properties of the
+     *              {@link androidx.credentials.provider.CredentialEntry} class populated as slice
+     *              items against specific hints as used in the class's {@code toSlice} method,
+     *              since the Android System uses this library to parse the {@code slice} and
+     *              extract the required attributes
      *
      * @throws IllegalArgumentException If {@code beginGetCredentialOptionId} or {@code type}
      * is null, or empty
diff --git a/core/java/android/service/credentials/RemoteEntry.java b/core/java/android/service/credentials/RemoteEntry.java
index 5b3218a..5fd9925 100644
--- a/core/java/android/service/credentials/RemoteEntry.java
+++ b/core/java/android/service/credentials/RemoteEntry.java
@@ -73,7 +73,14 @@
     /**
      * Constructs a RemoteEntry to be displayed on the UI.
      *
-     * @param slice the display content to be displayed on the UI, along with this entry
+     * @param slice the slice containing the metadata to be shown on the UI, must be constructed
+     *              through the {@link androidx.credentials.provider} Jetpack library;
+     *              If constructed manually, the {@code slice} object must
+     *              contain the non-null properties of the
+     *              {@link androidx.credentials.provider.RemoteEntry} class populated as slice items
+     *              against specific hints as used in the class's {@code toSlice} method,
+     *              since the Android System uses this library to parse the {@code slice} and
+     *              extract the required attributes
      */
     public RemoteEntry(
             @NonNull Slice slice) {
diff --git a/core/java/android/service/dreams/OWNERS b/core/java/android/service/dreams/OWNERS
index 489a5f6..77bcee8 100644
--- a/core/java/android/service/dreams/OWNERS
+++ b/core/java/android/service/dreams/OWNERS
@@ -4,5 +4,7 @@
 dsandler@google.com
 galinap@google.com
 jjaggi@google.com
+lusilva@google.com
 michaelwr@google.com
 santoscordon@google.com
+wxyz@google.com
diff --git a/core/java/android/service/trust/OWNERS b/core/java/android/service/trust/OWNERS
index a895f7f..d352525 100644
--- a/core/java/android/service/trust/OWNERS
+++ b/core/java/android/service/trust/OWNERS
@@ -1,4 +1,4 @@
 # Bug component: 36824
 
-cbrubaker@google.com
 jacobhobbie@google.com
+dlm@google.com
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 23afb03..a208d1f 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -81,6 +81,8 @@
     private int mConnectionCount = 0;
     private final InputMethodManager mImm;
 
+    private final Rect mTempRect = new Rect();
+
     private final RectF mTempRectF = new RectF();
 
     private final Region mTempRegion = new Region();
@@ -401,8 +403,9 @@
 
             final View cachedHoverTarget = getCachedHoverTarget();
             if (cachedHoverTarget != null) {
-                final Rect handwritingArea = getViewHandwritingArea(cachedHoverTarget);
-                if (isInHandwritingArea(handwritingArea, hoverX, hoverY, cachedHoverTarget,
+                final Rect handwritingArea = mTempRect;
+                if (getViewHandwritingArea(cachedHoverTarget, handwritingArea)
+                        && isInHandwritingArea(handwritingArea, hoverX, hoverY, cachedHoverTarget,
                         /* isHover */ true)
                         && shouldTriggerStylusHandwritingForView(cachedHoverTarget)) {
                     return cachedHoverTarget;
@@ -445,8 +448,9 @@
         // directly return the connectedView.
         final View connectedView = getConnectedView();
         if (connectedView != null) {
-            Rect handwritingArea = getViewHandwritingArea(connectedView);
-            if (isInHandwritingArea(handwritingArea, x, y, connectedView, isHover)
+            Rect handwritingArea = mTempRect;
+            if (getViewHandwritingArea(connectedView, handwritingArea)
+                    && isInHandwritingArea(handwritingArea, x, y, connectedView, isHover)
                     && shouldTriggerStylusHandwritingForView(connectedView)) {
                 return connectedView;
             }
@@ -528,28 +532,30 @@
     /**
      * Return the handwriting area of the given view, represented in the window's coordinate.
      * If the view didn't set any handwriting area, it will return the view's boundary.
-     * It will return null if the view or its handwriting area is not visible.
      *
-     * The handwriting area is clipped to its visible part.
+     * <p> The handwriting area is clipped to its visible part.
      * Notice that the returned rectangle is the view's original handwriting area without the
-     * view's handwriting area extends.
+     * view's handwriting area extends. </p>
+     *
+     * @param view the {@link View} whose handwriting area we want to compute.
+     * @param rect the {@link Rect} to receive the result.
+     *
+     * @return true if the view's handwriting area is still visible, or false if it's clipped and
+     * fully invisible. This method only consider the clip by given view's parents, but not the case
+     * where a view is covered by its sibling view.
      */
-    @Nullable
-    private static Rect getViewHandwritingArea(@NonNull View view) {
+    private static boolean getViewHandwritingArea(@NonNull View view, @NonNull Rect rect) {
         final ViewParent viewParent = view.getParent();
         if (viewParent != null && view.isAttachedToWindow() && view.isAggregatedVisible()) {
             final Rect localHandwritingArea = view.getHandwritingArea();
-            final Rect globalHandwritingArea = new Rect();
             if (localHandwritingArea != null) {
-                globalHandwritingArea.set(localHandwritingArea);
+                rect.set(localHandwritingArea);
             } else {
-                globalHandwritingArea.set(0, 0, view.getWidth(), view.getHeight());
+                rect.set(0, 0, view.getWidth(), view.getHeight());
             }
-            if (viewParent.getChildVisibleRect(view, globalHandwritingArea, null)) {
-                return globalHandwritingArea;
-            }
+            return viewParent.getChildVisibleRect(view, rect, null);
         }
-        return null;
+        return false;
     }
 
     /**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 62e37a4..b8385c6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1318,8 +1318,8 @@
      * that have the ignore orientation request display setting enabled by OEMs
      * (enables compatibility mode for fixed orientation on Android 12 (API
      * level 31) or higher; see
-     * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
-     * Large screen app compatibility</a>
+     * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-compatibility-mode">
+     * Large screen compatibility mode</a>
      * for more details).
      *
      * <p>To opt out of the user aspect ratio compatibility override, add this property
@@ -1358,8 +1358,8 @@
      * that have the ignore orientation request display setting enabled by OEMs
      * (enables compatibility mode for fixed orientation on Android 12 (API
      * level 31) or higher; see
-     * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
-     * Large screen app compatibility</a>
+     * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-compatibility-mode">
+     * Large screen compatibility mode</a>
      * for more details).
      *
      * <p>To opt out of the full-screen option of the user aspect ratio compatibility
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index c270053..2747233a 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -17,8 +17,10 @@
 package android.window;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
@@ -107,18 +109,38 @@
     }
 
     /**
-     * Returns {@code true} if the given activities can be displayed on this virtual display and
-     * the windowing mode is supported.
+     * Returns {@code true} if all of the given activities can be launched on this virtual display
+     * in the configuration defined by the rest of the arguments.
+     *
+     * @see #canContainActivity
      */
-    public abstract boolean canContainActivities(@NonNull List<ActivityInfo> activities,
-            @WindowConfiguration.WindowingMode int windowingMode);
+    public boolean canContainActivities(@NonNull List<ActivityInfo> activities,
+            @WindowConfiguration.WindowingMode int windowingMode) {
+        for (int i = 0; i < activities.size(); i++) {
+            if (!canContainActivity(activities.get(i), windowingMode,
+                    /*launchingFromDisplayId=*/ INVALID_DISPLAY, /*isNewTask=*/ false)) {
+                return false;
+            }
+        }
+        return true;
+    }
 
     /**
-     * Returns {@code true} if the given new task can be launched on this virtual display.
+     * Returns {@code true} if the given activity can be launched on this virtual display in the
+     * configuration defined by the rest of the arguments. If the given intent would be intercepted
+     * by the display owner then this means that the activity cannot be launched.
      */
-    public abstract boolean canActivityBeLaunched(@NonNull ActivityInfo activityInfo, Intent intent,
-            @WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
-            boolean isNewTask);
+    public abstract boolean canActivityBeLaunched(@NonNull ActivityInfo activityInfo,
+            @Nullable Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
+            int launchingFromDisplayId, boolean isNewTask);
+
+    /**
+     * Returns {@code true} if the given activity can be launched on this virtual display in the
+     * configuration defined by the rest of the arguments.
+     */
+    protected abstract boolean canContainActivity(@NonNull ActivityInfo activityInfo,
+            @WindowConfiguration.WindowingMode int windowingMode,
+            int launchingFromDisplayId, boolean isNewTask);
 
     /**
      * Called when an Activity window is layouted with the new changes where contains the
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index fff778c..755113b 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -33,6 +33,7 @@
 import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED;
 import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
 import static com.android.internal.app.procstats.ProcessStats.STATE_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_FROZEN;
 import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
 import static com.android.internal.app.procstats.ProcessStats.STATE_HOME;
 import static com.android.internal.app.procstats.ProcessStats.STATE_IMPORTANT_BACKGROUND;
@@ -142,6 +143,7 @@
     private ProcessState mCommonProcess;
     private int mCurCombinedState = STATE_NOTHING;
     private long mStartTime;
+    private int mStateBeforeFrozen = STATE_NOTHING;
 
     private int mLastPssState = STATE_NOTHING;
     private long mLastPssTime;
@@ -423,6 +425,27 @@
     }
 
     /**
+     * Used to notify that this process was frozen.
+     */
+    public void onProcessFrozen(long now,
+            ArrayMap<String, ProcessStateHolder> pkgList) {
+        mStateBeforeFrozen = mCurCombinedState % STATE_COUNT;
+        int currentMemFactor = mCurCombinedState / STATE_COUNT;
+        int combinedState = STATE_FROZEN + (currentMemFactor * STATE_COUNT);
+        setCombinedState(combinedState, now, pkgList);
+    }
+
+    /**
+     * Used to notify that this process was unfrozen.
+     */
+    public void onProcessUnfrozen(long now,
+            ArrayMap<String, ProcessStateHolder> pkgList) {
+        int currentMemFactor = mCurCombinedState / STATE_COUNT;
+        int combinedState = mStateBeforeFrozen + (currentMemFactor * STATE_COUNT);
+        setCombinedState(combinedState, now, pkgList);
+    }
+
+    /**
      * Update the current state of the given list of processes.
      *
      * @param state Current ActivityManager.PROCESS_STATE_*
@@ -434,13 +457,20 @@
             ArrayMap<String, ProcessStateHolder> pkgList) {
         if (state < 0) {
             state = mNumStartedServices > 0
-                    ? (STATE_SERVICE_RESTARTING+(memFactor*STATE_COUNT)) : STATE_NOTHING;
+                    ? (STATE_SERVICE_RESTARTING + (memFactor * STATE_COUNT)) : STATE_NOTHING;
         } else {
-            state = PROCESS_STATE_TO_STATE[state] + (memFactor*STATE_COUNT);
+            state = PROCESS_STATE_TO_STATE[state] + (memFactor * STATE_COUNT);
         }
+        setCombinedState(state, now, pkgList);
+    }
 
+    /**
+     * Sets combined state on the corresponding ProcessState objects.
+     */
+    void setCombinedState(int state, long now,
+            ArrayMap<String, ProcessStateHolder> pkgList) {
         // First update the common process.
-        mCommonProcess.setCombinedState(state, now);
+        mCommonProcess.setCombinedStateIdv(state, now);
 
         // If the common process is not multi-package, there is nothing else to do.
         if (!mCommonProcess.mMultiPackage) {
@@ -449,12 +479,15 @@
 
         if (pkgList != null) {
             for (int ip=pkgList.size()-1; ip>=0; ip--) {
-                pullFixedProc(pkgList, ip).setCombinedState(state, now);
+                pullFixedProc(pkgList, ip).setCombinedStateIdv(state, now);
             }
         }
     }
 
-    public void setCombinedState(int state, long now) {
+    /**
+     * Sets the combined state for this individual ProcessState object.
+     */
+    void setCombinedStateIdv(int state, long now) {
         ensureNotDead();
         if (!mDead && (mCurCombinedState != state)) {
             //Slog.i(TAG, "Setting state in " + mName + "/" + mPackage + ": " + state);
@@ -545,7 +578,7 @@
         }
         mNumStartedServices++;
         if (mNumStartedServices == 1 && mCurCombinedState == STATE_NOTHING) {
-            setCombinedState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now);
+            setCombinedStateIdv(STATE_SERVICE_RESTARTING + (memFactor * STATE_COUNT), now);
         }
     }
 
@@ -561,7 +594,7 @@
         }
         mNumStartedServices--;
         if (mNumStartedServices == 0 && (mCurCombinedState %STATE_COUNT) == STATE_SERVICE_RESTARTING) {
-            setCombinedState(STATE_NOTHING, now);
+            setCombinedStateIdv(STATE_NOTHING, now);
         } else if (mNumStartedServices < 0) {
             Slog.wtfStack(TAG, "Proc started services underrun: pkg="
                     + mPackage + " uid=" + mUid + " name=" + mName);
@@ -1588,7 +1621,9 @@
                 case STATE_CACHED:
                     cachedMs += duration;
                     break;
-                    // TODO (b/261910877) Add support for tracking frozenMs.
+                case STATE_FROZEN:
+                    frozenMs += duration;
+                    break;
             }
         }
         statsEventOutput.write(
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 21369f9..d6c5593 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -352,7 +352,7 @@
 
     const char* dirStr = env->GetStringUTFChars(internalDataDir, NULL);
     code->internalDataPathObj = dirStr;
-    code->internalDataPath = code->internalDataPathObj.string();
+    code->internalDataPath = code->internalDataPathObj.c_str();
     env->ReleaseStringUTFChars(internalDataDir, dirStr);
 
     if (externalDataDir != NULL) {
@@ -360,7 +360,7 @@
         code->externalDataPathObj = dirStr;
         env->ReleaseStringUTFChars(externalDataDir, dirStr);
     }
-    code->externalDataPath = code->externalDataPathObj.string();
+    code->externalDataPath = code->externalDataPathObj.c_str();
 
     code->sdkVersion = sdkVersion;
 
@@ -372,7 +372,7 @@
         code->obbPathObj = dirStr;
         env->ReleaseStringUTFChars(obbDir, dirStr);
     }
-    code->obbPath = code->obbPathObj.string();
+    code->obbPath = code->obbPathObj.c_str();
 
     jbyte* rawSavedState = NULL;
     jsize rawSavedSize = 0;
diff --git a/core/jni/android_app_backup_FullBackup.cpp b/core/jni/android_app_backup_FullBackup.cpp
index 339a7d3..5e096d7 100644
--- a/core/jni/android_app_backup_FullBackup.cpp
+++ b/core/jni/android_app_backup_FullBackup.cpp
@@ -106,15 +106,14 @@
             : NULL;
 
     if (path.length() < rootpath.length()) {
-        ALOGE("file path [%s] shorter than root path [%s]",
-                path.string(), rootpath.string());
+        ALOGE("file path [%s] shorter than root path [%s]", path.c_str(), rootpath.c_str());
         return (jint) -1;
     }
 
     off64_t tarSize = 0;
     jint err = write_tarfile(packageName, domain, rootpath, path, &tarSize, writer);
     if (!err) {
-        ALOGI("measured [%s] at %lld", path.string(), (long long)tarSize);
+        ALOGI("measured [%s] at %lld", path.c_str(), (long long)tarSize);
         env->CallVoidMethod(dataOutputObj, sFullBackupDataOutput.addSize, (jlong) tarSize);
     }
 
diff --git a/core/jni/android_backup_BackupDataInput.cpp b/core/jni/android_backup_BackupDataInput.cpp
index 79fa2a2..fc081a7 100644
--- a/core/jni/android_backup_BackupDataInput.cpp
+++ b/core/jni/android_backup_BackupDataInput.cpp
@@ -76,7 +76,7 @@
             return err < 0 ? err : -1;
         }
         // TODO: Set the fields in the entity object
-        jstring keyStr = env->NewStringUTF(key.string());
+        jstring keyStr = env->NewStringUTF(key.c_str());
         env->SetObjectField(entity, s_keyField, keyStr);
         env->SetIntField(entity, s_dataSizeField, dataSize);
         return 0;
diff --git a/core/jni/android_backup_BackupHelperDispatcher.cpp b/core/jni/android_backup_BackupHelperDispatcher.cpp
index efe7d0b..efce8e1 100644
--- a/core/jni/android_backup_BackupHelperDispatcher.cpp
+++ b/core/jni/android_backup_BackupHelperDispatcher.cpp
@@ -118,7 +118,7 @@
     }
 
     env->SetIntField(headerObj, s_chunkSizeField, flattenedHeader.dataSize);
-    env->SetObjectField(headerObj, s_keyPrefixField, env->NewStringUTF(keyPrefix.string()));
+    env->SetObjectField(headerObj, s_keyPrefixField, env->NewStringUTF(keyPrefix.c_str()));
 
     return (jint) 0;
 }
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index 2435406..60da2c2 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -58,13 +58,13 @@
     msg.appendFormat("Couldn't read row %d, col %d from CursorWindow.  "
             "Make sure the Cursor is initialized correctly before accessing data from it.",
             row, column);
-    jniThrowException(env, "java/lang/IllegalStateException", msg.string());
+    jniThrowException(env, "java/lang/IllegalStateException", msg.c_str());
 }
 
 static void throwUnknownTypeException(JNIEnv * env, jint type) {
     String8 msg;
     msg.appendFormat("UNKNOWN type %d", type);
-    jniThrowException(env, "java/lang/IllegalStateException", msg.string());
+    jniThrowException(env, "java/lang/IllegalStateException", msg.c_str());
 }
 
 static int getFdCount() {
@@ -107,7 +107,7 @@
 fail:
     jniThrowExceptionFmt(env, "android/database/CursorWindowAllocationException",
                          "Could not allocate CursorWindow '%s' of size %d due to error %d.",
-                         name.string(), cursorWindowSize, status);
+                         name.c_str(), cursorWindowSize, status);
     return 0;
 }
 
@@ -139,7 +139,7 @@
 
 static jstring nativeGetName(JNIEnv* env, jclass clazz, jlong windowPtr) {
     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
-    return env->NewStringUTF(window->name().string());
+    return env->NewStringUTF(window->name().c_str());
 }
 
 static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jlong windowPtr,
@@ -151,7 +151,7 @@
     if (status) {
         String8 msg;
         msg.appendFormat("Could not write CursorWindow to Parcel due to error %d.", status);
-        jniThrowRuntimeException(env, msg.string());
+        jniThrowRuntimeException(env, msg.c_str());
     }
 }
 
@@ -267,7 +267,7 @@
         // doesn't like UTF-8 strings with high codepoints.  It actually expects
         // Modified UTF-8 with encoded surrogate pairs.
         String16 utf16(value, sizeIncludingNull - 1);
-        return env->NewString(reinterpret_cast<const jchar*>(utf16.string()), utf16.size());
+        return env->NewString(reinterpret_cast<const jchar*>(utf16.c_str()), utf16.size());
     } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
         int64_t value = window->getFieldSlotValueLong(fieldSlot);
         char buf[32];
diff --git a/core/jni/android_database_SQLiteCommon.cpp b/core/jni/android_database_SQLiteCommon.cpp
index daa2087..c6a7511 100644
--- a/core/jni/android_database_SQLiteCommon.cpp
+++ b/core/jni/android_database_SQLiteCommon.cpp
@@ -229,7 +229,7 @@
             fullMessage.append(": ");
             fullMessage.append(message);
         }
-        jniThrowException(env, exceptionClass, fullMessage.string());
+        jniThrowException(env, exceptionClass, fullMessage.c_str());
     } else {
         jniThrowException(env, exceptionClass, message);
     }
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index 29520c2..893cc98 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -91,15 +91,14 @@
 // Called each time a statement begins execution, when tracing is enabled.
 static void sqliteTraceCallback(void *data, const char *sql) {
     SQLiteConnection* connection = static_cast<SQLiteConnection*>(data);
-    ALOG(LOG_VERBOSE, SQLITE_TRACE_TAG, "%s: \"%s\"\n",
-            connection->label.string(), sql);
+    ALOG(LOG_VERBOSE, SQLITE_TRACE_TAG, "%s: \"%s\"\n", connection->label.c_str(), sql);
 }
 
 // Called each time a statement finishes execution, when profiling is enabled.
 static void sqliteProfileCallback(void *data, const char *sql, sqlite3_uint64 tm) {
     SQLiteConnection* connection = static_cast<SQLiteConnection*>(data);
-    ALOG(LOG_VERBOSE, SQLITE_PROFILE_TAG, "%s: \"%s\" took %0.3f ms\n",
-            connection->label.string(), sql, tm * 0.000001f);
+    ALOG(LOG_VERBOSE, SQLITE_PROFILE_TAG, "%s: \"%s\" took %0.3f ms\n", connection->label.c_str(),
+         sql, tm * 0.000001f);
 }
 
 // Called after each SQLite VM instruction when cancelation is enabled.
@@ -130,7 +129,7 @@
     env->ReleaseStringUTFChars(labelStr, labelChars);
 
     sqlite3* db;
-    int err = sqlite3_open_v2(path.string(), &db, sqliteFlags, NULL);
+    int err = sqlite3_open_v2(path.c_str(), &db, sqliteFlags, NULL);
     if (err != SQLITE_OK) {
         throw_sqlite3_exception_errcode(env, err, "Could not open database");
         return 0;
@@ -180,7 +179,7 @@
         sqlite3_profile(db, &sqliteProfileCallback, connection);
     }
 
-    ALOGV("Opened connection %p with label '%s'", db, label.string());
+    ALOGV("Opened connection %p with label '%s'", db, label.c_str());
     return reinterpret_cast<jlong>(connection);
 }
 
@@ -760,7 +759,7 @@
     if (status) {
         String8 msg;
         msg.appendFormat("Failed to clear the cursor window, status=%d", status);
-        throw_sqlite3_exception(env, connection->db, msg.string());
+        throw_sqlite3_exception(env, connection->db, msg.c_str());
         return 0;
     }
 
@@ -770,7 +769,7 @@
         String8 msg;
         msg.appendFormat("Failed to set the cursor window column count to %d, status=%d",
                 numColumns, status);
-        throw_sqlite3_exception(env, connection->db, msg.string());
+        throw_sqlite3_exception(env, connection->db, msg.c_str());
         return 0;
     }
 
@@ -845,7 +844,7 @@
         String8 msg;
         msg.appendFormat("Row too big to fit into CursorWindow requiredPos=%d, totalRows=%d",
                 requiredPos, totalRows);
-        throw_sqlite3_exception(env, SQLITE_TOOBIG, NULL, msg.string());
+        throw_sqlite3_exception(env, SQLITE_TOOBIG, NULL, msg.c_str());
         return 0;
     }
 
diff --git a/core/jni/android_ddm_DdmHandleNativeHeap.cpp b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
index 2ca4500..d36d29c 100644
--- a/core/jni/android_ddm_DdmHandleNativeHeap.cpp
+++ b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
@@ -100,8 +100,8 @@
     if (array != NULL) {
         env->SetByteArrayRegion(array, 0,
                                 sizeof(header), reinterpret_cast<jbyte*>(&header));
-        env->SetByteArrayRegion(array, sizeof(header),
-                                maps.size(), reinterpret_cast<const jbyte*>(maps.string()));
+        env->SetByteArrayRegion(array, sizeof(header), maps.size(),
+                                reinterpret_cast<const jbyte*>(maps.c_str()));
         env->SetByteArrayRegion(array, sizeof(header) + maps.size(),
                                 header.allocSize, reinterpret_cast<jbyte*>(leak_info.buffer));
     }
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index eb49f41..5f3a1b5 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -878,7 +878,7 @@
         jniThrowRuntimeException(env, "getParameters failed (empty parameters)");
         return 0;
     }
-    return env->NewStringUTF(params8.string());
+    return env->NewStringUTF(params8.c_str());
 }
 
 static void android_hardware_Camera_reconnect(JNIEnv *env, jobject thiz)
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 3e4c7c7..041fed7 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -514,7 +514,7 @@
         ssize_t res;
         while ((res = TEMP_FAILURE_RETRY(read(readFd, &out[0], /*count*/1))) > 0) {
             if (out[0] == '\n') {
-                ALOGD("%s", logLine.string());
+                ALOGD("%s", logLine.c_str());
                 logLine.clear();
             } else {
                 logLine.append(out);
@@ -527,7 +527,7 @@
                     errno, strerror(errno));
             //return;
         } else if (!logLine.empty()) {
-            ALOGD("%s", logLine.string());
+            ALOGD("%s", logLine.c_str());
         }
 
         close(readFd);
@@ -956,8 +956,8 @@
         return OK;
     } else if (!res.isOk()) {
         VendorTagDescriptor::clearGlobalVendorTagDescriptor();
-        ALOGE("%s: Failed to setup vendor tag descriptors: %s",
-                __FUNCTION__, res.toString8().string());
+        ALOGE("%s: Failed to setup vendor tag descriptors: %s", __FUNCTION__,
+              res.toString8().c_str());
         return res.serviceSpecificErrorCode();
     }
     if (0 < desc->getTagCount()) {
@@ -971,8 +971,8 @@
             return OK;
         } else if (!res.isOk()) {
             VendorTagDescriptorCache::clearGlobalVendorTagCache();
-            ALOGE("%s: Failed to setup vendor tag cache: %s",
-                    __FUNCTION__, res.toString8().string());
+            ALOGE("%s: Failed to setup vendor tag cache: %s", __FUNCTION__,
+                  res.toString8().c_str());
             return res.serviceSpecificErrorCode();
         }
 
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index c947fba..30e546c 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -1543,7 +1543,8 @@
         String8 captureTime = nativeContext->getCaptureTime();
 
         if (writer->addEntry(TAG_DATETIME, NativeContext::DATETIME_COUNT,
-                reinterpret_cast<const uint8_t*>(captureTime.string()), TIFF_IFD_0) != OK) {
+                             reinterpret_cast<const uint8_t*>(captureTime.c_str()),
+                             TIFF_IFD_0) != OK) {
             jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
                     "Invalid metadata for tag %x", TAG_DATETIME);
             return nullptr;
@@ -1551,7 +1552,8 @@
 
         // datetime original
         if (writer->addEntry(TAG_DATETIMEORIGINAL, NativeContext::DATETIME_COUNT,
-                reinterpret_cast<const uint8_t*>(captureTime.string()), TIFF_IFD_0) != OK) {
+                             reinterpret_cast<const uint8_t*>(captureTime.c_str()),
+                             TIFF_IFD_0) != OK) {
             jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
                     "Invalid metadata for tag %x", TAG_DATETIMEORIGINAL);
             return nullptr;
@@ -1879,8 +1881,10 @@
         cameraModel += brand.c_str();
 
         BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1,
-                reinterpret_cast<const uint8_t*>(cameraModel.string()), TIFF_IFD_0), env,
-                TAG_UNIQUECAMERAMODEL, writer);
+                                                     reinterpret_cast<const uint8_t*>(
+                                                             cameraModel.c_str()),
+                                                     TIFF_IFD_0),
+                                    env, TAG_UNIQUECAMERAMODEL, writer);
     }
 
     {
@@ -2165,7 +2169,8 @@
         String8 description = nativeContext->getDescription();
         size_t len = description.bytes() + 1;
         if (writer->addEntry(TAG_IMAGEDESCRIPTION, len,
-                reinterpret_cast<const uint8_t*>(description.string()), TIFF_IFD_0) != OK) {
+                             reinterpret_cast<const uint8_t*>(description.c_str()),
+                             TIFF_IFD_0) != OK) {
             jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
                     "Invalid metadata for tag %x", TAG_IMAGEDESCRIPTION);
         }
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index 4c4443f..0e3c510 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -285,7 +285,7 @@
         hardware::Parcel *parcel =
             JHwParcel::GetNativeContext(env, thiz)->getParcel();
 
-        status_t err = parcel->writeInterfaceToken(nameCopy.string());
+        status_t err = parcel->writeInterfaceToken(nameCopy.c_str());
         signalExceptionForError(env, err);
     }
 }
@@ -687,9 +687,7 @@
 static jstring MakeStringObjFromHidlString(JNIEnv *env, const hidl_string &s) {
     String16 utf16String(s.c_str(), s.size());
 
-    return env->NewString(
-            reinterpret_cast<const jchar *>(utf16String.string()),
-            utf16String.size());
+    return env->NewString(reinterpret_cast<const jchar *>(utf16String.c_str()), utf16String.size());
 }
 
 static jstring JHwParcel_native_readString(JNIEnv *env, jobject thiz) {
diff --git a/core/jni/android_os_UEventObserver.cpp b/core/jni/android_os_UEventObserver.cpp
index eda5075..43a8be1 100644
--- a/core/jni/android_os_UEventObserver.cpp
+++ b/core/jni/android_os_UEventObserver.cpp
@@ -51,8 +51,8 @@
         const char* field = buffer;
         const char* end = buffer + length + 1;
         do {
-            if (strstr(field, match.string())) {
-                ALOGV("Matched uevent message with pattern: %s", match.string());
+            if (strstr(field, match.c_str())) {
+                ALOGV("Matched uevent message with pattern: %s", match.c_str());
                 return true;
             }
             field += strlen(field) + 1;
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 9dce5e3..6ed0a8a 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -953,8 +953,10 @@
             String8 msg;
             msg.appendFormat("Unknown binder error code. 0x%" PRIx32, err);
             // RemoteException is a checked exception, only throw from certain methods.
-            jniThrowException(env, canThrowRemoteException
-                    ? "android/os/RemoteException" : "java/lang/RuntimeException", msg.string());
+            jniThrowException(env,
+                              canThrowRemoteException ? "android/os/RemoteException"
+                                                      : "java/lang/RuntimeException",
+                              msg.c_str());
             break;
     }
 }
@@ -1286,8 +1288,7 @@
     IBinder* target = getBPNativeData(env, obj)->mObject.get();
     if (target != NULL) {
         const String16& desc = target->getInterfaceDescriptor();
-        return env->NewString(reinterpret_cast<const jchar*>(desc.string()),
-                              desc.size());
+        return env->NewString(reinterpret_cast<const jchar*>(desc.c_str()), desc.size());
     }
     jniThrowException(env, "java/lang/RuntimeException",
             "No binder found for object");
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 4f2bf4a..18c60a7 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -148,7 +148,7 @@
 
     const size_t N = name8.size();
     if (N > 0) {
-        const char* str = name8.string();
+        const char* str = name8.c_str();
         for (size_t i=0; i<N; i++) {
             if (str[i] < '0' || str[i] > '9') {
                 struct passwd* pwd = getpwnam(str);
@@ -180,7 +180,7 @@
 
     const size_t N = name8.size();
     if (N > 0) {
-        const char* str = name8.string();
+        const char* str = name8.c_str();
         for (size_t i=0; i<N; i++) {
             if (str[i] < '0' || str[i] > '9') {
                 struct group* grp = getgrnam(str);
@@ -583,8 +583,8 @@
         env->ReleaseStringCritical(name, str);
     }
 
-    if (!name8.isEmpty()) {
-        AndroidRuntime::getRuntime()->setArgv0(name8.string(), true /* setProcName */);
+    if (!name8.empty()) {
+        AndroidRuntime::getRuntime()->setArgv0(name8.c_str(), true /* setProcName */);
     }
 }
 
@@ -690,7 +690,7 @@
         return;
     }
 
-    int fd = open(file.string(), O_RDONLY | O_CLOEXEC);
+    int fd = open(file.c_str(), O_RDONLY | O_CLOEXEC);
 
     if (fd >= 0) {
         //ALOGI("Clearing %" PRId32 " sizes", count);
@@ -704,7 +704,7 @@
         close(fd);
 
         if (len < 0) {
-            ALOGW("Unable to read %s", file.string());
+            ALOGW("Unable to read %s", file.c_str());
             len = 0;
         }
         buffer[len] = 0;
@@ -717,7 +717,7 @@
             //ALOGI("Parsing at: %s", p);
             for (i=0; i<count; i++) {
                 const String8& field = fields[i];
-                if (strncmp(p, field.string(), field.length()) == 0) {
+                if (strncmp(p, field.c_str(), field.length()) == 0) {
                     p += field.length();
                     while (*p == ' ' || *p == '\t') p++;
                     char* num = p;
@@ -729,7 +729,7 @@
                     }
                     char* end;
                     sizesArray[i] = strtoll(num, &end, 10);
-                    //ALOGI("Field %s = %" PRId64, field.string(), sizesArray[i]);
+                    // ALOGI("Field %s = %" PRId64, field.c_str(), sizesArray[i]);
                     foundCount++;
                     break;
                 }
@@ -746,7 +746,7 @@
 
         free(buffer);
     } else {
-        ALOGW("Unable to open %s", file.string());
+        ALOGW("Unable to open %s", file.c_str());
     }
 
     //ALOGI("Done!");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dd1a499..d898a23 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2094,6 +2094,16 @@
     <permission android:name="android.permission.MANAGE_TEST_NETWORKS"
         android:protectionLevel="signature" />
 
+    <!-- Allows direct access to the <RemoteAuth>Service interfaces.
+         @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+    <permission android:name="android.permission.MANAGE_REMOTE_AUTH"
+                android:protectionLevel="signature" />
+
+    <!-- Allows direct access to the <RemoteAuth>Service authentication methods.
+         @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+    <permission android:name="android.permission.USE_REMOTE_AUTH"
+                android:protectionLevel="signature" />
+
     <!-- @SystemApi @hide Allows applications to read Wi-Fi credential.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.READ_WIFI_CREDENTIAL"
diff --git a/core/res/OWNERS b/core/res/OWNERS
index b46902e..0df7c20 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -52,3 +52,6 @@
 # Telephony
 per-file res/values/config_telephony.xml = file:/platform/frameworks/opt/telephony:/OWNERS
 per-file res/xml/sms_short_codes.xml = file:/platform/frameworks/opt/telephony:/OWNERS
+
+# TV Input Framework
+per-file res/values/config_tv_external_input_logging.xml = file:/services/core/java/com/android/server/tv/OWNERS
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index e67ea82..a6830a6 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -552,10 +552,6 @@
     <color name="accessibility_magnification_thumbnail_container_background_color">#99000000</color>
     <color name="accessibility_magnification_thumbnail_container_stroke_color">#FFFFFF</color>
 
-    <!-- Color of camera light when camera is in use -->
-    <color name="camera_privacy_light_day">#FFFFFF</color>
-    <color name="camera_privacy_light_night">#FFFFFF</color>
-
     <!-- Lily Language Picker language item view colors -->
     <color name="language_picker_item_text_color">#202124</color>
     <color name="language_picker_item_text_color_secondary">#5F6368</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0ea5b8a..a72f779 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3487,7 +3487,8 @@
     <!-- Whether this device prefers to show snapshot or splash screen on back predict target.
          When set true, there will create windowless starting surface for the preview target, so it
          won't affect activity's lifecycle. This should only be disabled on low-ram device. -->
-    <bool name="config_predictShowStartingSurface">true</bool>
+    <!-- TODO(b/268563842) enable once activity snapshot is ready -->
+    <bool name="config_predictShowStartingSurface">false</bool>
 
     <!-- default window ShowCircularMask property -->
     <bool name="config_windowShowCircularMask">false</bool>
@@ -6534,8 +6535,19 @@
 
     <!-- Interval in milliseconds to average light sensor values for camera light brightness -->
     <integer name="config_cameraPrivacyLightAlsAveragingIntervalMillis">3000</integer>
-    <!-- Light sensor's lux value to use as the threshold between using day or night brightness -->
-    <integer name="config_cameraPrivacyLightAlsNightThreshold">4</integer>
+    <!-- Ambient Light sensor's lux values to use as the threshold between brightness colors defined
+         by config_cameraPrivacyLightColors. If the ambient brightness less than the first element
+         in this array then lights of type "camera" will be set to the color in position 0 of
+         config_cameraPrivacyLightColors. This array must be strictly increasing and have a length
+         of zero means there is only one brightness -->
+    <integer-array name="config_cameraPrivacyLightAlsLuxThresholds">
+    </integer-array>
+    <!-- Colors to configure the camera privacy light at different brightnesses. This array must
+         have exactly one more entry than config_cameraPrivacyLightAlsLuxThresholds,
+         or a length of zero if the feature isn't supported. If nonempty and the device doesn't have
+         an ambient light sensor the last element in this array will be the only one used -->
+    <array name="config_cameraPrivacyLightColors">
+    </array>
 
     <!-- List of system components which are allowed to receive ServiceState entries in an
          un-sanitized form, even if the location toggle is off. This is intended ONLY for system
diff --git a/core/res/res/values/config_tv_external_input_logging.xml b/core/res/res/values/config_tv_external_input_logging.xml
new file mode 100644
index 0000000..72e30be
--- /dev/null
+++ b/core/res/res/values/config_tv_external_input_logging.xml
@@ -0,0 +1,50 @@
+<?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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds.  Do not translate.
+
+     NOTE: The naming convention is "config_camelCaseValue". Some legacy
+     entries do not follow the convention, but all new entries should. -->
+
+<resources>
+    <bool name="config_tvExternalInputLoggingDisplayNameFilterEnabled">false</bool>
+
+    <string-array name="config_tvExternalInputLoggingDeviceOnScreenDisplayNames">
+        <item>Chromecast</item>
+        <item>Chromecast HD</item>
+        <item>SHIELD</item>
+        <item>Roku</item>
+        <item>Roku Express 4</item>
+        <item>Home Theater</item>
+        <item>Fire TV Stick</item>
+        <item>PlayStation 5</item>
+        <item>NintendoSwitch</item>
+    </string-array>
+
+    <string-array name="config_tvExternalInputLoggingDeviceBrandNames">
+        <item>Chromecast</item>
+        <item>SHIELD</item>
+        <item>Roku</item>
+        <item>Apple</item>
+        <item>Fire TV</item>
+        <item>PlayStation</item>
+        <item>Nintendo</item>
+    </string-array>
+</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 889901a..ee0563b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5009,10 +5009,9 @@
   <java-symbol type="string" name="vdm_camera_access_denied" />
   <java-symbol type="string" name="vdm_secure_window" />
 
-  <java-symbol type="color" name="camera_privacy_light_day"/>
-  <java-symbol type="color" name="camera_privacy_light_night"/>
   <java-symbol type="integer" name="config_cameraPrivacyLightAlsAveragingIntervalMillis"/>
-  <java-symbol type="integer" name="config_cameraPrivacyLightAlsNightThreshold"/>
+  <java-symbol type="array" name="config_cameraPrivacyLightAlsLuxThresholds"/>
+  <java-symbol type="array" name="config_cameraPrivacyLightColors"/>
 
   <java-symbol type="bool" name="config_bg_current_drain_monitor_enabled" />
   <java-symbol type="array" name="config_bg_current_drain_threshold_to_restricted_bucket" />
@@ -5221,4 +5220,9 @@
 
   <!-- Whether we order unlocking and waking -->
   <java-symbol type="bool" name="config_orderUnlockAndWake" />
+
+  <!-- External TV Input Logging Configs -->
+  <java-symbol type="bool" name="config_tvExternalInputLoggingDisplayNameFilterEnabled" />
+  <java-symbol type="array" name="config_tvExternalInputLoggingDeviceOnScreenDisplayNames" />
+  <java-symbol type="array" name="config_tvExternalInputLoggingDeviceBrandNames" />
 </resources>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index a358c4f..20d8d91 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -86,8 +86,8 @@
     <uses-permission android:name="android.permission.TEST_GRANTED" />
     <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
     <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
-    <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
-    <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />
+    <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" android:maxSdkVersion="34"/>
+    <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" android:maxSdkVersion="34"/>
 
     <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
diff --git a/core/tests/coretests/res/layout/viewgroup_test.xml b/core/tests/coretests/res/layout/viewgroup_test.xml
index 04f4f52..9b57047 100644
--- a/core/tests/coretests/res/layout/viewgroup_test.xml
+++ b/core/tests/coretests/res/layout/viewgroup_test.xml
@@ -42,8 +42,8 @@
         android:id="@+id/view_translate"
         android:layout_width="20dp"
         android:layout_height="10dp"
-        android:translationX="10dp"
-        android:translationY="20dp"
+        android:translationX="10px"
+        android:translationY="20px"
         android:text="Hello World!"
         android:background="#2F00FF00" />
     <FrameLayout
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index a923479..a41fb64 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -630,7 +630,7 @@
         });
     }
 
-    @FlakyTest(bugId = 295234586)
+    @FlakyTest(bugId = 298331121)
     @Test
     public void testHandleConfigurationChanged_DoesntOverrideActivityConfig() {
         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
@@ -836,8 +836,10 @@
     }
 
     private static ClientTransaction newRelaunchResumeTransaction(Activity activity) {
+        final Configuration currentConfig = activity.getResources().getConfiguration();
         final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null,
-                null, 0, new MergedConfiguration(), false /* preserveWindow */);
+                null, 0, new MergedConfiguration(currentConfig, currentConfig),
+                false /* preserveWindow */);
         final ResumeActivityItem resumeStateRequest =
                 ResumeActivityItem.obtain(true /* isForward */,
                         false /* shouldSendCompatFakeFocus*/);
diff --git a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
index 6189914..fe30d81 100644
--- a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.app.procstats;
 
-import static com.android.internal.app.procstats.ProcessStats.STATE_TOP;
-
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
@@ -108,7 +106,8 @@
         ProcessState processState =
                 processStats.getProcessStateLocked(
                         APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
-        processState.setCombinedState(STATE_TOP, NOW_MS);
+        processState.setState(ActivityManager.PROCESS_STATE_TOP, ProcessStats.ADJ_MEM_FACTOR_NORMAL,
+                NOW_MS, /* pkgList */ null);
         processState.commitStateTime(NOW_MS + TimeUnit.SECONDS.toMillis(DURATION_SECS));
         processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
         verify(mStatsEventOutput)
@@ -158,6 +157,38 @@
     }
 
     @SmallTest
+    public void testDumpFrozenDuration() throws Exception {
+        ProcessStats processStats = new ProcessStats();
+        ProcessState processState =
+                processStats.getProcessStateLocked(
+                        APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+        processState.setState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+                ProcessStats.ADJ_MEM_FACTOR_NORMAL, NOW_MS, /* pkgList */ null);
+        processState.onProcessFrozen(NOW_MS   + 1 * TimeUnit.SECONDS.toMillis(DURATION_SECS),
+            /* pkgList */ null);
+        processState.onProcessUnfrozen(NOW_MS + 2 * TimeUnit.SECONDS.toMillis(DURATION_SECS),
+            /* pkgList */ null);
+        processState.commitStateTime(NOW_MS   + 3 * TimeUnit.SECONDS.toMillis(DURATION_SECS));
+        processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+        verify(mStatsEventOutput)
+                .write(
+                        eq(FrameworkStatsLog.PROCESS_STATE),
+                        eq(APP_1_UID),
+                        eq(APP_1_PROCESS_NAME),
+                        anyInt(),
+                        anyInt(),
+                        eq(0),
+                        eq(0),
+                        eq(0),
+                        eq(0),
+                        eq(2 * DURATION_SECS),  // bound_fgs
+                        eq(0),
+                        eq(0),
+                        eq(DURATION_SECS),  // frozen
+                        eq(0));
+    }
+
+    @SmallTest
     public void testDumpProcessAssociation() throws Exception {
         ProcessStats processStats = new ProcessStats();
         AssociationState associationState =
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
index 246a1e7..a0e9947 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
@@ -48,7 +48,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 @Presubmit
 @RunWith(AndroidJUnit4.class)
@@ -102,6 +104,25 @@
         return partitionOrder.toString();
     }
 
+    // configIndex should come from real time partition order cause partitions could get
+    // reordered by /product/overlay/partition_order.xml
+    private Map<String, Integer> createConfigIndexes(OverlayConfig overlayConfig,
+            String... configPartitions) {
+        Map<String, Integer> configIndexes = new HashMap<>();
+        for (int i = 0; i < configPartitions.length; i++) {
+            configIndexes.put(configPartitions[i], -1);
+        }
+
+        String[] partitions = overlayConfig.getPartitionOrder().split(", ");
+        int index = 0;
+        for (int i = 0; i < partitions.length; i++) {
+            if (configIndexes.containsKey(partitions[i])) {
+                configIndexes.put(partitions[i], index++);
+            }
+        }
+        return configIndexes;
+    }
+
     @Test
     public void testImmutableAfterNonImmutableFails() throws IOException {
         mExpectedException.expect(IllegalStateException.class);
@@ -292,11 +313,13 @@
         mScannerRule.addOverlay(createFile("/system_ext/overlay/five.apk"), "five");
 
         final OverlayConfig overlayConfig = createConfigImpl();
-        assertConfig(overlayConfig, "one", true, true, 0);
-        assertConfig(overlayConfig, "two", true, true, 1);
-        assertConfig(overlayConfig, "three", true, true, 2);
-        assertConfig(overlayConfig, "four", true, true, 3);
-        assertConfig(overlayConfig, "five", true, true, 4);
+        Map<String, Integer> configIndexes = createConfigIndexes(overlayConfig,
+                "vendor", "odm", "oem", "product", "system_ext");
+        assertConfig(overlayConfig, "one", true, true, configIndexes.get("vendor"));
+        assertConfig(overlayConfig, "two", true, true, configIndexes.get("odm"));
+        assertConfig(overlayConfig, "three", true, true, configIndexes.get("oem"));
+        assertConfig(overlayConfig, "four", true, true, configIndexes.get("product"));
+        assertConfig(overlayConfig, "five", true, true, configIndexes.get("system_ext"));
     }
 
     @Test
@@ -313,9 +336,11 @@
                 true, 0);
 
         final OverlayConfig overlayConfig = createConfigImpl();
-        assertConfig(overlayConfig, "one", false, true, 0);
-        assertConfig(overlayConfig, "two", true, true, 1);
-        assertConfig(overlayConfig, "three", false, true, 2);
+        Map<String, Integer> configIndexes = createConfigIndexes(overlayConfig,
+                "vendor", "odm", "product");
+        assertConfig(overlayConfig, "one", false, true, configIndexes.get("vendor"));
+        assertConfig(overlayConfig, "two", true, true, configIndexes.get("odm"));
+        assertConfig(overlayConfig, "three", false, true, configIndexes.get("product"));
     }
 
     @Test
@@ -327,9 +352,11 @@
                 true, 0);
 
         final OverlayConfig overlayConfig = createConfigImpl();
-        assertConfig(overlayConfig, "one", false, true, 0);
-        assertConfig(overlayConfig, "two", false, true, 1);
-        assertConfig(overlayConfig, "three", false, true, 2);
+        Map<String, Integer> configIndexes = createConfigIndexes(overlayConfig,
+                "vendor", "odm", "product");
+        assertConfig(overlayConfig, "one", false, true, configIndexes.get("vendor"));
+        assertConfig(overlayConfig, "two", false, true, configIndexes.get("odm"));
+        assertConfig(overlayConfig, "three", false, true, configIndexes.get("product"));
     }
 
     @Test
diff --git a/data/keyboards/Vendor_18d1_Product_9451.idc b/data/keyboards/Vendor_18d1_Product_9451.idc
index e13fcb2..07cfc79 100644
--- a/data/keyboards/Vendor_18d1_Product_9451.idc
+++ b/data/keyboards/Vendor_18d1_Product_9451.idc
@@ -13,11 +13,10 @@
 # limitations under the License.
 
 #
-# Input Device Configuration file for a flavor of the Google Reference RCU Remote.
+# Input Device Configuration file for a flavor of Google Remote Control.
 #
 #
 
 # Basic Parameters
-keyboard.layout = Vendor_0957_Product_0001
 keyboard.doNotWakeByDefault = 1
-audio.mic = 1
\ No newline at end of file
+audio.mic = 1
diff --git a/data/keyboards/Vendor_18d1_Product_9451.kl b/data/keyboards/Vendor_18d1_Product_9451.kl
new file mode 100644
index 0000000..fb425be
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_9451.kl
@@ -0,0 +1,39 @@
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Key Layout file for flavor of Google Remote Control.
+#
+
+key 116   POWER         WAKE
+key 217   ASSIST        WAKE
+
+key 103   DPAD_UP
+key 108   DPAD_DOWN
+key 105   DPAD_LEFT
+key 106   DPAD_RIGHT
+key 353   DPAD_CENTER
+
+key 158   BACK
+key 172   HOME          WAKE
+
+key 113   VOLUME_MUTE
+key 114   VOLUME_DOWN
+key 115   VOLUME_UP
+
+# custom keys
+key usage 0x000c0186    MACRO_1      WAKE
+
+key usage 0x000c0077    BUTTON_3     WAKE #YouTube
+key usage 0x000c0078    BUTTON_4     WAKE #Netflix
diff --git a/drm/jni/android_drm_DrmManagerClient.cpp b/drm/jni/android_drm_DrmManagerClient.cpp
index e2bb6a6..14d5eaf 100644
--- a/drm/jni/android_drm_DrmManagerClient.cpp
+++ b/drm/jni/android_drm_DrmManagerClient.cpp
@@ -167,8 +167,8 @@
     jint uniqueId = event.getUniqueId();
     jint type = event.getType();
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jstring message = env->NewStringUTF(event.getMessage().string());
-    ALOGV("JNIOnInfoListener::onInfo => %d | %d | %s", uniqueId, type, event.getMessage().string());
+    jstring message = env->NewStringUTF(event.getMessage().c_str());
+    ALOGV("JNIOnInfoListener::onInfo => %d | %d | %s", uniqueId, type, event.getMessage().c_str());
 
     env->CallStaticVoidMethod(
             mClass,
@@ -273,15 +273,15 @@
                 const char* value = pConstraints->getAsByteArray(&key);
                 if (NULL != value) {
                     ScopedLocalRef<jbyteArray> dataArray(env, env->NewByteArray(strlen(value)));
-                    ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.string()));
+                    ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.c_str()));
                     env->SetByteArrayRegion(dataArray.get(), 0, strlen(value), (jbyte*)value);
                     env->CallVoidMethod(constraints, ContentValues_putByteArray,
                                         keyString.get(), dataArray.get());
                 }
             } else {
                 String8 value = pConstraints->get(key);
-                ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.string()));
-                ScopedLocalRef<jstring> valueString(env, env->NewStringUTF(value.string()));
+                ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.c_str()));
+                ScopedLocalRef<jstring> valueString(env, env->NewStringUTF(value.c_str()));
                 env->CallVoidMethod(constraints, ContentValues_putString,
                                     keyString.get(), valueString.get());
             }
@@ -320,8 +320,8 @@
                     // insert the entry<constraintKey, constraintValue>
                     // to newly created java object
                     String8 value = pMetadata->get(key);
-                    ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.string()));
-                    ScopedLocalRef<jstring> valueString(env, env->NewStringUTF(value.string()));
+                    ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.c_str()));
+                    ScopedLocalRef<jstring> valueString(env, env->NewStringUTF(value.c_str()));
                     env->CallVoidMethod(metadata, ContentValues_putString,
                                         keyString.get(), valueString.get());
                 }
@@ -357,19 +357,19 @@
 
         env->CallVoidMethod(
             drmSupportInfo, env->GetMethodID(clazz, "setDescription", "(Ljava/lang/String;)V"),
-            env->NewStringUTF(info.getDescription().string()));
+            env->NewStringUTF(info.getDescription().c_str()));
 
         DrmSupportInfo::MimeTypeIterator iterator = info.getMimeTypeIterator();
         while (iterator.hasNext()) {
             String8  value = iterator.next();
-            env->CallVoidMethod(drmSupportInfo, addMimeTypeId, env->NewStringUTF(value.string()));
+            env->CallVoidMethod(drmSupportInfo, addMimeTypeId, env->NewStringUTF(value.c_str()));
         }
 
         DrmSupportInfo::FileSuffixIterator it = info.getFileSuffixIterator();
         while (it.hasNext()) {
             String8 value = it.next();
             env->CallVoidMethod(
-                drmSupportInfo, addFileSuffixId, env->NewStringUTF(value.string()));
+                drmSupportInfo, addFileSuffixId, env->NewStringUTF(value.c_str()));
         }
 
         env->SetObjectArrayElement(array, i, drmSupportInfo);
@@ -459,7 +459,7 @@
 
         String8 keyString = Utility::getStringValue(env, key.get());
         String8 valueString = Utility::getStringValue(env, valString.get());
-        ALOGV("Key: %s | Value: %s", keyString.string(), valueString.string());
+        ALOGV("Key: %s | Value: %s", keyString.c_str(), valueString.c_str());
 
         drmInfo.put(keyString, valueString);
     }
@@ -488,15 +488,15 @@
         jmethodID constructorId
             = env->GetMethodID(clazz, "<init>", "([BLjava/lang/String;Ljava/lang/String;)V");
         jobject processedData = env->NewObject(clazz, constructorId, dataArray,
-                    env->NewStringUTF((drmInfo.get(DrmInfoRequest::ACCOUNT_ID)).string()),
-                    env->NewStringUTF((drmInfo.get(DrmInfoRequest::SUBSCRIPTION_ID)).string()));
+                    env->NewStringUTF((drmInfo.get(DrmInfoRequest::ACCOUNT_ID)).c_str()),
+                    env->NewStringUTF((drmInfo.get(DrmInfoRequest::SUBSCRIPTION_ID)).c_str()));
 
         constructorId
             = env->GetMethodID(localRef,
                 "<init>", "(IILandroid/drm/ProcessedData;Ljava/lang/String;)V");
 
         drmInfoStatus = env->NewObject(localRef, constructorId, statusCode, infoType,
-                processedData, env->NewStringUTF(pDrmInfoStatus->mimeType.string()));
+                processedData, env->NewStringUTF(pDrmInfoStatus->mimeType.c_str()));
     }
 
     delete[] mData; mData = NULL;
@@ -533,7 +533,7 @@
 
         String8 keyString = Utility::getStringValue(env, key.get());
         String8 valueString = Utility::getStringValue(env, value.get());
-        ALOGV("Key: %s | Value: %s", keyString.string(), valueString.string());
+        ALOGV("Key: %s | Value: %s", keyString.c_str(), valueString.c_str());
 
         drmInfoReq.put(keyString, valueString);
     }
@@ -554,7 +554,7 @@
             drmInfoObject
                 = env->NewObject(localRef,
                     env->GetMethodID(localRef, "<init>", "(I[BLjava/lang/String;)V"),
-                    mInfoType, dataArray, env->NewStringUTF(pDrmInfo->getMimeType().string()));
+                    mInfoType, dataArray, env->NewStringUTF(pDrmInfo->getMimeType().c_str()));
 
             DrmInfo::KeyIterator it = pDrmInfo->keyIterator();
             jmethodID putMethodId
@@ -563,8 +563,8 @@
             while (it.hasNext()) {
                 String8 key = it.next();
                 String8 value = pDrmInfo->get(key);
-                ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.string()));
-                ScopedLocalRef<jstring> valueString(env, env->NewStringUTF(value.string()));
+                ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.c_str()));
+                ScopedLocalRef<jstring> valueString(env, env->NewStringUTF(value.c_str()));
                 env->CallVoidMethod(drmInfoObject, putMethodId,
                     keyString.get(), valueString.get());
             }
@@ -602,7 +602,7 @@
             ->getOriginalMimeType(uniqueId,
                                   Utility::getStringValue(env, path), fd);
     ALOGV("getOriginalMimeType Exit");
-    return env->NewStringUTF(mimeType.string());
+    return env->NewStringUTF(mimeType.c_str());
 }
 
 static jint android_drm_DrmManagerClient_checkRightsStatus(
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 55eabb0..c3d8f9a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -16,12 +16,14 @@
 
 package androidx.window.extensions;
 
+import android.app.ActivityTaskManager;
 import android.app.ActivityThread;
 import android.app.Application;
 import android.content.Context;
 import android.window.TaskFragmentOrganizer;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
 import androidx.window.common.RawFoldingFeatureProducer;
 import androidx.window.extensions.area.WindowAreaComponent;
@@ -111,9 +113,13 @@
      * {@link WindowExtensions#getWindowLayoutComponent()}.
      * @return {@link ActivityEmbeddingComponent} OEM implementation.
      */
-    @NonNull
+    @Nullable
     public ActivityEmbeddingComponent getActivityEmbeddingComponent() {
         if (mSplitController == null) {
+            if (!ActivityTaskManager.supportsMultiWindow(getApplication())) {
+                // Disable AE for device that doesn't support multi window.
+                return null;
+            }
             synchronized (mLock) {
                 if (mSplitController == null) {
                     mSplitController = new SplitController(
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 896fe61..d894487 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -651,7 +651,8 @@
         if (minDimensionsPair == null) {
             return splitAttributes;
         }
-        final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+        final FoldingFeature foldingFeature = getFoldingFeatureForHingeType(
+                taskProperties, splitAttributes);
         final Configuration taskConfiguration = taskProperties.getConfiguration();
         final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes,
                 foldingFeature);
@@ -726,7 +727,8 @@
     Rect getRelBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties,
             @NonNull SplitAttributes splitAttributes) {
         final Configuration taskConfiguration = taskProperties.getConfiguration();
-        final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+        final FoldingFeature foldingFeature = getFoldingFeatureForHingeType(
+                taskProperties, splitAttributes);
         if (!shouldShowSplit(splitAttributes)) {
             return new Rect();
         }
@@ -933,6 +935,17 @@
     }
 
     @Nullable
+    private FoldingFeature getFoldingFeatureForHingeType(
+            @NonNull TaskProperties taskProperties,
+            @NonNull SplitAttributes splitAttributes) {
+        SplitType splitType = splitAttributes.getSplitType();
+        if (!(splitType instanceof HingeSplitType)) {
+            return null;
+        }
+        return getFoldingFeature(taskProperties);
+    }
+
+    @Nullable
     @VisibleForTesting
     FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) {
         final int displayId = taskProperties.getDisplayId();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index ccf9552..e03e1ec 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -319,13 +319,17 @@
             return features;
         }
 
+        // We will transform the feature bounds to the Activity window, so using the rotation
+        // from the same source (WindowConfiguration) to make sure they are synchronized.
+        final int rotation = windowConfiguration.getDisplayRotation();
+
         for (CommonFoldingFeature baseFeature : storedFeatures) {
             Integer state = convertToExtensionState(baseFeature.getState());
             if (state == null) {
                 continue;
             }
             Rect featureRect = baseFeature.getRect();
-            rotateRectToDisplayRotation(displayId, featureRect);
+            rotateRectToDisplayRotation(displayId, rotation, featureRect);
             transformToWindowSpaceRect(windowConfiguration, featureRect);
 
             if (isZero(featureRect)) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 5bfb0ebd..15a329bd 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -120,10 +120,12 @@
         }
 
         List<SidecarDisplayFeature> features = new ArrayList<>();
+        final int rotation = activity.getResources().getConfiguration().windowConfiguration
+                .getDisplayRotation();
         for (CommonFoldingFeature baseFeature : mStoredFeatures) {
             SidecarDisplayFeature feature = new SidecarDisplayFeature();
             Rect featureRect = baseFeature.getRect();
-            rotateRectToDisplayRotation(displayId, featureRect);
+            rotateRectToDisplayRotation(displayId, rotation, featureRect);
             transformToWindowSpaceRect(activity, featureRect);
             feature.setRect(featureRect);
             feature.setType(baseFeature.getType());
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
index 9e2611f..57f8308 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
@@ -16,8 +16,6 @@
 
 package androidx.window.util;
 
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
@@ -25,8 +23,8 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
+import android.util.RotationUtils;
 import android.view.DisplayInfo;
-import android.view.Surface;
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
@@ -45,10 +43,9 @@
      * Rotates the input rectangle specified in default display orientation to the current display
      * rotation.
      */
-    public static void rotateRectToDisplayRotation(int displayId, Rect inOutRect) {
+    public static void rotateRectToDisplayRotation(int displayId, int rotation, Rect inOutRect) {
         DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance();
         DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId);
-        int rotation = displayInfo.rotation;
 
         boolean isSideRotation = rotation == ROTATION_90 || rotation == ROTATION_270;
         int displayWidth = isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth;
@@ -56,35 +53,7 @@
 
         inOutRect.intersect(0, 0, displayWidth, displayHeight);
 
-        rotateBounds(inOutRect, displayWidth, displayHeight, rotation);
-    }
-
-    /**
-     * Rotates the input rectangle within parent bounds for a given delta.
-     */
-    private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight,
-            @Surface.Rotation int delta) {
-        int origLeft = inOutRect.left;
-        switch (delta) {
-            case ROTATION_0:
-                return;
-            case ROTATION_90:
-                inOutRect.left = inOutRect.top;
-                inOutRect.top = parentWidth - inOutRect.right;
-                inOutRect.right = inOutRect.bottom;
-                inOutRect.bottom = parentWidth - origLeft;
-                return;
-            case ROTATION_180:
-                inOutRect.left = parentWidth - inOutRect.right;
-                inOutRect.right = parentWidth - origLeft;
-                return;
-            case ROTATION_270:
-                inOutRect.left = parentHeight - inOutRect.bottom;
-                inOutRect.bottom = inOutRect.right;
-                inOutRect.right = parentHeight - inOutRect.top;
-                inOutRect.top = origLeft;
-                return;
-        }
+        RotationUtils.rotateBounds(inOutRect, displayWidth, displayHeight, rotation);
     }
 
     /** Transforms rectangle from absolute coordinate space to the window coordinate space. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index d189ae2..45564cb 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -16,8 +16,11 @@
 
 package androidx.window.extensions;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.ActivityTaskManager;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -52,7 +55,11 @@
 
     @Test
     public void testGetActivityEmbeddingComponent() {
-        assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
+        if (ActivityTaskManager.supportsMultiWindow(getInstrumentation().getContext())) {
+            assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
+        } else {
+            assertThat(mExtensions.getActivityEmbeddingComponent()).isNull();
+        }
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 8400dde..645a961 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.bubbles;
 
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
@@ -115,6 +114,7 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.taskview.TaskView;
 import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -143,6 +143,8 @@
     // Should match with PhoneWindowManager
     private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
     private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
+    private static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
+    private static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
 
     /**
      * Common interface to send updates to bubble views.
@@ -182,6 +184,7 @@
     private final ShellTaskOrganizer mTaskOrganizer;
     private final DisplayController mDisplayController;
     private final TaskViewTransitions mTaskViewTransitions;
+    private final Transitions mTransitions;
     private final SyncTransactionQueue mSyncQueue;
     private final ShellController mShellController;
     private final ShellCommandHandler mShellCommandHandler;
@@ -282,6 +285,7 @@
             @ShellMainThread Handler mainHandler,
             @ShellBackgroundThread ShellExecutor bgExecutor,
             TaskViewTransitions taskViewTransitions,
+            Transitions transitions,
             SyncTransactionQueue syncQueue,
             IWindowManager wmService,
             BubbleProperties bubbleProperties) {
@@ -317,6 +321,7 @@
                         com.android.internal.R.dimen.importance_ring_stroke_width));
         mDisplayController = displayController;
         mTaskViewTransitions = taskViewTransitions;
+        mTransitions = transitions;
         mOneHandedOptional = oneHandedOptional;
         mDragAndDropController = dragAndDropController;
         mSyncQueue = syncQueue;
@@ -416,23 +421,9 @@
             }
         }, mMainHandler);
 
-        mTaskStackListener.addListener(new TaskStackListenerCallback() {
-            @Override
-            public void onTaskMovedToFront(int taskId) {
-                mMainExecutor.execute(() -> {
-                    int expandedId = INVALID_TASK_ID;
-                    if (mStackView != null && mStackView.getExpandedBubble() != null
-                            && isStackExpanded()
-                            && !mStackView.isExpansionAnimating()
-                            && !mStackView.isSwitchAnimating()) {
-                        expandedId = mStackView.getExpandedBubble().getTaskId();
-                    }
-                    if (expandedId != INVALID_TASK_ID && expandedId != taskId) {
-                        mBubbleData.setExpanded(false);
-                    }
-                });
-            }
+        mTransitions.registerObserver(new BubblesTransitionObserver(this, mBubbleData));
 
+        mTaskStackListener.addListener(new TaskStackListenerCallback() {
             @Override
             public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
                     boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
@@ -883,8 +874,10 @@
 
             String action = intent.getAction();
             String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
-            if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
-                    && SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason))
+            boolean validReasonToCollapse = SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason)
+                    || SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason)
+                    || SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason);
+            if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) && validReasonToCollapse)
                     || Intent.ACTION_SCREEN_OFF.equals(action)) {
                 mMainExecutor.execute(() -> collapseStack());
             }
@@ -1961,6 +1954,15 @@
         }
     }
 
+    /**
+     * Returns whether the stack is animating or not.
+     */
+    public boolean isStackAnimating() {
+        return mStackView != null
+                && (mStackView.isExpansionAnimating()
+                || mStackView.isSwitchAnimating());
+    }
+
     @VisibleForTesting
     @Nullable
     public BubbleStackView getStackView() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
new file mode 100644
index 0000000..9e8a385
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
@@ -0,0 +1,83 @@
+/*
+ * 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.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
+
+/**
+ * Observer used to identify tasks that are opening or moving to front. If a bubble activity is
+ * currently opened when this happens, we'll collapse the bubbles.
+ */
+public class BubblesTransitionObserver implements Transitions.TransitionObserver {
+
+    private BubbleController mBubbleController;
+    private BubbleData mBubbleData;
+
+    public BubblesTransitionObserver(BubbleController controller,
+            BubbleData bubbleData) {
+        mBubbleController = controller;
+        mBubbleData = bubbleData;
+    }
+
+    @Override
+    public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        for (TransitionInfo.Change change : info.getChanges()) {
+            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+            // We only care about opens / move to fronts when bubbles are expanded & not animating.
+            if (taskInfo == null
+                    || taskInfo.taskId == INVALID_TASK_ID
+                    || !TransitionUtil.isOpeningType(change.getMode())
+                    || mBubbleController.isStackAnimating()
+                    || !mBubbleData.isExpanded()
+                    || mBubbleData.getSelectedBubble() == null) {
+                continue;
+            }
+            int expandedId = mBubbleData.getSelectedBubble().getTaskId();
+            // If the task id that's opening is the same as the expanded bubble, skip collapsing
+            // because it is our bubble that is opening.
+            if (expandedId != INVALID_TASK_ID && expandedId != taskInfo.taskId) {
+                mBubbleData.setExpanded(false);
+            }
+        }
+    }
+
+    @Override
+    public void onTransitionStarting(@NonNull IBinder transition) {
+
+    }
+
+    @Override
+    public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
+
+    }
+
+    @Override
+    public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
+
+    }
+}
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 1c2cee5..998cd5d 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
@@ -72,7 +72,6 @@
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
 import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -109,13 +108,13 @@
 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
+import java.util.Optional;
+
 import dagger.BindsOptionalOf;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
-import java.util.Optional;
-
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
  * accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -800,30 +799,10 @@
     @WMSingleton
     @Provides
     static Optional<DesktopMode> provideDesktopMode(
-            Optional<DesktopModeController> desktopModeController,
             Optional<DesktopTasksController> desktopTasksController) {
-        if (DesktopModeStatus.isProto2Enabled()) {
-            return desktopTasksController.map(DesktopTasksController::asDesktopMode);
-        }
-        return desktopModeController.map(DesktopModeController::asDesktopMode);
+        return desktopTasksController.map(DesktopTasksController::asDesktopMode);
     }
 
-    @BindsOptionalOf
-    @DynamicOverride
-    abstract DesktopModeController optionalDesktopModeController();
-
-    @WMSingleton
-    @Provides
-    static Optional<DesktopModeController> provideDesktopModeController(
-            @DynamicOverride Optional<Lazy<DesktopModeController>> desktopModeController) {
-        // Use optional-of-lazy for the dependency that this provider relies on.
-        // Lazy ensures that this provider will not be the cause the dependency is created
-        // when it will not be returned due to the condition below.
-        if (DesktopModeStatus.isProto1Enabled()) {
-            return desktopModeController.map(Lazy::get);
-        }
-        return Optional.empty();
-    }
 
     @BindsOptionalOf
     @DynamicOverride
@@ -836,7 +815,7 @@
         // Use optional-of-lazy for the dependency that this provider relies on.
         // Lazy ensures that this provider will not be the cause the dependency is created
         // when it will not be returned due to the condition below.
-        if (DesktopModeStatus.isProto2Enabled()) {
+        if (DesktopModeStatus.isEnabled()) {
             return desktopTasksController.map(Lazy::get);
         }
         return Optional.empty();
@@ -853,7 +832,7 @@
         // Use optional-of-lazy for the dependency that this provider relies on.
         // Lazy ensures that this provider will not be the cause the dependency is created
         // when it will not be returned due to the condition below.
-        if (DesktopModeStatus.isAnyEnabled()) {
+        if (DesktopModeStatus.isEnabled()) {
             return desktopModeTaskRepository.map(Lazy::get);
         }
         return Optional.empty();
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 93ce91f..e9f3e1a 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
@@ -54,7 +54,6 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
 import com.android.wm.shell.dagger.pip.PipModule;
-import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -167,6 +166,7 @@
             @ShellMainThread Handler mainHandler,
             @ShellBackgroundThread ShellExecutor bgExecutor,
             TaskViewTransitions taskViewTransitions,
+            Transitions transitions,
             SyncTransactionQueue syncQueue,
             IWindowManager wmService) {
         return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
@@ -176,7 +176,8 @@
                 statusBarService, windowManager, windowManagerShellWrapper, userManager,
                 launcherApps, logger, taskStackListener, organizer, positioner, displayController,
                 oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
-                taskViewTransitions, syncQueue, wmService, ProdBubbleProperties.INSTANCE);
+                taskViewTransitions, transitions, syncQueue, wmService,
+                ProdBubbleProperties.INSTANCE);
     }
 
     //
@@ -195,10 +196,9 @@
             ShellController shellController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
-            Optional<DesktopModeController> desktopModeController,
             Optional<DesktopTasksController> desktopTasksController,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
-        if (DesktopModeStatus.isAnyEnabled()) {
+        if (DesktopModeStatus.isEnabled()) {
             return new DesktopModeWindowDecorViewModel(
                     context,
                     mainHandler,
@@ -209,7 +209,6 @@
                     shellController,
                     syncQueue,
                     transitions,
-                    desktopModeController,
                     desktopTasksController,
                     rootTaskDisplayAreaOrganizer);
         }
@@ -351,13 +350,12 @@
             @Nullable PipTransitionController pipTransitionController,
             Optional<RecentsTransitionHandler> recentsTransitionHandler,
             KeyguardTransitionHandler keyguardTransitionHandler,
-            Optional<DesktopModeController> desktopModeController,
             Optional<DesktopTasksController> desktopTasksController,
             Optional<UnfoldTransitionHandler> unfoldHandler,
             Transitions transitions) {
         return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
                 pipTransitionController, recentsTransitionHandler,
-                keyguardTransitionHandler, desktopModeController, desktopTasksController,
+                keyguardTransitionHandler, desktopTasksController,
                 unfoldHandler);
     }
 
@@ -469,24 +467,6 @@
     @WMSingleton
     @Provides
     @DynamicOverride
-    static DesktopModeController provideDesktopModeController(Context context,
-            ShellInit shellInit,
-            ShellController shellController,
-            ShellTaskOrganizer shellTaskOrganizer,
-            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
-            Transitions transitions,
-            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
-            @ShellMainThread Handler mainHandler,
-            @ShellMainThread ShellExecutor mainExecutor
-    ) {
-        return new DesktopModeController(context, shellInit, shellController, shellTaskOrganizer,
-                rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
-                mainExecutor);
-    }
-
-    @WMSingleton
-    @Provides
-    @DynamicOverride
     static DesktopTasksController provideDesktopTasksController(
             Context context,
             ShellInit shellInit,
@@ -551,8 +531,7 @@
     @ShellCreateTriggerOverride
     @Provides
     static Object provideIndependentShellComponentsToCreate(
-            DefaultMixedHandler defaultMixedHandler,
-            Optional<DesktopModeController> desktopModeController) {
+            DefaultMixedHandler defaultMixedHandler) {
         return new Object();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
deleted file mode 100644
index 5b24d7a..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ /dev/null
@@ -1,557 +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 com.android.wm.shell.desktopmode;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.WindowConfiguration;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.database.ContentObserver;
-import android.graphics.Region;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.ArraySet;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.window.DisplayAreaInfo;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.BinderThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-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.ExternalInterfaceBinder;
-import com.android.wm.shell.common.RemoteCallable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Handles windowing changes when desktop mode system setting changes
- */
-public class DesktopModeController implements RemoteCallable<DesktopModeController>,
-        Transitions.TransitionHandler {
-
-    private final Context mContext;
-    private final ShellController mShellController;
-    private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
-    private final Transitions mTransitions;
-    private final DesktopModeTaskRepository mDesktopModeTaskRepository;
-    private final ShellExecutor mMainExecutor;
-    private final DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl();
-    private final SettingsObserver mSettingsObserver;
-
-    public DesktopModeController(Context context,
-            ShellInit shellInit,
-            ShellController shellController,
-            ShellTaskOrganizer shellTaskOrganizer,
-            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
-            Transitions transitions,
-            DesktopModeTaskRepository desktopModeTaskRepository,
-            @ShellMainThread Handler mainHandler,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        mContext = context;
-        mShellController = shellController;
-        mShellTaskOrganizer = shellTaskOrganizer;
-        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
-        mTransitions = transitions;
-        mDesktopModeTaskRepository = desktopModeTaskRepository;
-        mMainExecutor = mainExecutor;
-        mSettingsObserver = new SettingsObserver(mContext, mainHandler);
-        if (DesktopModeStatus.isProto1Enabled()) {
-            shellInit.addInitCallback(this::onInit, this);
-        }
-    }
-
-    private void onInit() {
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_DESKTOP_MODE,
-                this::createExternalInterface, this);
-        mSettingsObserver.observe();
-        if (DesktopModeStatus.isActive(mContext)) {
-            updateDesktopModeActive(true);
-        }
-        mTransitions.addHandler(this);
-    }
-
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public ShellExecutor getRemoteCallExecutor() {
-        return mMainExecutor;
-    }
-
-    /**
-     * Get connection interface between sysui and shell
-     */
-    public DesktopMode asDesktopMode() {
-        return mDesktopModeImpl;
-    }
-
-    /**
-     * Creates a new instance of the external interface to pass to another process.
-     */
-    private ExternalInterfaceBinder createExternalInterface() {
-        return new IDesktopModeImpl(this);
-    }
-
-    /**
-     * Adds a listener to find out about changes in the visibility of freeform tasks.
-     *
-     * @param listener the listener to add.
-     * @param callbackExecutor the executor to call the listener on.
-     */
-    public void addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener,
-            Executor callbackExecutor) {
-        mDesktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor);
-    }
-
-    /**
-     * Adds a listener to track changes to corners of desktop mode tasks.
-     * @param listener the listener to add.
-     * @param callbackExecutor the executor to call the listener on.
-     */
-    public void addTaskCornerListener(Consumer<Region> listener,
-            Executor callbackExecutor) {
-        mDesktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor);
-    }
-
-    @VisibleForTesting
-    void updateDesktopModeActive(boolean active) {
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
-
-        int displayId = mContext.getDisplayId();
-
-        ArrayList<RunningTaskInfo> runningTasks = mShellTaskOrganizer.getRunningTasks(displayId);
-
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        // Reset freeform windowing mode that is set per task level so tasks inherit it
-        clearFreeformForStandardTasks(runningTasks, wct);
-        if (active) {
-            moveHomeBehindVisibleTasks(runningTasks, wct);
-            setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct);
-        } else {
-            clearBoundsForStandardTasks(runningTasks, wct);
-            setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct);
-        }
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
-        } else {
-            mRootTaskDisplayAreaOrganizer.applyTransaction(wct);
-        }
-    }
-
-    private WindowContainerTransaction clearBoundsForStandardTasks(
-            ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct) {
-        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks");
-        for (RunningTaskInfo taskInfo : runningTasks) {
-            if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
-                ProtoLog.v(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
-                        taskInfo.token, taskInfo);
-                wct.setBounds(taskInfo.token, null);
-            }
-        }
-        return wct;
-    }
-
-    private void clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks,
-            WindowContainerTransaction wct) {
-        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks");
-        for (RunningTaskInfo taskInfo : runningTasks) {
-            if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
-                    && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
-                ProtoLog.v(WM_SHELL_DESKTOP_MODE,
-                        "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
-                        taskInfo);
-                wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
-            }
-        }
-    }
-
-    private void moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks,
-            WindowContainerTransaction wct) {
-        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks");
-        RunningTaskInfo homeTask = null;
-        ArrayList<RunningTaskInfo> visibleTasks = new ArrayList<>();
-        for (RunningTaskInfo taskInfo : runningTasks) {
-            if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
-                homeTask = taskInfo;
-            } else if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
-                    && taskInfo.isVisible()) {
-                visibleTasks.add(taskInfo);
-            }
-        }
-        if (homeTask == null) {
-            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: home task not found");
-        } else {
-            ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: visible tasks %d",
-                    visibleTasks.size());
-            wct.reorder(homeTask.getToken(), true /* onTop */);
-            for (RunningTaskInfo task : visibleTasks) {
-                wct.reorder(task.getToken(), true /* onTop */);
-            }
-        }
-    }
-
-    private void setDisplayAreaWindowingMode(int displayId,
-            @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct) {
-        DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
-                displayId);
-        if (displayAreaInfo == null) {
-            ProtoLog.e(WM_SHELL_DESKTOP_MODE,
-                    "unable to update windowing mode for display %d display not found", displayId);
-            return;
-        }
-
-        ProtoLog.v(WM_SHELL_DESKTOP_MODE,
-                "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
-                displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
-                windowingMode);
-
-        wct.setWindowingMode(displayAreaInfo.token, windowingMode);
-    }
-
-    /**
-     * Show apps on desktop
-     */
-    void showDesktopApps(int displayId) {
-        // Bring apps to front, ignoring their visibility status to always ensure they are on top.
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        bringDesktopAppsToFront(displayId, wct);
-
-        if (!wct.isEmpty()) {
-            if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-                // TODO(b/268662477): add animation for the transition
-                mTransitions.startTransition(TRANSIT_NONE, wct, null /* handler */);
-            } else {
-                mShellTaskOrganizer.applyTransaction(wct);
-            }
-        }
-    }
-
-    /** Get number of tasks that are marked as visible */
-    int getVisibleTaskCount(int displayId) {
-        return mDesktopModeTaskRepository.getVisibleTaskCount(displayId);
-    }
-
-    private void bringDesktopAppsToFront(int displayId, WindowContainerTransaction wct) {
-        final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(displayId);
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
-
-        final List<RunningTaskInfo> taskInfos = new ArrayList<>();
-        for (Integer taskId : activeTasks) {
-            RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
-            if (taskInfo != null) {
-                taskInfos.add(taskInfo);
-            }
-        }
-
-        if (taskInfos.isEmpty()) {
-            return;
-        }
-
-        moveHomeTaskToFront(wct);
-
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
-                "bringDesktopAppsToFront: reordering all active tasks to the front");
-        final List<Integer> allTasksInZOrder =
-                mDesktopModeTaskRepository.getFreeformTasksInZOrder();
-        // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last
-        // in the WCT.
-        taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId)));
-        for (RunningTaskInfo task : taskInfos) {
-            wct.reorder(task.token, true);
-        }
-    }
-
-    private void moveHomeTaskToFront(WindowContainerTransaction wct) {
-        for (RunningTaskInfo task : mShellTaskOrganizer.getRunningTasks(mContext.getDisplayId())) {
-            if (task.getActivityType() == ACTIVITY_TYPE_HOME) {
-                wct.reorder(task.token, true /* onTop */);
-                return;
-            }
-        }
-    }
-
-    /**
-     * Update corner rects stored for a specific task
-     * @param taskId task to update
-     * @param taskCorners task's new corner handles
-     */
-    public void onTaskCornersChanged(int taskId, Region taskCorners) {
-        mDesktopModeTaskRepository.updateTaskCorners(taskId, taskCorners);
-    }
-
-    /**
-     * Remove corners saved for a task. Likely used due to task closure.
-     * @param taskId task to remove
-     */
-    public void removeCornersForTask(int taskId) {
-        mDesktopModeTaskRepository.removeTaskCorners(taskId);
-    }
-
-    /**
-     * Moves a specifc task to the front.
-     * @param taskInfo the task to show in front.
-     */
-    public void moveTaskToFront(RunningTaskInfo taskInfo) {
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.reorder(taskInfo.token, true /* onTop */);
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null);
-        } else {
-            mShellTaskOrganizer.applyTransaction(wct);
-        }
-    }
-
-    /**
-     * Turn desktop mode on or off
-     * @param active the desired state for desktop mode setting
-     */
-    public void setDesktopModeActive(boolean active) {
-        int value = active ? 1 : 0;
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.DESKTOP_MODE, value);
-    }
-
-    /**
-     * Returns the windowing mode of the display area with the specified displayId.
-     * @param displayId
-     * @return
-     */
-    public int getDisplayAreaWindowingMode(int displayId) {
-        return mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
-                .configuration.windowConfiguration.getWindowingMode();
-    }
-
-    @Override
-    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        // This handler should never be the sole handler, so should not animate anything.
-        return false;
-    }
-
-    @Nullable
-    @Override
-    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
-            @NonNull TransitionRequestInfo request) {
-        RunningTaskInfo triggerTask = request.getTriggerTask();
-        // Only do anything if we are in desktop mode and opening/moving-to-front a task/app in
-        // freeform
-        if (!DesktopModeStatus.isActive(mContext)) {
-            ProtoLog.d(WM_SHELL_DESKTOP_MODE,
-                    "skip shell transition request: desktop mode not active");
-            return null;
-        }
-        if (request.getType() != TRANSIT_OPEN && request.getType() != TRANSIT_TO_FRONT) {
-            ProtoLog.d(WM_SHELL_DESKTOP_MODE,
-                    "skip shell transition request: unsupported type %s",
-                    WindowManager.transitTypeToString(request.getType()));
-            return null;
-        }
-        if (triggerTask == null || triggerTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
-            ProtoLog.d(WM_SHELL_DESKTOP_MODE, "skip shell transition request: not freeform task");
-            return null;
-        }
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request);
-
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        bringDesktopAppsToFront(triggerTask.displayId, wct);
-        wct.reorder(triggerTask.token, true /* onTop */);
-
-        return wct;
-    }
-
-    /**
-     * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
-     * This is intended to be used when desktop mode is part of another animation but isn't, itself,
-     * animating.
-     */
-    public void syncSurfaceState(@NonNull TransitionInfo info,
-            SurfaceControl.Transaction finishTransaction) {
-        // Add rounded corners to freeform windows
-        final TypedArray ta = mContext.obtainStyledAttributes(
-                new int[]{android.R.attr.dialogCornerRadius});
-        final int cornerRadius = ta.getDimensionPixelSize(0, 0);
-        ta.recycle();
-        for (TransitionInfo.Change change: info.getChanges()) {
-            if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-                finishTransaction.setCornerRadius(change.getLeash(), cornerRadius);
-            }
-        }
-    }
-
-    /**
-     * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
-     */
-    private final class SettingsObserver extends ContentObserver {
-
-        private final Uri mDesktopModeSetting = Settings.System.getUriFor(
-                Settings.System.DESKTOP_MODE);
-
-        private final Context mContext;
-
-        SettingsObserver(Context context, Handler handler) {
-            super(handler);
-            mContext = context;
-        }
-
-        public void observe() {
-            // TODO(b/242867463): listen for setting change for all users
-            mContext.getContentResolver().registerContentObserver(mDesktopModeSetting,
-                    false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, @Nullable Uri uri) {
-            if (mDesktopModeSetting.equals(uri)) {
-                ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting");
-                desktopModeSettingChanged();
-            }
-        }
-
-        private void desktopModeSettingChanged() {
-            boolean enabled = DesktopModeStatus.isActive(mContext);
-            updateDesktopModeActive(enabled);
-        }
-    }
-
-    /**
-     * The interface for calls from outside the shell, within the host process.
-     */
-    @ExternalThread
-    private final class DesktopModeImpl implements DesktopMode {
-
-        @Override
-        public void addVisibleTasksListener(
-                DesktopModeTaskRepository.VisibleTasksListener listener,
-                Executor callbackExecutor) {
-            mMainExecutor.execute(() -> {
-                DesktopModeController.this.addVisibleTasksListener(listener, callbackExecutor);
-            });
-        }
-
-        @Override
-        public void addDesktopGestureExclusionRegionListener(Consumer<Region> listener,
-                Executor callbackExecutor) {
-            mMainExecutor.execute(() -> {
-                DesktopModeController.this.addTaskCornerListener(listener, callbackExecutor);
-            });
-        }
-    }
-
-    /**
-     * The interface for calls from outside the host process.
-     */
-    @BinderThread
-    private static class IDesktopModeImpl extends IDesktopMode.Stub
-            implements ExternalInterfaceBinder {
-
-        private DesktopModeController mController;
-
-        IDesktopModeImpl(DesktopModeController controller) {
-            mController = controller;
-        }
-
-        /**
-         * Invalidates this instance, preventing future calls from updating the controller.
-         */
-        @Override
-        public void invalidate() {
-            mController = null;
-        }
-
-        @Override
-        public void showDesktopApps(int displayId) {
-            executeRemoteCallWithTaskPermission(mController, "showDesktopApps",
-                    controller -> controller.showDesktopApps(displayId));
-        }
-
-        @Override
-        public void showDesktopApp(int taskId) throws RemoteException {
-            // TODO
-        }
-
-        @Override
-        public int getVisibleTaskCount(int displayId) throws RemoteException {
-            int[] result = new int[1];
-            executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
-                    controller -> result[0] = controller.getVisibleTaskCount(displayId),
-                    true /* blocking */
-            );
-            return result[0];
-        }
-
-        @Override
-        public void onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo) {
-
-        }
-
-        @Override
-        public void stashDesktopApps(int displayId) throws RemoteException {
-            // Stashing of desktop apps not needed. Apps always launch on desktop
-        }
-
-        @Override
-        public void hideStashedDesktopApps(int displayId) throws RemoteException {
-            // Stashing of desktop apps not needed. Apps always launch on desktop
-        }
-
-        @Override
-        public void setTaskListener(IDesktopTaskListener listener) throws RemoteException {
-            // TODO(b/261234402): move visibility from sysui state to listener
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 517f9f2..7783113 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -16,14 +16,7 @@
 
 package com.android.wm.shell.desktopmode;
 
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
-
-import android.content.Context;
 import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.internal.protolog.common.ProtoLog;
 
 /**
  * Constants for desktop mode feature
@@ -31,13 +24,7 @@
 public class DesktopModeStatus {
 
     /**
-     * Flag to indicate whether desktop mode is available on the device
-     */
-    private static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
-            "persist.wm.debug.desktop_mode", false);
-
-    /**
-     * Flag to indicate whether desktop mode proto 2 is available on the device
+     * Flag to indicate whether desktop mode proto is available on the device
      */
     private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
             "persist.wm.debug.desktop_mode_2", false);
@@ -64,28 +51,13 @@
             "persist.wm.debug.desktop_stashing", false);
 
     /**
-     * Return {@code true} if desktop mode support is enabled
-     */
-    public static boolean isProto1Enabled() {
-        return IS_SUPPORTED;
-    }
-
-    /**
      * Return {@code true} is desktop windowing proto 2 is enabled
      */
-    public static boolean isProto2Enabled() {
+    public static boolean isEnabled() {
         return IS_PROTO2_ENABLED;
     }
 
     /**
-     * Return {@code true} if proto 1 or 2 is enabled.
-     * Can be used to guard logic that is common for both prototypes.
-     */
-    public static boolean isAnyEnabled() {
-        return isProto1Enabled() || isProto2Enabled();
-    }
-
-    /**
      * Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
      */
     public static boolean isVeiledResizeEnabled() {
@@ -99,26 +71,4 @@
     public static boolean isStashingEnabled() {
         return IS_STASHING_ENABLED;
     }
-    /**
-     * Check if desktop mode is active
-     *
-     * @return {@code true} if active
-     */
-    public static boolean isActive(Context context) {
-        if (!isAnyEnabled()) {
-            return false;
-        }
-        if (isProto2Enabled()) {
-            // Desktop mode is always active in prototype 2
-            return true;
-        }
-        try {
-            int result = Settings.System.getIntForUser(context.getContentResolver(),
-                    Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
-            return result != 0;
-        } catch (Exception e) {
-            ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
-            return false;
-        }
-    }
 }
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 236dec0..b918c83 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
@@ -122,7 +122,7 @@
 
     init {
         desktopMode = DesktopModeImpl()
-        if (DesktopModeStatus.isProto2Enabled()) {
+        if (DesktopModeStatus.isEnabled()) {
             shellInit.addInitCallback({ onInit() }, this)
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 22541bbd..a80241e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -68,7 +68,7 @@
 
     private void onInit() {
         mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
-        if (DesktopModeStatus.isAnyEnabled()) {
+        if (DesktopModeStatus.isEnabled()) {
             mShellTaskOrganizer.addFocusListener(this);
         }
     }
@@ -90,7 +90,7 @@
             t.apply();
         }
 
-        if (DesktopModeStatus.isAnyEnabled()) {
+        if (DesktopModeStatus.isEnabled()) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
                 if (taskInfo.isVisible) {
@@ -111,7 +111,7 @@
                 taskInfo.taskId);
         mTasks.remove(taskInfo.taskId);
 
-        if (DesktopModeStatus.isAnyEnabled()) {
+        if (DesktopModeStatus.isEnabled()) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 repository.removeFreeformTask(taskInfo.taskId);
                 if (repository.removeActiveTask(taskInfo.taskId)) {
@@ -135,7 +135,7 @@
                 taskInfo.taskId);
         mWindowDecorationViewModel.onTaskInfoChanged(taskInfo);
         state.mTaskInfo = taskInfo;
-        if (DesktopModeStatus.isAnyEnabled()) {
+        if (DesktopModeStatus.isEnabled()) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 if (taskInfo.isVisible) {
                     if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
@@ -154,7 +154,7 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
                 "Freeform Task Focus Changed: #%d focused=%b",
                 taskInfo.taskId, taskInfo.isFocused);
-        if (DesktopModeStatus.isAnyEnabled() && taskInfo.isFocused) {
+        if (DesktopModeStatus.isEnabled() && taskInfo.isFocused) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
             });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index e3922d6..018d674 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1059,7 +1059,21 @@
     private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange,
             @NonNull SurfaceControl.Transaction startTransaction) {
         final SurfaceControl leash = prevPipTaskChange.getLeash();
-        startTransaction.remove(leash);
+        final Rect bounds = prevPipTaskChange.getEndAbsBounds();
+        final Point offset = prevPipTaskChange.getEndRelOffset();
+        bounds.offset(-offset.x, -offset.y);
+
+        startTransaction.setWindowCrop(leash, null);
+        startTransaction.setMatrix(leash, 1, 0, 0, 1);
+        startTransaction.setCornerRadius(leash, 0);
+        startTransaction.setPosition(leash, bounds.left, bounds.top);
+
+        if (mHasFadeOut && prevPipTaskChange.getTaskInfo().isVisible()) {
+            if (mPipAnimationController.getCurrentAnimator() != null) {
+                mPipAnimationController.getCurrentAnimator().cancel();
+            }
+            startTransaction.setAlpha(leash, 1);
+        }
 
         mHasFadeOut = false;
         mCurrentPipTaskToken = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
index ec09827..6dabb3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
@@ -1,3 +1,4 @@
 # WM shell sub-module pip owner
 hwwang@google.com
 mateuszc@google.com
+gabiyev@google.com
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 ac142e9..94e1b33 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
@@ -340,7 +340,7 @@
                 continue;
             }
 
-            if (DesktopModeStatus.isProto2Enabled() && mDesktopModeTaskRepository.isPresent()
+            if (DesktopModeStatus.isEnabled() && mDesktopModeTaskRepository.isPresent()
                     && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
                 // Freeform tasks will be added as a separate entry
                 freeformTasks.add(taskInfo);
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 986560b..87ceaa4 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,7 +43,6 @@
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.split.SplitScreenUtils;
-import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
@@ -71,7 +70,6 @@
     private RecentsTransitionHandler mRecentsHandler;
     private StageCoordinator mSplitHandler;
     private final KeyguardTransitionHandler mKeyguardHandler;
-    private DesktopModeController mDesktopModeController;
     private DesktopTasksController mDesktopTasksController;
     private UnfoldTransitionHandler mUnfoldHandler;
 
@@ -141,7 +139,6 @@
             @Nullable PipTransitionController pipTransitionController,
             Optional<RecentsTransitionHandler> recentsHandlerOptional,
             KeyguardTransitionHandler keyguardHandler,
-            Optional<DesktopModeController> desktopModeControllerOptional,
             Optional<DesktopTasksController> desktopTasksControllerOptional,
             Optional<UnfoldTransitionHandler> unfoldHandler) {
         mPlayer = player;
@@ -161,7 +158,6 @@
                 if (mRecentsHandler != null) {
                     mRecentsHandler.addMixer(this);
                 }
-                mDesktopModeController = desktopModeControllerOptional.orElse(null);
                 mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
                 mUnfoldHandler = unfoldHandler.orElse(null);
             }, this);
@@ -244,7 +240,7 @@
     @Override
     public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
         if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible()
-                || DesktopModeStatus.isActive(mPlayer.getContext()))) {
+                || DesktopModeStatus.isEnabled())) {
             return this;
         }
         return null;
@@ -259,7 +255,7 @@
                     MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
             mixed.mLeftoversHandler = mRecentsHandler;
             mActiveTransitions.add(mixed);
-        } else if (DesktopModeStatus.isActive(mPlayer.getContext())) {
+        } else if (DesktopModeStatus.isEnabled()) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
                     + "desktop mode is active, so treat it as Mixed.");
             final MixedTransition mixed = new MixedTransition(
@@ -666,11 +662,6 @@
         if (!consumed) {
             return false;
         }
-        //Sync desktop mode state (proto 1)
-        if (mDesktopModeController != null) {
-            mDesktopModeController.syncSurfaceState(info, finishTransaction);
-            return true;
-        }
         //Sync desktop mode state (proto 2)
         if (mDesktopTasksController != null) {
             mDesktopTasksController.syncSurfaceState(info, finishTransaction);
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 92b44d4..abd2ad4 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
@@ -69,7 +69,6 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -102,7 +101,6 @@
     private final Choreographer mMainChoreographer;
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
-    private final Optional<DesktopModeController> mDesktopModeController;
     private final Optional<DesktopTasksController> mDesktopTasksController;
     private boolean mTransitionDragActive;
 
@@ -135,7 +133,6 @@
             ShellController shellController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
-            Optional<DesktopModeController> desktopModeController,
             Optional<DesktopTasksController> desktopTasksController,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
     ) {
@@ -149,7 +146,6 @@
                 shellController,
                 syncQueue,
                 transitions,
-                desktopModeController,
                 desktopTasksController,
                 new DesktopModeWindowDecoration.Factory(),
                 new InputMonitorFactory(),
@@ -169,7 +165,6 @@
             ShellController shellController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
-            Optional<DesktopModeController> desktopModeController,
             Optional<DesktopTasksController> desktopTasksController,
             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
             InputMonitorFactory inputMonitorFactory,
@@ -185,7 +180,6 @@
         mDisplayController = displayController;
         mSyncQueue = syncQueue;
         mTransitions = transitions;
-        mDesktopModeController = desktopModeController;
         mDesktopTasksController = desktopTasksController;
 
         mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
@@ -214,9 +208,8 @@
             public void onTaskStageChanged(int taskId, int stage, boolean visible) {
                 if (visible) {
                     DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
-                    if (decor != null && DesktopModeStatus.isActive(mContext)
+                    if (decor != null && DesktopModeStatus.isEnabled()
                             && decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-                        mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                         mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo));
                     }
                 }
@@ -376,7 +369,6 @@
                     decoration.closeHandleMenu();
                 }
             } else if (id == R.id.desktop_button) {
-                mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                 if (mDesktopTasksController.isPresent()) {
                     final WindowContainerTransaction wct = new WindowContainerTransaction();
                     // App sometimes draws before the insets from WindowDecoration#relayout have
@@ -387,7 +379,6 @@
                 }
                 decoration.closeHandleMenu();
             } else if (id == R.id.fullscreen_button) {
-                mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                 mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
                 decoration.closeHandleMenu();
             } else if (id == R.id.split_screen_button) {
@@ -465,7 +456,6 @@
         private void moveTaskToFront(RunningTaskInfo taskInfo) {
             if (!taskInfo.isFocused) {
                 mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo));
-                mDesktopModeController.ifPresent(c -> c.moveTaskToFront(taskInfo));
             }
         }
 
@@ -476,15 +466,10 @@
         @Override
         public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
             final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
-            if (DesktopModeStatus.isProto2Enabled()
+            if (DesktopModeStatus.isEnabled()
                     && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
                 return false;
             }
-            if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
-                    && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
-                    == WINDOWING_MODE_FULLSCREEN) {
-                return false;
-            }
             if (mGestureDetector.onTouchEvent(e)) {
                 return true;
             }
@@ -634,7 +619,7 @@
      */
     private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
         final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
-        if (DesktopModeStatus.isProto2Enabled()) {
+        if (DesktopModeStatus.isEnabled()) {
             if (relevantDecor == null
                     || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
                     || mTransitionDragActive) {
@@ -643,14 +628,10 @@
         }
         handleEventOutsideFocusedCaption(ev, relevantDecor);
         // Prevent status bar from reacting to a caption drag.
-        if (DesktopModeStatus.isProto2Enabled()) {
+        if (DesktopModeStatus.isEnabled()) {
             if (mTransitionDragActive) {
                 inputMonitor.pilferPointers();
             }
-        } else if (DesktopModeStatus.isProto1Enabled()) {
-            if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
-                inputMonitor.pilferPointers();
-            }
         }
     }
 
@@ -683,7 +664,7 @@
                     mDragToDesktopAnimationStartBounds.set(
                             relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
                     boolean dragFromStatusBarAllowed = false;
-                    if (DesktopModeStatus.isProto2Enabled()) {
+                    if (DesktopModeStatus.isEnabled()) {
                         // In proto2 any full screen or multi-window task can be dragged to
                         // freeform.
                         final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
@@ -708,10 +689,8 @@
                     final int statusBarHeight = getStatusBarHeight(
                             relevantDecor.mTaskInfo.displayId);
                     if (ev.getY() > 2 * statusBarHeight) {
-                        if (DesktopModeStatus.isProto2Enabled()) {
+                        if (DesktopModeStatus.isEnabled()) {
                             animateToDesktop(relevantDecor, ev);
-                        } else if (DesktopModeStatus.isProto1Enabled()) {
-                            mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                         }
                         mMoveToDesktopAnimator = null;
                         return;
@@ -902,7 +881,7 @@
                 && taskInfo.isFocused) {
             return false;
         }
-        return DesktopModeStatus.isProto2Enabled()
+        return DesktopModeStatus.isEnabled()
                 && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
                 && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
                 && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
@@ -984,13 +963,11 @@
 
         @Override
         public void onTaskCornersChanged(int taskId, Region corner) {
-            mDesktopModeController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
             mDesktopTasksController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
         }
 
         @Override
         public void onTaskCornersRemoved(int taskId) {
-            mDesktopModeController.ifPresent(d -> d.removeCornersForTask(taskId));
             mDesktopTasksController.ifPresent(d -> d.removeCornersForTask(taskId));
         }
     }
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 a75dce2..3e21c8c 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
@@ -428,7 +428,7 @@
                 .setOnTouchListener(mOnCaptionTouchListener)
                 .setLayoutId(mRelayoutParams.mLayoutResId)
                 .setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
-                .setWindowingButtonsVisible(DesktopModeStatus.isProto2Enabled())
+                .setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
                 .build();
         mHandleMenu.show();
     }
@@ -549,9 +549,6 @@
     }
 
     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;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index ac4a597..ca7cbfd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -236,7 +236,7 @@
             t.setPosition(mAppInfoPill.mWindowSurface,
                     mAppInfoPillPosition.x, mAppInfoPillPosition.y);
             // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
-            final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
+            final boolean shouldShowWindowingPill = DesktopModeStatus.isEnabled();
             if (shouldShowWindowingPill) {
                 t.setPosition(mWindowingPill.mWindowSurface,
                         mWindowingPillPosition.x, mWindowingPillPosition.y);
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index dfbadae..434b008 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -69,10 +69,22 @@
 }
 
 filegroup {
+    name: "WMShellFlickerServicePlatinumTests-src",
+    srcs: [
+        "src/com/android/wm/shell/flicker/service/*/platinum/**/*.kt",
+        "src/com/android/wm/shell/flicker/service/*/scenarios/**/*.kt",
+        "src/com/android/wm/shell/flicker/service/common/**/*.kt",
+    ],
+}
+
+filegroup {
     name: "WMShellFlickerServiceTests-src",
     srcs: [
         "src/com/android/wm/shell/flicker/service/**/*.kt",
     ],
+    exclude_srcs: [
+        "src/com/android/wm/shell/flicker/service/*/platinum/**/*.kt",
+    ],
 }
 
 java_library {
@@ -143,6 +155,7 @@
         ":WMShellFlickerTestsSplitScreenGroup2-src",
         ":WMShellFlickerTestsSplitScreenBase-src",
         ":WMShellFlickerServiceTests-src",
+        ":WMShellFlickerServicePlatinumTests-src",
     ],
 }
 
@@ -210,3 +223,15 @@
         ":WMShellFlickerServiceTests-src",
     ],
 }
+
+android_test {
+    name: "WMShellFlickerServicePlatinumTests",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestService.xml"],
+    package_name: "com.android.wm.shell.flicker.service",
+    instrumentation_target_package: "com.android.wm.shell.flicker.service",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerServicePlatinumTests-src",
+    ],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt
index fa723e3..5f15785 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.service
+package com.android.wm.shell.flicker.service.common
 
 import android.app.Instrumentation
 import android.platform.test.rule.NavigationModeRule
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
new file mode 100644
index 0000000..a5c5122
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
+    @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
new file mode 100644
index 0000000..092fb67
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
+    @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
new file mode 100644
index 0000000..8cb25fe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavLandscape :
+    DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+    @Test
+    override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
new file mode 100644
index 0000000..fa1be63
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavPortrait :
+    DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+    @Test
+    override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..aa35237
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavLandscape :
+    DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+    @Test
+    override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..e195360
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavPortrait :
+    DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+    @Test
+    override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
new file mode 100644
index 0000000..c1b3aad
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+    @Test
+    override fun dragDividerToResize() = super.dragDividerToResize()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
new file mode 100644
index 0000000..c6e2e85
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+    @Test
+    override fun dragDividerToResize() = super.dragDividerToResize()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
new file mode 100644
index 0000000..5f771c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
+    EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
new file mode 100644
index 0000000..729a401
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
+    EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
new file mode 100644
index 0000000..6e4cf9f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
+    EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromNotification() =
+        super.enterSplitScreenByDragFromNotification()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
new file mode 100644
index 0000000..cc28702
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
+    EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromNotification() =
+        super.enterSplitScreenByDragFromNotification()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
new file mode 100644
index 0000000..736604f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
+    EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
new file mode 100644
index 0000000..8df8dfa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
+    EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
new file mode 100644
index 0000000..378f055
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
+    EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
new file mode 100644
index 0000000..b33d262
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
+    EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
new file mode 100644
index 0000000..b1d3858
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavLandscape :
+    EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
new file mode 100644
index 0000000..6d824c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavPortrait :
+    EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
new file mode 100644
index 0000000..f1d3d0c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavLandscape :
+    SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios([])
+    @Test
+    override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
new file mode 100644
index 0000000..a867bac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavPortrait :
+    SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios([])
+    @Test
+    override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
new file mode 100644
index 0000000..76247ba
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
+    SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
new file mode 100644
index 0000000..e179da8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
+    SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..20f554f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavLandscape :
+    SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..f7776ee
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavPortrait :
+    SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
new file mode 100644
index 0000000..00f6073
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavLandscape :
+    SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
new file mode 100644
index 0000000..b3340e7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavPortrait :
+    SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
new file mode 100644
index 0000000..3da61e5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
new file mode 100644
index 0000000..627ae18
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
new file mode 100644
index 0000000..f4e7298
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
new file mode 100644
index 0000000..f38b2e8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index e530f63..245184c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
index e9fc437..1f2f1ec 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
index 416692c..ebbf7c5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
index 494a246..71e701c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
index 9b43816..c433b21 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index 50151f1..3f087a5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 76fbf60..767e7b5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index f8e43f1..2592fd4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index c2100f6..f2cbf24 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index 70f3bed..538ed96 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -25,7 +25,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 86f394d..0dab5ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index d7b611e..ad3a2d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index 3cc5df0..b780a16 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index 4a9c32f..329d61d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
index 383a6b3..a9933bbe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -23,7 +23,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
new file mode 100644
index 0000000..9655f97
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.IWindowContainerToken;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.TransitionInfoBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests of {@link BubblesTransitionObserver}.
+ */
+@SmallTest
+public class BubblesTransitionObserverTest {
+
+    @Mock
+    private BubbleController mBubbleController;
+    @Mock
+    private BubbleData mBubbleData;
+
+    @Mock
+    private IBinder mTransition;
+    @Mock
+    private SurfaceControl.Transaction mStartT;
+    @Mock
+    private SurfaceControl.Transaction mFinishT;
+
+    @Mock
+    private Bubble mBubble;
+
+    private BubblesTransitionObserver mTransitionObserver;
+
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mTransitionObserver = new BubblesTransitionObserver(mBubbleController, mBubbleData);
+    }
+
+    @Test
+    public void testOnTransitionReady_open_collapsesStack() {
+        when(mBubbleData.isExpanded()).thenReturn(true);
+        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+        when(mBubble.getTaskId()).thenReturn(1);
+        when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+        TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));
+
+        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+        verify(mBubbleData).setExpanded(eq(false));
+    }
+
+    @Test
+    public void testOnTransitionReady_toFront_collapsesStack() {
+        when(mBubbleData.isExpanded()).thenReturn(true);
+        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+        when(mBubble.getTaskId()).thenReturn(1);
+        when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+        TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2));
+
+        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+        verify(mBubbleData).setExpanded(eq(false));
+    }
+
+    @Test
+    public void testOnTransitionReady_noTaskInfo_skip() {
+        when(mBubbleData.isExpanded()).thenReturn(true);
+        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+        when(mBubble.getTaskId()).thenReturn(1);
+        when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+        // Null task info
+        TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, null /* taskInfo */);
+
+        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+        verify(mBubbleData, never()).setExpanded(eq(false));
+    }
+
+    @Test
+    public void testOnTransitionReady_noTaskId_skip() {
+        when(mBubbleData.isExpanded()).thenReturn(true);
+        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+        when(mBubble.getTaskId()).thenReturn(1);
+        when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+        // Invalid task id
+        TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT,
+                createTaskInfo(INVALID_TASK_ID));
+
+        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+        verify(mBubbleData, never()).setExpanded(eq(false));
+    }
+
+    @Test
+    public void testOnTransitionReady_notOpening_skip() {
+        when(mBubbleData.isExpanded()).thenReturn(true);
+        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+        when(mBubble.getTaskId()).thenReturn(1);
+        when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+        // Transits that aren't opening
+        TransitionInfo info = createTransitionInfo(TRANSIT_CHANGE, createTaskInfo(2));
+        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+        info = createTransitionInfo(TRANSIT_CLOSE, createTaskInfo(3));
+        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+        info = createTransitionInfo(TRANSIT_TO_BACK, createTaskInfo(4));
+        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+        verify(mBubbleData, never()).setExpanded(eq(false));
+    }
+
+    @Test
+    public void testOnTransitionReady_stackAnimating_skip() {
+        when(mBubbleData.isExpanded()).thenReturn(true);
+        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+        when(mBubble.getTaskId()).thenReturn(1);
+        when(mBubbleController.isStackAnimating()).thenReturn(true); // Stack is animating
+
+        TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));
+
+        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+        verify(mBubbleData, never()).setExpanded(eq(false));
+    }
+
+    @Test
+    public void testOnTransitionReady_stackNotExpanded_skip() {
+        when(mBubbleData.isExpanded()).thenReturn(false); // Stack is not expanded
+        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+        when(mBubble.getTaskId()).thenReturn(1);
+        when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+        TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2));
+
+        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+        verify(mBubbleData, never()).setExpanded(eq(false));
+    }
+
+    @Test
+    public void testOnTransitionReady_noSelectedBubble_skip() {
+        when(mBubbleData.isExpanded()).thenReturn(true);
+        when(mBubbleData.getSelectedBubble()).thenReturn(null); // No selected bubble
+        when(mBubble.getTaskId()).thenReturn(1);
+        when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+        TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));
+
+        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+        verify(mBubbleData, never()).setExpanded(eq(false));
+    }
+
+    @Test
+    public void testOnTransitionReady_openingMatchesExpanded_skip() {
+        when(mBubbleData.isExpanded()).thenReturn(true);
+        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+        when(mBubble.getTaskId()).thenReturn(1);
+        when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+        // What's moving to front is same as the opened bubble
+        TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(1));
+
+        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+        verify(mBubbleData, never()).setExpanded(eq(false));
+    }
+
+    private ActivityManager.RunningTaskInfo createTaskInfo(int taskId) {
+        final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+        taskInfo.taskId = taskId;
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        return taskInfo;
+    }
+
+    private TransitionInfo createTransitionInfo(int changeType,
+            ActivityManager.RunningTaskInfo info) {
+        final TransitionInfo.Change change = new TransitionInfo.Change(
+                new WindowContainerToken(mock(IWindowContainerToken.class)),
+                mock(SurfaceControl.class));
+        change.setMode(changeType);
+        change.setTaskInfo(info);
+
+        return new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+                .addChange(change).build();
+    }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
deleted file mode 100644
index 605a762..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ /dev/null
@@ -1,531 +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 com.android.wm.shell.desktopmode;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask;
-import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask;
-import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createHomeTask;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.testing.AndroidTestingRunner;
-import android.window.DisplayAreaInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransaction.Change;
-import android.window.WindowContainerTransaction.HierarchyOp;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.wm.shell.MockToken;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class DesktopModeControllerTest extends ShellTestCase {
-
-    private static final int SECOND_DISPLAY = 2;
-
-    @Mock
-    private ShellController mShellController;
-    @Mock
-    private ShellTaskOrganizer mShellTaskOrganizer;
-    @Mock
-    private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
-    @Mock
-    private ShellExecutor mTestExecutor;
-    @Mock
-    private Handler mMockHandler;
-    @Mock
-    private Transitions mTransitions;
-    private DesktopModeController mController;
-    private DesktopModeTaskRepository mDesktopModeTaskRepository;
-    private ShellInit mShellInit;
-    private StaticMockitoSession mMockitoSession;
-
-    @Before
-    public void setUp() {
-        mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
-        when(DesktopModeStatus.isProto1Enabled()).thenReturn(true);
-        when(DesktopModeStatus.isActive(any())).thenReturn(true);
-
-        mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
-
-        mDesktopModeTaskRepository = new DesktopModeTaskRepository();
-
-        mController = createController();
-
-        when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
-
-        mShellInit.init();
-        clearInvocations(mShellTaskOrganizer);
-        clearInvocations(mRootTaskDisplayAreaOrganizer);
-        clearInvocations(mTransitions);
-    }
-
-    @After
-    public void tearDown() {
-        mMockitoSession.finishMocking();
-    }
-
-    @Test
-    public void instantiate_addInitCallback() {
-        verify(mShellInit).addInitCallback(any(), any());
-    }
-
-    @Test
-    public void instantiate_flagOff_doNotAddInitCallback() {
-        when(DesktopModeStatus.isProto1Enabled()).thenReturn(false);
-        clearInvocations(mShellInit);
-
-        createController();
-
-        verify(mShellInit, never()).addInitCallback(any(), any());
-    }
-
-    @Test
-    public void testDesktopModeEnabled_rootTdaSetToFreeform() {
-        DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
-
-        mController.updateDesktopModeActive(true);
-        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
-        // 1 change: Root TDA windowing mode
-        assertThat(wct.getChanges().size()).isEqualTo(1);
-        // Verify WCT has a change for setting windowing mode to freeform
-        Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
-        assertThat(change).isNotNull();
-        assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
-    }
-
-    @Test
-    public void testDesktopModeDisabled_rootTdaSetToFullscreen() {
-        DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
-
-        mController.updateDesktopModeActive(false);
-        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
-        // 1 change: Root TDA windowing mode
-        assertThat(wct.getChanges().size()).isEqualTo(1);
-        // Verify WCT has a change for setting windowing mode to fullscreen
-        Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
-        assertThat(change).isNotNull();
-        assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
-    }
-
-    @Test
-    public void testDesktopModeEnabled_windowingModeCleared() {
-        createMockDisplayArea();
-        RunningTaskInfo freeformTask = createFreeformTask();
-        RunningTaskInfo fullscreenTask = createFullscreenTask();
-        RunningTaskInfo homeTask = createHomeTask();
-        when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
-                Arrays.asList(freeformTask, fullscreenTask, homeTask)));
-
-        mController.updateDesktopModeActive(true);
-        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
-        // 2 changes: Root TDA windowing mode and 1 task
-        assertThat(wct.getChanges().size()).isEqualTo(2);
-        // No changes for tasks that are not standard or freeform
-        assertThat(wct.getChanges().get(fullscreenTask.token.asBinder())).isNull();
-        assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
-        // Standard freeform task has windowing mode cleared
-        Change change = wct.getChanges().get(freeformTask.token.asBinder());
-        assertThat(change).isNotNull();
-        assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
-    }
-
-    @Test
-    public void testDesktopModeDisabled_windowingModeAndBoundsCleared() {
-        createMockDisplayArea();
-        RunningTaskInfo freeformTask = createFreeformTask();
-        RunningTaskInfo fullscreenTask = createFullscreenTask();
-        RunningTaskInfo homeTask = createHomeTask();
-        when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
-                Arrays.asList(freeformTask, fullscreenTask, homeTask)));
-
-        mController.updateDesktopModeActive(false);
-        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
-        // 3 changes: Root TDA windowing mode and 2 tasks
-        assertThat(wct.getChanges().size()).isEqualTo(3);
-        // No changes to home task
-        assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
-        // Standard tasks have bounds cleared
-        assertThatBoundsCleared(wct.getChanges().get(freeformTask.token.asBinder()));
-        assertThatBoundsCleared(wct.getChanges().get(fullscreenTask.token.asBinder()));
-        // Freeform standard tasks have windowing mode cleared
-        assertThat(wct.getChanges().get(
-                freeformTask.token.asBinder()).getWindowingMode()).isEqualTo(
-                WINDOWING_MODE_UNDEFINED);
-    }
-
-    @Test
-    public void testDesktopModeEnabled_homeTaskBehindVisibleTask() {
-        createMockDisplayArea();
-        RunningTaskInfo fullscreenTask1 = createFullscreenTask();
-        fullscreenTask1.isVisible = true;
-        RunningTaskInfo fullscreenTask2 = createFullscreenTask();
-        fullscreenTask2.isVisible = false;
-        RunningTaskInfo homeTask = createHomeTask();
-        when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
-                Arrays.asList(fullscreenTask1, fullscreenTask2, homeTask)));
-
-        mController.updateDesktopModeActive(true);
-        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
-        // Check that there are hierarchy changes for home task and visible task
-        assertThat(wct.getHierarchyOps()).hasSize(2);
-        // First show home task
-        HierarchyOp op1 = wct.getHierarchyOps().get(0);
-        assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
-
-        // Then visible task on top of it
-        HierarchyOp op2 = wct.getHierarchyOps().get(1);
-        assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
-    }
-
-    @Test
-    public void testShowDesktopApps_allAppsInvisible_bringsToFront() {
-        // Set up two active tasks on desktop, task2 is on top of task1.
-        RunningTaskInfo freeformTask1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask1.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
-                DEFAULT_DISPLAY, freeformTask1.taskId, false /* visible */);
-        RunningTaskInfo freeformTask2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask2.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
-                DEFAULT_DISPLAY, freeformTask2.taskId, false /* visible */);
-        when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
-                freeformTask1);
-        when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
-                freeformTask2);
-
-        // Run show desktop apps logic
-        mController.showDesktopApps(DEFAULT_DISPLAY);
-
-        final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
-        // Check wct has reorder calls
-        assertThat(wct.getHierarchyOps()).hasSize(2);
-
-        // Task 1 appeared first, must be first reorder to top.
-        HierarchyOp op1 = wct.getHierarchyOps().get(0);
-        assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op1.getContainer()).isEqualTo(freeformTask1.token.asBinder());
-
-        // Task 2 appeared last, must be last reorder to top.
-        HierarchyOp op2 = wct.getHierarchyOps().get(1);
-        assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op2.getContainer()).isEqualTo(freeformTask2.token.asBinder());
-    }
-
-    @Test
-    public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() {
-        final RunningTaskInfo task1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
-                true /* visible */);
-        when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
-        final RunningTaskInfo task2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
-                true /* visible */);
-        when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
-
-        mController.showDesktopApps(DEFAULT_DISPLAY);
-
-        final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
-        // Check wct has reorder calls
-        assertThat(wct.getHierarchyOps()).hasSize(2);
-        // Task 1 appeared first, must be first reorder to top.
-        HierarchyOp op1 = wct.getHierarchyOps().get(0);
-        assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
-
-        // Task 2 appeared last, must be last reorder to top.
-        HierarchyOp op2 = wct.getHierarchyOps().get(1);
-        assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
-    }
-
-    @Test
-    public void testShowDesktopApps_someAppsInvisible_reordersAll() {
-        final RunningTaskInfo task1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
-                false /* visible */);
-        when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
-        final RunningTaskInfo task2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
-                true /* visible */);
-        when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
-
-        mController.showDesktopApps(DEFAULT_DISPLAY);
-
-        final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
-        // Both tasks should be reordered to top, even if one was already visible.
-        assertThat(wct.getHierarchyOps()).hasSize(2);
-        final HierarchyOp op1 = wct.getHierarchyOps().get(0);
-        assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
-        final HierarchyOp op2 = wct.getHierarchyOps().get(1);
-        assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
-    }
-
-    @Test
-    public void testShowDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() {
-        RunningTaskInfo taskDefaultDisplay = createFreeformTask(DEFAULT_DISPLAY);
-        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
-                DEFAULT_DISPLAY, taskDefaultDisplay.taskId, false /* visible */);
-        when(mShellTaskOrganizer.getRunningTaskInfo(taskDefaultDisplay.taskId)).thenReturn(
-                taskDefaultDisplay);
-
-        RunningTaskInfo taskSecondDisplay = createFreeformTask(SECOND_DISPLAY);
-        mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
-                SECOND_DISPLAY, taskSecondDisplay.taskId, false /* visible */);
-        when(mShellTaskOrganizer.getRunningTaskInfo(taskSecondDisplay.taskId)).thenReturn(
-                taskSecondDisplay);
-
-        mController.showDesktopApps(DEFAULT_DISPLAY);
-
-        WindowContainerTransaction wct = getBringAppsToFrontTransaction();
-        assertThat(wct.getHierarchyOps()).hasSize(1);
-        HierarchyOp op = wct.getHierarchyOps().get(0);
-        assertThat(op.getContainer()).isEqualTo(taskDefaultDisplay.token.asBinder());
-    }
-
-    @Test
-    public void testGetVisibleTaskCount_noTasks_returnsZero() {
-        assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0);
-    }
-
-    @Test
-    public void testGetVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
-        RunningTaskInfo task1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
-                true /* visible */);
-
-        RunningTaskInfo task2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
-                true /* visible */);
-
-        assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2);
-    }
-
-    @Test
-    public void testGetVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
-        RunningTaskInfo task1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
-                true /* visible */);
-
-        RunningTaskInfo task2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
-                false /* visible */);
-
-        assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1);
-    }
-
-    @Test
-    public void testGetVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
-        RunningTaskInfo taskDefaultDisplay = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY,
-                taskDefaultDisplay.taskId,
-                true /* visible */);
-
-        RunningTaskInfo taskSecondDisplay = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId);
-        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(SECOND_DISPLAY,
-                taskSecondDisplay.taskId,
-                true /* visible */);
-
-        assertThat(mController.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1);
-    }
-
-    @Test
-    public void testHandleTransitionRequest_desktopModeNotActive_returnsNull() {
-        when(DesktopModeStatus.isActive(any())).thenReturn(false);
-        WindowContainerTransaction wct = mController.handleRequest(
-                new Binder(),
-                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
-        assertThat(wct).isNull();
-    }
-
-    @Test
-    public void testHandleTransitionRequest_unsupportedTransit_returnsNull() {
-        WindowContainerTransaction wct = mController.handleRequest(
-                new Binder(),
-                new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
-        assertThat(wct).isNull();
-    }
-
-    @Test
-    public void testHandleTransitionRequest_notFreeform_returnsNull() {
-        RunningTaskInfo trigger = new RunningTaskInfo();
-        trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-        WindowContainerTransaction wct = mController.handleRequest(
-                new Binder(),
-                new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */));
-        assertThat(wct).isNull();
-    }
-
-    @Test
-    public void testHandleTransitionRequest_taskOpen_returnsWct() {
-        RunningTaskInfo trigger = new RunningTaskInfo();
-        trigger.token = new MockToken().token();
-        trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        WindowContainerTransaction wct = mController.handleRequest(
-                mock(IBinder.class),
-                new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */));
-        assertThat(wct).isNotNull();
-    }
-
-    @Test
-    public void testHandleTransitionRequest_taskToFront_returnsWct() {
-        RunningTaskInfo trigger = new RunningTaskInfo();
-        trigger.token = new MockToken().token();
-        trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        WindowContainerTransaction wct = mController.handleRequest(
-                mock(IBinder.class),
-                new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */));
-        assertThat(wct).isNotNull();
-    }
-
-    @Test
-    public void testHandleTransitionRequest_taskOpen_doesNotStartAnotherTransition() {
-        RunningTaskInfo trigger = new RunningTaskInfo();
-        trigger.token = new MockToken().token();
-        trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        mController.handleRequest(
-                mock(IBinder.class),
-                new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */));
-        verifyZeroInteractions(mTransitions);
-    }
-
-    private DesktopModeController createController() {
-        return new DesktopModeController(mContext, mShellInit, mShellController,
-                mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
-                mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
-    }
-
-    private DisplayAreaInfo createMockDisplayArea() {
-        DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().token(),
-                mContext.getDisplayId(), 0);
-        when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
-                .thenReturn(displayAreaInfo);
-        return displayAreaInfo;
-    }
-
-    private WindowContainerTransaction getDesktopModeSwitchTransaction() {
-        ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
-                WindowContainerTransaction.class);
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            verify(mTransitions).startTransition(eq(TRANSIT_CHANGE), arg.capture(), any());
-        } else {
-            verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
-        }
-        return arg.getValue();
-    }
-
-    private WindowContainerTransaction getBringAppsToFrontTransaction() {
-        final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
-                WindowContainerTransaction.class);
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            verify(mTransitions).startTransition(eq(TRANSIT_NONE), arg.capture(), any());
-        } else {
-            verify(mShellTaskOrganizer).applyTransaction(arg.capture());
-        }
-        return arg.getValue();
-    }
-
-    private void assertThatBoundsCleared(Change change) {
-        assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
-        assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
-    }
-
-}
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 5d87cf8..be4a287 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
@@ -107,7 +107,7 @@
     @Before
     fun setUp() {
         mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking()
-        whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(true)
+        whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
 
         shellInit = Mockito.spy(ShellInit(testExecutor))
         desktopModeTaskRepository = DesktopModeTaskRepository()
@@ -154,7 +154,7 @@
 
     @Test
     fun instantiate_flagOff_doNotAddInitCallback() {
-        whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(false)
+        whenever(DesktopModeStatus.isEnabled()).thenReturn(false)
         clearInvocations(shellInit)
 
         createController()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 9e9e1ca..40ce785 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -257,7 +257,7 @@
     public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() {
         StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
                 DesktopModeStatus.class).startMocking();
-        when(DesktopModeStatus.isProto2Enabled()).thenReturn(true);
+        when(DesktopModeStatus.isEnabled()).thenReturn(true);
 
         ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
         ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -297,7 +297,7 @@
     public void testGetRecentTasks_hasActiveDesktopTasks_proto2Disabled_doNotGroupFreeformTasks() {
         StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
                 DesktopModeStatus.class).startMocking();
-        when(DesktopModeStatus.isProto2Enabled()).thenReturn(false);
+        when(DesktopModeStatus.isEnabled()).thenReturn(false);
 
         ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
         ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index 7f0465a..d8afe68 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -55,7 +55,6 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -88,7 +87,6 @@
     @Mock private DisplayController mDisplayController;
     @Mock private DisplayLayout mDisplayLayout;
     @Mock private SyncTransactionQueue mSyncQueue;
-    @Mock private DesktopModeController mDesktopModeController;
     @Mock private DesktopTasksController mDesktopTasksController;
     @Mock private InputMonitor mInputMonitor;
     @Mock private InputManager mInputManager;
@@ -121,7 +119,6 @@
                         mShellController,
                         mSyncQueue,
                         mTransitions,
-                        Optional.of(mDesktopModeController),
                         Optional.of(mDesktopTasksController),
                         mDesktopModeWindowDecorFactory,
                         mMockInputMonitorFactory,
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 5ffec34..6523469 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -1013,13 +1013,13 @@
     const auto& assets = GetApkAssets(step.cookie);
     log_stream << "\n\t" << prefix << ": " << (assets ? assets->GetDebugName() : "<null>")
                << " #" << step.cookie;
-    if (!step.config_name.isEmpty()) {
+    if (!step.config_name.empty()) {
       log_stream << " - " << step.config_name;
     }
   }
 
   log_stream << "\nBest matching is from "
-             << (last_resolution_.best_config_name.isEmpty() ? "default"
+             << (last_resolution_.best_config_name.empty() ? "default"
                                                    : last_resolution_.best_config_name)
              << " configuration of " << last_resolution_.best_package_name;
   return log_stream.str();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 120d812..d54c5f5 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -162,7 +162,6 @@
     destroyHardwareResources();
     mAnimationContext->destroy();
     mRenderThread.cacheManager().onContextStopped(this);
-    mHintSessionWrapper.destroy();
 }
 
 static void setBufferCount(ANativeWindow* window) {
diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp
index d344a981..858813f 100644
--- a/libs/incident/src/IncidentReportArgs.cpp
+++ b/libs/incident/src/IncidentReportArgs.cpp
@@ -152,8 +152,8 @@
     }
     mPrivacyPolicy = privacyPolicy;
 
-    mReceiverPkg = String8(in->readString16()).string();
-    mReceiverCls = String8(in->readString16()).string();
+    mReceiverPkg = String8(in->readString16()).c_str();
+    mReceiverCls = String8(in->readString16()).c_str();
 
     int32_t gzip;
     err = in->readInt32(&gzip);
diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp
index 3716e01..60bb00a 100644
--- a/libs/services/src/os/DropBoxManager.cpp
+++ b/libs/services/src/os/DropBoxManager.cpp
@@ -196,7 +196,7 @@
     vector<uint8_t> dataArg;
     dataArg.assign(data, data + size);
     Status status = service->addData(tag, dataArg, flags);
-    ALOGD("service->add returned %s", status.toString8().string());
+    ALOGD("service->add returned %s", status.toString8().c_str());
     return status;
 }
 
@@ -230,7 +230,7 @@
     android::base::unique_fd uniqueFd(fd);
     android::os::ParcelFileDescriptor parcelFd(std::move(uniqueFd));
     Status status = service->addFile(tag, parcelFd, flags);
-    ALOGD("service->add returned %s", status.toString8().string());
+    ALOGD("service->add returned %s", status.toString8().c_str());
     return status;
 }
 
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index b0769ab..e28ad67 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -1525,50 +1525,61 @@
     /**
      * Gets the GNSS measurement's code type.
      *
-     * <p>Similar to the Attribute field described in RINEX 3.03, e.g., in Tables 4-10, and Table
-     * A2 at the RINEX 3.03 Update 1 Document.
+     * <p>Similar to the Attribute field described in RINEX 4.00, e.g., in Tables 9-16 (see
+     * https://igs.org/wg/rinex/#documents-formats).
      *
-     * <p>Returns "A" for GALILEO E1A, GALILEO E6A, IRNSS L5A, IRNSS SA.
+     * <p>Returns "A" for GALILEO E1A, GALILEO E6A, IRNSS L5A SPS, IRNSS SA SPS, GLONASS G1a L1OCd,
+     * GLONASS G2a L2CSI.
      *
-     * <p>Returns "B" for GALILEO E1B, GALILEO E6B, IRNSS L5B, IRNSS SB.
+     * <p>Returns "B" for GALILEO E1B, GALILEO E6B, IRNSS L5B RS (D), IRNSS SB RS (D), GLONASS G1a
+     * L1OCp, GLONASS G2a L2OCp, QZSS L1Sb.
      *
-     * <p>Returns "C" for GPS L1 C/A,  GPS L2 C/A, GLONASS G1 C/A, GLONASS G2 C/A, GALILEO E1C,
-     * GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, IRNSS L5C.
+     * <p>Returns "C" for GPS L1 C/A, GPS L2 C/A, GLONASS G1 C/A, GLONASS G2 C/A, GALILEO E1C,
+     * GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, IRNSS L5C RS (P), IRNSS SC RS (P).
      *
-     * <p>Returns "D" for BDS B1C D.
+     * <p>Returns "D" for GPS L2 (L1(C/A) + (P2-P1) (semi-codeless)), QZSS L5S(I), BDS B1C Data,
+     * BDS B2a Data, BDS B2b Data, BDS B2 (B2a+B2b) Data, BDS B3a Data.
+     *
+     * <p>Returns “E” for QZSS L1 C/B, QZSS L6E.
      *
      * <p>Returns "I" for GPS L5 I, GLONASS G3 I, GALILEO E5a I, GALILEO E5b I, GALILEO E5a+b I,
      * SBAS L5 I, QZSS L5 I, BDS B1 I, BDS B2 I, BDS B3 I.
      *
-     * <p>Returns "L" for GPS L1C (P), GPS L2C (L), QZSS L1C (P), QZSS L2C (L), LEX(6) L.
+     * <p>Returns "L" for GPS L1C (P), GPS L2C (L), QZSS L1C (P), QZSS L2C (L), QZSS L6P, BDS
+     * B1a Pilot.
      *
      * <p>Returns "M" for GPS L1M, GPS L2M.
      *
      * <p>Returns "N" for GPS L1 codeless, GPS L2 codeless.
      *
-     * <p>Returns "P" for GPS L1P, GPS L2P, GLONASS G1P, GLONASS G2P, BDS B1C P.
+     * <p>Returns "P" for GPS L1P, GPS L2P, GLONASS G1P, GLONASS G2P, BDS B1C Pilot, BDS B2a Pilot,
+     * BDS B2b Pilot, BDS B2 (B2a+B2b) Pilot, BDS B3a Pilot, QZSS L5S(Q).
      *
      * <p>Returns "Q" for GPS L5 Q, GLONASS G3 Q, GALILEO E5a Q, GALILEO E5b Q, GALILEO E5a+b Q,
      * SBAS L5 Q, QZSS L5 Q, BDS B1 Q, BDS B2 Q, BDS B3 Q.
      *
-     * <p>Returns "S" for GPS L1C (D), GPS L2C (M), QZSS L1C (D), QZSS L2C (M), LEX(6) S.
+     * <p>Returns "S" for GPS L1C (D), GPS L2C (M), QZSS L1C (D), QZSS L2C (M), QZSS L6D, BDS B1a
+     * Data.
      *
      * <p>Returns "W" for GPS L1 Z-tracking, GPS L2 Z-tracking.
      *
-     * <p>Returns "X" for GPS L1C (D+P), GPS L2C (M+L), GPS L5 (I+Q), GLONASS G3 (I+Q), GALILEO
-     * E1 (B+C), GALILEO E5a (I+Q), GALILEO E5b (I+Q), GALILEO E5a+b(I+Q), GALILEO E6 (B+C), SBAS
-     * L5 (I+Q), QZSS L1C (D+P), QZSS L2C (M+L), QZSS L5 (I+Q), LEX(6) (S+L), BDS B1 (I+Q), BDS
-     * B1C (D+P), BDS B2 (I+Q), BDS B3 (I+Q), IRNSS L5 (B+C).
+     * <p>Returns "X" for GPS L1C (D+P), GPS L2C (M+L), GPS L5 (I+Q), GLONASS G1a L1OCd+L1OCp,
+     * GLONASS G2a L2CSI+L2OCp, GLONASS G3 (I+Q), GALILEO E1 (B+C), GALILEO E5a (I+Q), GALILEO
+     * E5b (I+Q), GALILEO E5a+b (I+Q), GALILEO E6 (B+C), SBAS L5 (I+Q), QZSS L1C (D+P), QZSS L2C
+     * (M+L), QZSS L5 (I+Q), QZSS L6 (D+P), BDS B1 (I+Q), BDS B1C Data+Pilot, BDS B2a Data+Pilot,
+     * BDS B2 (I+Q), BDS B2 (B2a+B2b) Data+Pilot, BDS B3 (I+Q), IRNSS L5 (B+C), IRNSS S (B+C).
      *
      * <p>Returns "Y" for GPS L1Y, GPS L2Y.
      *
-     * <p>Returns "Z" for GALILEO E1 (A+B+C), GALILEO E6 (A+B+C), QZSS L1-SAIF.
+     * <p>Returns "Z" for GALILEO E1 (A+B+C), GALILEO E6 (A+B+C), QZSS L1S/L1-SAIF, QZSS L5S (I+Q),
+     * QZSS L6(D+E), BDS B1A Data+Pilot, BDS B2b Data+Pilot, BDS B3a Data+Pilot.
      *
      * <p>Returns "UNKNOWN" if the GNSS Measurement's code type is unknown.
      *
-     * <p>This is used to specify the observation descriptor defined in GNSS Observation Data File
-     * Header Section Description in the RINEX standard (Version 3.XX), in cases where the code type
-     * does not align with the above listed values. For example, if a code type "G" is added, this
+     * <p>The code type is used to specify the observation descriptor defined in GNSS Observation
+     * Data File Header Section Description in the RINEX standard (Version 4.00). In cases where
+     * the code type does not align with the above listed values, the code type from the most
+     * recent version of RINEX should be used. For example, if a code type "G" is added, this
      * string shall be set to "G".
      */
     @NonNull
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 0eb657a..fd3e5a2 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -709,11 +709,9 @@
     /**
      * Returns the Mean Sea Level altitude of this location in meters.
      *
-     * @throws IllegalStateException if {@link #hasMslAltitude()} is false.
+     * <p>This is only valid if {@link #hasMslAltitude()} is true.
      */
     public @FloatRange double getMslAltitudeMeters() {
-        Preconditions.checkState(hasMslAltitude(),
-                "The Mean Sea Level altitude of this location is not set.");
         return mMslAltitudeMeters;
     }
 
@@ -744,11 +742,9 @@
      * percentile confidence level. This means that there is 68% chance that the true Mean Sea Level
      * altitude of this location falls within {@link #getMslAltitudeMeters()} +/- this uncertainty.
      *
-     * @throws IllegalStateException if {@link #hasMslAltitudeAccuracy()} is false.
+     * <p>This is only valid if {@link #hasMslAltitudeAccuracy()} is true.
      */
     public @FloatRange(from = 0.0) float getMslAltitudeAccuracyMeters() {
-        Preconditions.checkState(hasMslAltitudeAccuracy(),
-                "The Mean Sea Level altitude accuracy of this location is not set.");
         return mMslAltitudeAccuracyMeters;
     }
 
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index da2e56f..382e65d 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -617,7 +617,7 @@
                         "match the ImageReader's configured buffer format 0x%x.",
                         bufferFormat, ctx->getBufferFormat());
                 jniThrowException(env, "java/lang/UnsupportedOperationException",
-                        msg.string());
+                        msg.c_str());
                 return -1;
             }
         }
@@ -795,7 +795,7 @@
         String8 msg;
         msg.appendFormat("Format 0x%x is opaque, thus not writable, the number of planes (%d)"
                 " must be 0", halReaderFormat, numPlanes);
-        jniThrowException(env, "java/lang/IllegalArgumentException", msg.string());
+        jniThrowException(env, "java/lang/IllegalArgumentException", msg.c_str());
         return NULL;
     }
 
@@ -860,7 +860,7 @@
         String8 msg;
         msg.appendFormat("Format 0x%x is opaque, thus not writable, the number of planes (%d)"
                 " must be 0", halReaderFormat, numPlanes);
-        jniThrowException(env, "java/lang/IllegalArgumentException", msg.string());
+        jniThrowException(env, "java/lang/IllegalArgumentException", msg.c_str());
         return NULL;
     }
 
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index 1927b6c..f64233f 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -1068,7 +1068,7 @@
         String8 msg;
         msg.appendFormat("Format 0x%x is opaque, thus not writable, the number of planes (%d)"
                 " must be 0", writerFormat, numPlanes);
-        jniThrowException(env, "java/lang/IllegalArgumentException", msg.string());
+        jniThrowException(env, "java/lang/IllegalArgumentException", msg.c_str());
         return NULL;
     }
 
diff --git a/media/jni/android_media_MediaCrypto.cpp b/media/jni/android_media_MediaCrypto.cpp
index f491be8..5506f61 100644
--- a/media/jni/android_media_MediaCrypto.cpp
+++ b/media/jni/android_media_MediaCrypto.cpp
@@ -299,7 +299,7 @@
             std::string strerr(StrCryptoError(err));
             msg.appendFormat(": general failure (%s)", strerr.c_str());
         }
-        jniThrowException(env, "android/media/MediaCryptoException", msg.string());
+        jniThrowException(env, "android/media/MediaCryptoException", msg.c_str());
     }
 }
 
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index b70818d..c616b84f 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -708,8 +708,8 @@
     jclass clazz = gFields.hashmapClassId;
     jobject hashMap = env->NewObject(clazz, gFields.hashmap.init);
     for (size_t i = 0; i < map.size(); ++i) {
-        jstring jkey = env->NewStringUTF(map.keyAt(i).string());
-        jstring jvalue = env->NewStringUTF(map.valueAt(i).string());
+        jstring jkey = env->NewStringUTF(map.keyAt(i).c_str());
+        jstring jvalue = env->NewStringUTF(map.valueAt(i).c_str());
         env->CallObjectMethod(hashMap, gFields.hashmap.put, jkey, jvalue);
         env->DeleteLocalRef(jkey);
         env->DeleteLocalRef(jvalue);
@@ -1169,7 +1169,7 @@
         jbyteArray jrequest = VectorToJByteArray(env, request);
         env->SetObjectField(keyObj, gFields.keyRequest.data, jrequest);
 
-        jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string());
+        jstring jdefaultUrl = env->NewStringUTF(defaultUrl.c_str());
         env->SetObjectField(keyObj, gFields.keyRequest.defaultUrl, jdefaultUrl);
 
         switch (keyRequestType) {
@@ -1332,7 +1332,7 @@
         jbyteArray jrequest = VectorToJByteArray(env, request);
         env->SetObjectField(provisionObj, gFields.provisionRequest.data, jrequest);
 
-        jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string());
+        jstring jdefaultUrl = env->NewStringUTF(defaultUrl.c_str());
         env->SetObjectField(provisionObj, gFields.provisionRequest.defaultUrl, jdefaultUrl);
     }
 
@@ -1686,7 +1686,7 @@
         return NULL;
     }
 
-    return env->NewStringUTF(value.string());
+    return env->NewStringUTF(value.c_str());
 }
 
 static jbyteArray android_media_MediaDrm_getPropertyByteArray(
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index ddc51cd..1458758 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -126,7 +126,7 @@
     tmp = NULL;
 
     // Don't let somebody trick us in to reading some random block of memory
-    if (strncmp("mem://", pathStr.string(), 6) == 0) {
+    if (strncmp("mem://", pathStr.c_str(), 6) == 0) {
         jniThrowException(
                 env, "java/lang/IllegalArgumentException", "Invalid pathname");
         return;
@@ -149,7 +149,7 @@
             env,
             retriever->setDataSource(
                 httpService,
-                pathStr.string(),
+                pathStr.c_str(),
                 headersVector.size() > 0 ? &headersVector : NULL),
 
             "java/lang/RuntimeException",
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 44aff64..3551ea4 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1234,7 +1234,7 @@
     String8 vendorMessage;
     if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) {
         vendorMessage = String8::format("DRM vendor-defined error: %d", err);
-        drmMessage = vendorMessage.string();
+        drmMessage = vendorMessage.c_str();
     }
 
     if (err == BAD_VALUE) {
@@ -1260,7 +1260,7 @@
                 msg = drmMessage;
             } else {
                 errbuf = String8::format("%s: %s", msg, drmMessage);
-                msg = errbuf.string();
+                msg = errbuf.c_str();
             }
         }
         throwDrmStateException(env, msg, err);
diff --git a/media/jni/android_media_Streams.cpp b/media/jni/android_media_Streams.cpp
index 4fd5153..dffeb89 100644
--- a/media/jni/android_media_Streams.cpp
+++ b/media/jni/android_media_Streams.cpp
@@ -38,7 +38,7 @@
 
 FileStream::FileStream(const String8 filename)
     : mPosition(0) {
-    mFile = fopen(filename.string(), "r");
+    mFile = fopen(filename.c_str(), "r");
     if (mFile == NULL) {
         return;
     }
@@ -86,7 +86,7 @@
 
     if (!piex::IsRaw(stream)) {
         // Format not supported.
-        ALOGV("Format not supported: %s", filename.string());
+        ALOGV("Format not supported: %s", filename.c_str());
         return false;
     }
 
@@ -94,7 +94,7 @@
 
     if (err != piex::Error::kOk) {
         // The input data seems to be broken.
-        ALOGV("Raw image not detected: %s (piex error code: %d)", filename.string(), (int32_t)err);
+        ALOGV("Raw image not detected: %s (piex error code: %d)", filename.c_str(), (int32_t)err);
         return false;
     }
 
diff --git a/native/android/asset_manager.cpp b/native/android/asset_manager.cpp
index 69cf804..1878716 100644
--- a/native/android/asset_manager.cpp
+++ b/native/android/asset_manager.cpp
@@ -118,7 +118,7 @@
     // the string to return and advance the iterator for next time.
     if (index < max) {
         assetDir->mCachedFileName = assetDir->mAssetDir->getFileName(index);
-        returnName = assetDir->mCachedFileName.string();
+        returnName = assetDir->mCachedFileName.c_str();
         index++;
     }
 
@@ -134,7 +134,7 @@
 const char* AAssetDir_getFileName(AAssetDir* assetDir, int index)
 {
     assetDir->mCachedFileName = assetDir->mAssetDir->getFileName(index);
-    return assetDir->mCachedFileName.string();
+    return assetDir->mCachedFileName.c_str();
 }
 
 void AAssetDir_close(AAssetDir* assetDir)
diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp
index 968de34..bb8708b 100644
--- a/native/android/sensor.cpp
+++ b/native/android/sensor.cpp
@@ -304,12 +304,12 @@
 
 const char* ASensor_getName(ASensor const* sensor) {
     RETURN_IF_SENSOR_IS_NULL(nullptr);
-    return static_cast<Sensor const*>(sensor)->getName().string();
+    return static_cast<Sensor const*>(sensor)->getName().c_str();
 }
 
 const char* ASensor_getVendor(ASensor const* sensor) {
     RETURN_IF_SENSOR_IS_NULL(nullptr);
-    return static_cast<Sensor const*>(sensor)->getVendor().string();
+    return static_cast<Sensor const*>(sensor)->getVendor().c_str();
 }
 
 int ASensor_getType(ASensor const* sensor) {
@@ -339,7 +339,7 @@
 
 const char* ASensor_getStringType(ASensor const* sensor) {
     RETURN_IF_SENSOR_IS_NULL(nullptr);
-    return static_cast<Sensor const*>(sensor)->getStringType().string();
+    return static_cast<Sensor const*>(sensor)->getStringType().c_str();
 }
 
 int ASensor_getReportingMode(ASensor const* sensor) {
diff --git a/native/android/storage_manager.cpp b/native/android/storage_manager.cpp
index 294ca9c..6db87df 100644
--- a/native/android/storage_manager.cpp
+++ b/native/android/storage_manager.cpp
@@ -175,7 +175,7 @@
         String16 filename16(filename);
         String16 path16;
         if (mMountService->getMountedObbPath(filename16, path16)) {
-            return String8(path16).string();
+            return String8(path16).c_str();
         } else {
             return NULL;
         }
@@ -183,7 +183,7 @@
 };
 
 void ObbActionListener::onObbResult(const android::String16& filename, const int32_t nonce, const int32_t state) {
-    mStorageManager->fireCallback(String8(filename).string(), nonce, state);
+    mStorageManager->fireCallback(String8(filename).c_str(), nonce, state);
 }
 
 
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 0acce03..ec24ab7 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -954,9 +954,6 @@
     <!-- UI debug setting: enable freeform window support summary [CHAR LIMIT=150] -->
     <string name="enable_freeform_support_summary">Enable support for experimental freeform windows.</string>
 
-    <!-- UI debug setting: enable desktop mode [CHAR LIMIT=25] -->
-    <string name="desktop_mode">Desktop mode</string>
-
     <!-- Local (desktop) backup password menu title [CHAR LIMIT=25] -->
     <string name="local_backup_password_title">Desktop backup password</string>
     <!-- Summary text of the "local backup password" setting when the user has not supplied a password -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
index 5e66972..7669e79b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
@@ -3,7 +3,10 @@
 hughchen@google.com
 timhypeng@google.com
 robertluo@google.com
-changbetty@google.com
 songferngwang@google.com
+yqian@google.com
+chelseahao@google.com
+yiyishen@google.com
+hahong@google.com
 
 # Emergency approvers in case the above are not available
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 92f65d6..c0f6231 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -98,7 +98,6 @@
                     Settings.System.VOLUME_VOICE, // deprecated since API 2?
                     Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
                     Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
-                    Settings.System.DESKTOP_MODE, // developer setting for internal prototyping
                     Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.SCREEN_BRIGHTNESS_FLOAT,
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 8eb012d..6f53b42 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -68,5 +68,6 @@
     kotlincflags: ["-Xjvm-default=all"],
 
     // sdk_version must be specified, otherwise it compiles against private APIs.
+    min_sdk_version: "33",
     sdk_version: "current",
 }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
index d9a45cd..2069ebd 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -19,18 +19,25 @@
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.spring
+import androidx.compose.foundation.gestures.DraggableState
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.draggable
 import androidx.compose.foundation.gestures.rememberDraggableState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
+import com.android.compose.nestedscroll.PriorityPostNestedScrollConnection
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
@@ -70,23 +77,38 @@
     // the same as SwipeableV2Defaults.PositionalThreshold.
     val positionalThreshold = with(LocalDensity.current) { 56.dp.toPx() }
 
-    return draggable(
-        orientation = orientation,
-        enabled = enabled,
-        startDragImmediately = startDragImmediately,
-        onDragStarted = { onDragStarted(layoutImpl, transition, orientation) },
-        state =
-            rememberDraggableState { delta -> onDrag(layoutImpl, transition, orientation, delta) },
-        onDragStopped = { velocity ->
-            onDragStopped(
-                layoutImpl,
-                transition,
-                velocity,
-                velocityThreshold,
-                positionalThreshold,
-            )
-        },
-    )
+    val draggableState = rememberDraggableState { delta ->
+        onDrag(layoutImpl, transition, orientation, delta)
+    }
+
+    return nestedScroll(
+            connection =
+                rememberSwipeToSceneNestedScrollConnection(
+                    orientation = orientation,
+                    coroutineScope = rememberCoroutineScope(),
+                    draggableState = draggableState,
+                    transition = transition,
+                    layoutImpl = layoutImpl,
+                    velocityThreshold = velocityThreshold,
+                    positionalThreshold = positionalThreshold
+                ),
+        )
+        .draggable(
+            state = draggableState,
+            orientation = orientation,
+            enabled = enabled,
+            startDragImmediately = startDragImmediately,
+            onDragStarted = { onDragStarted(layoutImpl, transition, orientation) },
+            onDragStopped = { velocity ->
+                onDragStopped(
+                    layoutImpl = layoutImpl,
+                    transition = transition,
+                    velocity = velocity,
+                    velocityThreshold = velocityThreshold,
+                    positionalThreshold = positionalThreshold,
+                )
+            },
+        )
 }
 
 private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
@@ -235,35 +257,18 @@
     // twice in a row to accelerate the transition and go from A => B then B => C really fast.
     maybeHandleAcceleratedSwipe(transition, orientation)
 
-    val fromScene = transition._fromScene
-    val upOrLeft = fromScene.upOrLeft(orientation)
-    val downOrRight = fromScene.downOrRight(orientation)
     val offset = transition.dragOffset
+    val fromScene = transition._fromScene
 
     // Compute the target scene depending on the current offset.
-    val targetSceneKey: SceneKey
-    val signedDistance: Float
-    when {
-        offset < 0f && upOrLeft != null -> {
-            targetSceneKey = upOrLeft
-            signedDistance = -transition.absoluteDistance
-        }
-        offset > 0f && downOrRight != null -> {
-            targetSceneKey = downOrRight
-            signedDistance = transition.absoluteDistance
-        }
-        else -> {
-            targetSceneKey = fromScene.key
-            signedDistance = 0f
-        }
+    val target = fromScene.findTargetSceneAndDistance(orientation, offset, layoutImpl)
+
+    if (transition._toScene.key != target.sceneKey) {
+        transition._toScene = layoutImpl.scenes.getValue(target.sceneKey)
     }
 
-    if (transition._toScene.key != targetSceneKey) {
-        transition._toScene = layoutImpl.scenes.getValue(targetSceneKey)
-    }
-
-    if (transition._distance != signedDistance) {
-        transition._distance = signedDistance
+    if (transition._distance != target.distance) {
+        transition._distance = target.distance
     }
 }
 
@@ -299,12 +304,55 @@
     // using fromScene and dragOffset.
 }
 
+private data class TargetScene(
+    val sceneKey: SceneKey,
+    val distance: Float,
+)
+
+private fun Scene.findTargetSceneAndDistance(
+    orientation: Orientation,
+    directionOffset: Float,
+    layoutImpl: SceneTransitionLayoutImpl,
+): TargetScene {
+    val maxDistance =
+        when (orientation) {
+            Orientation.Horizontal -> layoutImpl.size.width
+            Orientation.Vertical -> layoutImpl.size.height
+        }.toFloat()
+
+    val upOrLeft = upOrLeft(orientation)
+    val downOrRight = downOrRight(orientation)
+
+    // Compute the target scene depending on the current offset.
+    return when {
+        directionOffset < 0f && upOrLeft != null -> {
+            TargetScene(
+                sceneKey = upOrLeft,
+                distance = -maxDistance,
+            )
+        }
+        directionOffset > 0f && downOrRight != null -> {
+            TargetScene(
+                sceneKey = downOrRight,
+                distance = maxDistance,
+            )
+        }
+        else -> {
+            TargetScene(
+                sceneKey = key,
+                distance = 0f,
+            )
+        }
+    }
+}
+
 private fun CoroutineScope.onDragStopped(
     layoutImpl: SceneTransitionLayoutImpl,
     transition: SwipeTransition,
     velocity: Float,
     velocityThreshold: Float,
     positionalThreshold: Float,
+    canChangeScene: Boolean = true,
 ) {
     // The state was changed since the drag started; don't do anything.
     if (layoutImpl.state.transitionState != transition) {
@@ -323,14 +371,15 @@
     val offset = transition.dragOffset
     val distance = transition.distance
     if (
-        shouldCommitSwipe(
-            offset,
-            distance,
-            velocity,
-            velocityThreshold,
-            positionalThreshold,
-            wasCommitted = transition._currentScene == transition._toScene,
-        )
+        canChangeScene &&
+            shouldCommitSwipe(
+                offset,
+                distance,
+                velocity,
+                velocityThreshold,
+                positionalThreshold,
+                wasCommitted = transition._currentScene == transition._toScene,
+            )
     ) {
         targetOffset = distance
         targetScene = transition._toScene
@@ -348,31 +397,13 @@
         layoutImpl.onChangeScene(targetScene.key)
     }
 
-    // Animate the offset.
-    transition.offsetAnimationJob = launch {
-        transition.offsetAnimatable.snapTo(offset)
-        transition.isAnimatingOffset = true
-
-        transition.offsetAnimatable.animateTo(
-            targetOffset,
-            // TODO(b/290184746): Make this spring spec configurable.
-            spring(
-                stiffness = Spring.StiffnessMediumLow,
-                visibilityThreshold = OffsetVisibilityThreshold
-            ),
-            initialVelocity = velocity,
-        )
-
-        // Now that the animation is done, the state should be idle. Note that if the state was
-        // changed since this animation started, some external code changed it and we shouldn't do
-        // anything here. Note also that this job will be cancelled in the case where the user
-        // intercepts this swipe.
-        if (layoutImpl.state.transitionState == transition) {
-            layoutImpl.state.transitionState = TransitionState.Idle(targetScene.key)
-        }
-
-        transition.offsetAnimationJob = null
-    }
+    animateOffset(
+        transition = transition,
+        layoutImpl = layoutImpl,
+        initialVelocity = velocity,
+        targetOffset = targetOffset,
+        targetScene = targetScene.key
+    )
 }
 
 /**
@@ -412,8 +443,216 @@
     }
 }
 
+private fun CoroutineScope.animateOffset(
+    transition: SwipeTransition,
+    layoutImpl: SceneTransitionLayoutImpl,
+    initialVelocity: Float,
+    targetOffset: Float,
+    targetScene: SceneKey,
+) {
+    transition.offsetAnimationJob = launch {
+        if (!transition.isAnimatingOffset) {
+            transition.offsetAnimatable.snapTo(transition.dragOffset)
+        }
+        transition.isAnimatingOffset = true
+
+        transition.offsetAnimatable.animateTo(
+            targetOffset,
+            // TODO(b/290184746): Make this spring spec configurable.
+            spring(
+                stiffness = Spring.StiffnessMediumLow,
+                visibilityThreshold = OffsetVisibilityThreshold
+            ),
+            initialVelocity = initialVelocity,
+        )
+
+        // Now that the animation is done, the state should be idle. Note that if the state was
+        // changed since this animation started, some external code changed it and we shouldn't do
+        // anything here. Note also that this job will be cancelled in the case where the user
+        // intercepts this swipe.
+        if (layoutImpl.state.transitionState == transition) {
+            layoutImpl.state.transitionState = TransitionState.Idle(targetScene)
+        }
+
+        transition.offsetAnimationJob = null
+    }
+}
+
+private fun CoroutineScope.animateOverscroll(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: SwipeTransition,
+    velocity: Velocity,
+    orientation: Orientation,
+): Velocity {
+    val velocityAmount =
+        when (orientation) {
+            Orientation.Vertical -> velocity.y
+            Orientation.Horizontal -> velocity.x
+        }
+
+    if (velocityAmount == 0f) {
+        // There is no remaining velocity
+        return Velocity.Zero
+    }
+
+    val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+    val target = fromScene.findTargetSceneAndDistance(orientation, velocityAmount, layoutImpl)
+    val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
+
+    if (!isValidTarget || layoutImpl.state.transitionState == transition) {
+        // We have not found a valid target or we are already in a transition
+        return Velocity.Zero
+    }
+
+    transition._currentScene = fromScene
+    transition._fromScene = fromScene
+    transition._toScene = layoutImpl.scene(target.sceneKey)
+    transition._distance = target.distance
+    transition.absoluteDistance = target.distance.absoluteValue
+    transition.dragOffset = 0f
+    transition.isAnimatingOffset = false
+    transition.offsetAnimationJob = null
+
+    layoutImpl.state.transitionState = transition
+
+    animateOffset(
+        transition = transition,
+        layoutImpl = layoutImpl,
+        initialVelocity = velocityAmount,
+        targetOffset = 0f,
+        targetScene = fromScene.key
+    )
+
+    // The animateOffset animation consumes any remaining velocity.
+    return velocity
+}
+
 /**
  * The number of pixels below which there won't be a visible difference in the transition and from
  * which the animation can stop.
  */
 private const val OffsetVisibilityThreshold = 0.5f
+
+@Composable
+private fun rememberSwipeToSceneNestedScrollConnection(
+    orientation: Orientation,
+    coroutineScope: CoroutineScope,
+    draggableState: DraggableState,
+    transition: SwipeTransition,
+    layoutImpl: SceneTransitionLayoutImpl,
+    velocityThreshold: Float,
+    positionalThreshold: Float,
+): PriorityPostNestedScrollConnection {
+    val density = LocalDensity.current
+    val scrollConnection =
+        remember(
+            orientation,
+            coroutineScope,
+            draggableState,
+            transition,
+            layoutImpl,
+            velocityThreshold,
+            positionalThreshold,
+            density,
+        ) {
+            fun Offset.toAmount() =
+                when (orientation) {
+                    Orientation.Horizontal -> x
+                    Orientation.Vertical -> y
+                }
+
+            fun Velocity.toAmount() =
+                when (orientation) {
+                    Orientation.Horizontal -> x
+                    Orientation.Vertical -> y
+                }
+
+            fun Float.toOffset() =
+                when (orientation) {
+                    Orientation.Horizontal -> Offset(x = this, y = 0f)
+                    Orientation.Vertical -> Offset(x = 0f, y = this)
+                }
+
+            // The next potential scene is calculated during the canStart
+            var nextScene: SceneKey? = null
+
+            // This is the scene on which we will have priority during the scroll gesture.
+            var priorityScene: SceneKey? = null
+
+            // If we performed a long gesture before entering priority mode, we would have to avoid
+            // moving on to the next scene.
+            var gestureStartedOnNestedChild = false
+
+            PriorityPostNestedScrollConnection(
+                canStart = { offsetAvailable, offsetBeforeStart ->
+                    val amount = offsetAvailable.toAmount()
+                    if (amount == 0f) return@PriorityPostNestedScrollConnection false
+
+                    gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero
+
+                    val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+                    nextScene =
+                        when {
+                            amount < 0f -> fromScene.upOrLeft(orientation)
+                            amount > 0f -> fromScene.downOrRight(orientation)
+                            else -> null
+                        }
+
+                    nextScene != null
+                },
+                canContinueScroll = { priorityScene == transition._toScene.key },
+                onStart = {
+                    priorityScene = nextScene
+                    onDragStarted(layoutImpl, transition, orientation)
+                },
+                onScroll = { offsetAvailable ->
+                    val amount = offsetAvailable.toAmount()
+
+                    // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture
+                    // is initiated in a nested child.
+
+                    // Appends a new coroutine to attempt to drag by [amount] px. In this case we
+                    // are assuming that the [coroutineScope] is tied to the main thread and that
+                    // calls to [launch] are therefore queued.
+                    coroutineScope.launch { draggableState.drag { dragBy(amount) } }
+
+                    amount.toOffset()
+                },
+                onStop = { velocityAvailable ->
+                    priorityScene = null
+
+                    coroutineScope.onDragStopped(
+                        layoutImpl = layoutImpl,
+                        transition = transition,
+                        velocity = velocityAvailable.toAmount(),
+                        velocityThreshold = velocityThreshold,
+                        positionalThreshold = positionalThreshold,
+                        canChangeScene = !gestureStartedOnNestedChild
+                    )
+
+                    // The onDragStopped animation consumes any remaining velocity.
+                    velocityAvailable
+                },
+                onPostFling = { velocityAvailable ->
+                    // If there is any velocity left, we can try running an overscroll animation
+                    // between scenes.
+                    coroutineScope.animateOverscroll(
+                        layoutImpl = layoutImpl,
+                        transition = transition,
+                        velocity = velocityAvailable,
+                        orientation = orientation
+                    )
+                },
+            )
+        }
+    DisposableEffect(scrollConnection) {
+        onDispose {
+            coroutineScope.launch {
+                // This should ensure that the draggableState is in a consistent state and that it
+                // does not cause any unexpected behavior.
+                scrollConnection.reset()
+            }
+        }
+    }
+    return scrollConnection
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
new file mode 100644
index 0000000..cea8d9a
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.compose.nestedscroll
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+
+/**
+ * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
+ * following way:
+ * - If you **scroll up**, it **first brings the [height]** back to the [minHeight] and then allows
+ *   scrolling of the children (usually the content).
+ * - If you **scroll down**, it **first allows scrolling of the children** (usually the content) and
+ *   then resets the [height] to [maxHeight].
+ *
+ * This behavior is useful for implementing a
+ * [Large top app bar](https://m3.material.io/components/top-app-bar/specs) effect or something
+ * similar.
+ *
+ * @sample com.android.compose.animation.scene.demo.Shade
+ */
+class LargeTopAppBarNestedScrollConnection(
+    private val height: () -> Float,
+    private val onChangeHeight: (Float) -> Unit,
+    private val minHeight: Float,
+    private val maxHeight: Float,
+) : NestedScrollConnection {
+
+    constructor(
+        height: () -> Float,
+        onHeightChanged: (Float) -> Unit,
+        heightRange: ClosedFloatingPointRange<Float>,
+    ) : this(
+        height = height,
+        onChangeHeight = onHeightChanged,
+        minHeight = heightRange.start,
+        maxHeight = heightRange.endInclusive,
+    )
+
+    /**
+     * When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will expand.
+     * Then, you can then scroll down the content.
+     */
+    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+        val y = available.y
+        val currentHeight = height()
+        if (y >= 0 || currentHeight <= minHeight) {
+            return Offset.Zero
+        }
+
+        val amountLeft = minHeight - currentHeight
+        val amountConsumed = y.coerceAtLeast(amountLeft)
+        onChangeHeight(currentHeight + amountConsumed)
+        return Offset(0f, amountConsumed)
+    }
+
+    /**
+     * When swiping down, the content will scroll up until it reaches the top. Then, the
+     * LargeTopAppBar will expand until it reaches its [maxHeight].
+     */
+    override fun onPostScroll(
+        consumed: Offset,
+        available: Offset,
+        source: NestedScrollSource
+    ): Offset {
+        val y = available.y
+        val currentHeight = height()
+        if (y <= 0 || currentHeight >= maxHeight) {
+            return Offset.Zero
+        }
+
+        val amountLeft = maxHeight - currentHeight
+        val amountConsumed = y.coerceAtMost(amountLeft)
+        onChangeHeight(currentHeight + amountConsumed)
+        return Offset(0f, amountConsumed)
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
new file mode 100644
index 0000000..793a9a5
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.compose.nestedscroll
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.unit.Velocity
+
+/**
+ * This [NestedScrollConnection] waits for a child to scroll ([onPostScroll]), and then decides (via
+ * [canStart]) if it should take over scrolling. If it does, it will scroll before its children,
+ * until [canContinueScroll] allows it.
+ *
+ * Note: Call [reset] before destroying this object to make sure you always get a call to [onStop]
+ * after [onStart].
+ *
+ * @sample com.android.compose.animation.scene.rememberSwipeToSceneNestedScrollConnection
+ */
+class PriorityPostNestedScrollConnection(
+    private val canStart: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
+    private val canContinueScroll: () -> Boolean,
+    private val onStart: () -> Unit,
+    private val onScroll: (offsetAvailable: Offset) -> Offset,
+    private val onStop: (velocityAvailable: Velocity) -> Velocity,
+    private val onPostFling: suspend (velocityAvailable: Velocity) -> Velocity,
+) : NestedScrollConnection {
+
+    /** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
+    private var isPriorityMode = false
+
+    private var offsetScrolledBeforePriorityMode = Offset.Zero
+
+    override fun onPostScroll(
+        consumed: Offset,
+        available: Offset,
+        source: NestedScrollSource,
+    ): Offset {
+        // The offset before the start takes into account the up and down movements, starting from
+        // the beginning or from the last fling gesture.
+        val offsetBeforeStart = offsetScrolledBeforePriorityMode - available
+
+        if (
+            isPriorityMode ||
+                source == NestedScrollSource.Fling ||
+                !canStart(available, offsetBeforeStart)
+        ) {
+            // The priority mode cannot start so we won't consume the available offset.
+            return Offset.Zero
+        }
+
+        // Step 1: It's our turn! We start capturing scroll events when one of our children has an
+        // available offset following a scroll event.
+        isPriorityMode = true
+
+        // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
+        // lifted (step 3b), or this object has been destroyed (step 3c).
+        onStart()
+
+        return onScroll(available)
+    }
+
+    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+        if (!isPriorityMode) {
+            if (source != NestedScrollSource.Fling) {
+                // We want to track the amount of offset consumed before entering priority mode
+                offsetScrolledBeforePriorityMode += available
+            }
+
+            return Offset.Zero
+        }
+
+        if (!canContinueScroll()) {
+            // Step 3a: We have lost priority and we no longer need to intercept scroll events.
+            onPriorityStop(velocity = Velocity.Zero)
+            return Offset.Zero
+        }
+
+        // Step 2: We have the priority and can consume the scroll events.
+        return onScroll(available)
+    }
+
+    override suspend fun onPreFling(available: Velocity): Velocity {
+        // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the speed
+        // of the fling gesture.
+        return onPriorityStop(velocity = available)
+    }
+
+    override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+        return onPostFling(available)
+    }
+
+    /** Method to call before destroying the object or to reset the initial state. */
+    fun reset() {
+        // Step 3c: To ensure that an onStop is always called for every onStart.
+        onPriorityStop(velocity = Velocity.Zero)
+    }
+
+    private fun onPriorityStop(velocity: Velocity): Velocity {
+
+        // We can restart tracking the consumed offsets from scratch.
+        offsetScrolledBeforePriorityMode = Offset.Zero
+
+        if (!isPriorityMode) {
+            return Velocity.Zero
+        }
+
+        isPriorityMode = false
+
+        return onStop(velocity)
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
new file mode 100644
index 0000000..03d231a
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.compose.nestedscroll
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class LargeTopAppBarNestedScrollConnectionTest(testCase: TestCase) {
+    val scrollSource = testCase.scrollSource
+
+    private var height = 0f
+
+    private fun buildScrollConnection(heightRange: ClosedFloatingPointRange<Float>) =
+        LargeTopAppBarNestedScrollConnection(
+            height = { height },
+            onHeightChanged = { height = it },
+            heightRange = heightRange,
+        )
+
+    @Test
+    fun onScrollUp_consumeHeightFirst() {
+        val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+        height = 1f
+
+        val offsetConsumed =
+            scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource)
+
+        // It can decrease by 1 the height
+        assertThat(offsetConsumed).isEqualTo(Offset(0f, -1f))
+        assertThat(height).isEqualTo(0f)
+    }
+
+    @Test
+    fun onScrollUp_consumeDownToMin() {
+        val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+        height = 0f
+
+        val offsetConsumed =
+            scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource)
+
+        // It should not change the height (already at min)
+        assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+        assertThat(height).isEqualTo(0f)
+    }
+
+    @Test
+    fun onScrollUp_ignorePostScroll() {
+        val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+        height = 1f
+
+        val offsetConsumed =
+            scrollConnection.onPostScroll(
+                consumed = Offset.Zero,
+                available = Offset(x = 0f, y = -1f),
+                source = scrollSource
+            )
+
+        // It should ignore all onPostScroll events
+        assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+        assertThat(height).isEqualTo(1f)
+    }
+
+    @Test
+    fun onScrollDown_allowConsumeContentFirst() {
+        val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+        height = 1f
+
+        val offsetConsumed =
+            scrollConnection.onPreScroll(available = Offset(x = 0f, y = 1f), source = scrollSource)
+
+        // It should ignore all onPreScroll events
+        assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+        assertThat(height).isEqualTo(1f)
+    }
+
+    @Test
+    fun onScrollDown_consumeHeightPostScroll() {
+        val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+        height = 1f
+
+        val offsetConsumed =
+            scrollConnection.onPostScroll(
+                consumed = Offset.Zero,
+                available = Offset(x = 0f, y = 1f),
+                source = scrollSource
+            )
+
+        // It can increase by 1 the height
+        assertThat(offsetConsumed).isEqualTo(Offset(0f, 1f))
+        assertThat(height).isEqualTo(2f)
+    }
+
+    @Test
+    fun onScrollDown_consumeUpToMax() {
+        val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+        height = 2f
+
+        val offsetConsumed =
+            scrollConnection.onPostScroll(
+                consumed = Offset.Zero,
+                available = Offset(x = 0f, y = 1f),
+                source = scrollSource
+            )
+
+        // It should not change the height (already at max)
+        assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+        assertThat(height).isEqualTo(2f)
+    }
+
+    // NestedScroll Source is a value/inline class and must be wrapped in a parameterized test
+    // https://youtrack.jetbrains.com/issue/KT-35523/Parameterized-JUnit-tests-with-inline-classes-throw-IllegalArgumentException
+    data class TestCase(val scrollSource: NestedScrollSource) {
+        override fun toString() = scrollSource.toString()
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun data(): List<TestCase> =
+            listOf(
+                TestCase(NestedScrollSource.Drag),
+                TestCase(NestedScrollSource.Fling),
+                TestCase(NestedScrollSource.Wheel),
+            )
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
new file mode 100644
index 0000000..8e2b77a
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.compose.nestedscroll
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.unit.Velocity
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class PriorityPostNestedScrollConnectionTest {
+    private var canStart = false
+    private var canContinueScroll = false
+    private var isStarted = false
+    private var lastScroll: Offset? = null
+    private var returnOnScroll = Offset.Zero
+    private var lastStop: Velocity? = null
+    private var returnOnStop = Velocity.Zero
+    private var lastOnPostFling: Velocity? = null
+    private var returnOnPostFling = Velocity.Zero
+
+    private val scrollConnection =
+        PriorityPostNestedScrollConnection(
+            canStart = { _, _ -> canStart },
+            canContinueScroll = { canContinueScroll },
+            onStart = { isStarted = true },
+            onScroll = {
+                lastScroll = it
+                returnOnScroll
+            },
+            onStop = {
+                lastStop = it
+                returnOnStop
+            },
+            onPostFling = {
+                lastOnPostFling = it
+                returnOnPostFling
+            },
+        )
+
+    private val offset1 = Offset(1f, 1f)
+    private val offset2 = Offset(2f, 2f)
+    private val velocity1 = Velocity(1f, 1f)
+    private val velocity2 = Velocity(2f, 2f)
+
+    private fun startPriorityMode() {
+        canStart = true
+        scrollConnection.onPostScroll(
+            consumed = Offset.Zero,
+            available = Offset.Zero,
+            source = NestedScrollSource.Drag
+        )
+    }
+
+    @Test
+    fun step1_priorityModeShouldStartOnlyOnPostScroll() = runTest {
+        canStart = true
+
+        scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+        assertThat(isStarted).isEqualTo(false)
+
+        scrollConnection.onPreFling(available = Velocity.Zero)
+        assertThat(isStarted).isEqualTo(false)
+
+        scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
+        assertThat(isStarted).isEqualTo(false)
+
+        startPriorityMode()
+        assertThat(isStarted).isEqualTo(true)
+    }
+
+    @Test
+    fun step1_priorityModeShouldStartOnlyIfAllowed() {
+        scrollConnection.onPostScroll(
+            consumed = Offset.Zero,
+            available = Offset.Zero,
+            source = NestedScrollSource.Drag
+        )
+        assertThat(isStarted).isEqualTo(false)
+
+        startPriorityMode()
+        assertThat(isStarted).isEqualTo(true)
+    }
+
+    @Test
+    fun step1_onPriorityModeStarted_receiveAvailableOffset() {
+        canStart = true
+
+        scrollConnection.onPostScroll(
+            consumed = offset1,
+            available = offset2,
+            source = NestedScrollSource.Drag
+        )
+
+        assertThat(lastScroll).isEqualTo(offset2)
+    }
+
+    @Test
+    fun step2_onPriorityMode_shouldContinueIfAllowed() {
+        startPriorityMode()
+        canContinueScroll = true
+
+        scrollConnection.onPreScroll(available = offset1, source = NestedScrollSource.Drag)
+        assertThat(lastScroll).isEqualTo(offset1)
+
+        canContinueScroll = false
+        scrollConnection.onPreScroll(available = offset2, source = NestedScrollSource.Drag)
+        assertThat(lastScroll).isNotEqualTo(offset2)
+        assertThat(lastScroll).isEqualTo(offset1)
+    }
+
+    @Test
+    fun step3a_onPriorityMode_shouldStopIfCannotContinue() {
+        startPriorityMode()
+        canContinueScroll = false
+
+        scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+
+        assertThat(lastStop).isNotNull()
+    }
+
+    @Test
+    fun step3b_onPriorityMode_shouldStopOnFling() = runTest {
+        startPriorityMode()
+        canContinueScroll = true
+
+        scrollConnection.onPreFling(available = Velocity.Zero)
+
+        assertThat(lastStop).isNotNull()
+    }
+
+    @Test
+    fun step3c_onPriorityMode_shouldStopOnReset() {
+        startPriorityMode()
+        canContinueScroll = true
+
+        scrollConnection.reset()
+
+        assertThat(lastStop).isNotNull()
+    }
+
+    @Test
+    fun receive_onPostFling() = runTest {
+        scrollConnection.onPostFling(
+            consumed = velocity1,
+            available = velocity2,
+        )
+
+        assertThat(lastOnPostFling).isEqualTo(velocity2)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 7545ff4..8a8557a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -21,7 +21,6 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.LocalTextStyle
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.TextField
@@ -45,7 +44,6 @@
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 
 /** UI for the input part of a password-requiring version of the bouncer. */
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 internal fun PasswordBouncer(
     viewModel: PasswordBouncerViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 64227b8..03efbe0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -21,6 +21,8 @@
 import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.detectDragGestures
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
@@ -199,33 +201,39 @@
             .onSizeChanged { containerSize = it }
             .thenIf(isInputEnabled) {
                 Modifier.pointerInput(Unit) {
-                    detectDragGestures(
-                        onDragStart = { start ->
-                            inputPosition = start
-                            viewModel.onDragStart()
-                        },
-                        onDragEnd = {
-                            inputPosition = null
-                            if (isAnimationEnabled) {
-                                lineFadeOutAnimatables.values.forEach { animatable ->
-                                    // Launch using the longer-lived scope because we want these
-                                    // animations to proceed to completion even if the surrounding
-                                    // scope is canceled.
-                                    scope.launch { animatable.animateTo(1f) }
-                                }
-                            }
-                            viewModel.onDragEnd()
-                        },
-                    ) { change, _ ->
-                        inputPosition = change.position
-                        viewModel.onDrag(
-                            xPx = change.position.x,
-                            yPx = change.position.y,
-                            containerSizePx = containerSize.width,
-                            verticalOffsetPx = verticalOffset,
-                        )
+                        awaitEachGesture {
+                            awaitFirstDown()
+                            viewModel.onDown()
+                        }
                     }
-                }
+                    .pointerInput(Unit) {
+                        detectDragGestures(
+                            onDragStart = { start ->
+                                inputPosition = start
+                                viewModel.onDragStart()
+                            },
+                            onDragEnd = {
+                                inputPosition = null
+                                if (isAnimationEnabled) {
+                                    lineFadeOutAnimatables.values.forEach { animatable ->
+                                        // Launch using the longer-lived scope because we want these
+                                        // animations to proceed to completion even if the
+                                        // surrounding scope is canceled.
+                                        scope.launch { animatable.animateTo(1f) }
+                                    }
+                                }
+                                viewModel.onDragEnd()
+                            },
+                        ) { change, _ ->
+                            inputPosition = change.position
+                            viewModel.onDrag(
+                                xPx = change.position.x,
+                                yPx = change.position.y,
+                                containerSizePx = containerSize.width,
+                                verticalOffsetPx = verticalOffset,
+                            )
+                        }
+                    }
             }
     ) {
         if (isAnimationEnabled) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index ec6e5ed..e5c6977 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -24,6 +24,8 @@
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -76,7 +78,13 @@
 
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
-        modifier = modifier,
+        modifier =
+            modifier.pointerInput(Unit) {
+                awaitEachGesture {
+                    awaitFirstDown()
+                    viewModel.onDown()
+                }
+            }
     ) {
         PinInputDisplay(viewModel)
         Spacer(Modifier.height(100.dp))
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
index 48f40e7..418df5c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
@@ -19,13 +19,11 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.LocalContentColor
@@ -35,9 +33,13 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import com.android.compose.theme.LocalAndroidColorScheme
+import kotlin.math.roundToInt
 
 /**
  * The content of an AlertDialog which can be used together with
@@ -99,28 +101,101 @@
         Spacer(Modifier.height(32.dp))
 
         // Buttons.
-        // TODO(b/283817398): If there is not enough space, the buttons should automatically stack
-        // as shown in go/sysui-dialog-styling.
         if (positiveButton != null || negativeButton != null || neutralButton != null) {
-            Row(Modifier.fillMaxWidth()) {
-                if (neutralButton != null) {
-                    neutralButton()
-                    Spacer(Modifier.width(8.dp))
-                }
+            AlertDialogButtons(
+                positiveButton = positiveButton,
+                negativeButton = negativeButton,
+                neutralButton = neutralButton,
+            )
+        }
+    }
+}
 
-                Spacer(Modifier.weight(1f))
+@Composable
+private fun AlertDialogButtons(
+    positiveButton: (@Composable () -> Unit)?,
+    negativeButton: (@Composable () -> Unit)?,
+    neutralButton: (@Composable () -> Unit)?,
+    modifier: Modifier = Modifier,
+) {
+    Layout(
+        content = {
+            positiveButton?.let { Box(Modifier.layoutId("positive")) { it() } }
+            negativeButton?.let { Box(Modifier.layoutId("negative")) { it() } }
+            neutralButton?.let { Box(Modifier.layoutId("neutral")) { it() } }
+        },
+        modifier,
+    ) { measurables, constraints ->
+        check(constraints.hasBoundedWidth) {
+            "AlertDialogButtons should not be composed in an horizontally scrollable layout"
+        }
+        val maxWidth = constraints.maxWidth
 
-                if (negativeButton != null) {
-                    negativeButton()
-                }
+        // Measure the buttons.
+        var positive: Placeable? = null
+        var negative: Placeable? = null
+        var neutral: Placeable? = null
+        for (i in measurables.indices) {
+            val measurable = measurables[i]
+            when (val layoutId = measurable.layoutId) {
+                "positive" -> positive = measurable.measure(constraints)
+                "negative" -> negative = measurable.measure(constraints)
+                "neutral" -> neutral = measurable.measure(constraints)
+                else -> error("Unexpected layoutId=$layoutId")
+            }
+        }
 
-                if (positiveButton != null) {
-                    if (negativeButton != null) {
-                        Spacer(Modifier.width(8.dp))
+        fun Placeable?.width() = this?.width ?: 0
+        fun Placeable?.height() = this?.height ?: 0
+
+        // The min horizontal spacing between buttons.
+        val horizontalSpacing = 8.dp.toPx()
+        val totalHorizontalSpacing = (measurables.size - 1) * horizontalSpacing
+        val requiredWidth =
+            positive.width() + negative.width() + neutral.width() + totalHorizontalSpacing
+
+        if (requiredWidth <= maxWidth) {
+            // Stack horizontally: [neutral][flexSpace][negative][positive].
+            val height = maxOf(positive.height(), negative.height(), neutral.height())
+            layout(maxWidth, height) {
+                positive?.let { it.placeRelative(maxWidth - it.width, 0) }
+
+                negative?.let { negative ->
+                    if (positive == null) {
+                        negative.placeRelative(maxWidth - negative.width, 0)
+                    } else {
+                        negative.placeRelative(
+                            maxWidth -
+                                negative.width -
+                                positive.width -
+                                horizontalSpacing.roundToInt(),
+                            0
+                        )
                     }
-
-                    positiveButton()
                 }
+
+                neutral?.placeRelative(0, 0)
+            }
+        } else {
+            // Stack vertically, aligned on the right (in LTR layouts):
+            //   [positive]
+            //   [negative]
+            //    [neutral]
+            //
+            // TODO(b/283817398): Introduce a ResponsiveDialogButtons composable to create buttons
+            // that have different styles when stacked horizontally, as shown in
+            // go/sysui-dialog-styling.
+            val height = positive.height() + negative.height() + neutral.height()
+            layout(maxWidth, height) {
+                var y = 0
+                fun Placeable.place() {
+                    placeRelative(maxWidth - width, y)
+                    y += this.height
+                }
+
+                positive?.place()
+                negative?.place()
+                neutral?.place()
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 0a100ba..463253b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -14,20 +14,33 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
+@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
 
 package com.android.systemui.keyguard.ui.composable
 
 import android.view.View
 import android.view.ViewGroup
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.qualifiers.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
@@ -67,8 +80,8 @@
         modifier: Modifier,
     ) {
         LockscreenScene(
-            viewModel = viewModel,
             viewProvider = viewProvider,
+            longPressViewModel = viewModel.longPress,
             modifier = modifier,
         )
     }
@@ -85,23 +98,68 @@
 
 @Composable
 private fun LockscreenScene(
-    viewModel: LockscreenSceneViewModel,
     viewProvider: () -> View,
+    longPressViewModel: KeyguardLongPressViewModel,
     modifier: Modifier = Modifier,
 ) {
-    AndroidView(
-        factory = { _ ->
-            val keyguardRootView = viewProvider()
-            // Remove the KeyguardRootView from any parent it might already have in legacy code just
-            // in case (a view can't have two parents).
-            (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
-            keyguardRootView
-        },
-        update = { keyguardRootView ->
-            keyguardRootView.requireViewById<View>(R.id.lock_icon_view).setOnClickListener {
-                viewModel.onLockButtonClicked()
-            }
-        },
+    fun findSettingsMenu(): View {
+        return viewProvider().requireViewById(R.id.keyguard_settings_button)
+    }
+
+    Box(
         modifier = modifier,
+    ) {
+        LongPressSurface(
+            viewModel = longPressViewModel,
+            isSettingsMenuVisible = { findSettingsMenu().isVisible },
+            settingsMenuBounds = {
+                val bounds = android.graphics.Rect()
+                findSettingsMenu().getHitRect(bounds)
+                bounds.toComposeRect()
+            },
+            modifier = Modifier.fillMaxSize(),
+        )
+
+        AndroidView(
+            factory = { _ ->
+                val keyguardRootView = viewProvider()
+                // Remove the KeyguardRootView from any parent it might already have in legacy code
+                // just in case (a view can't have two parents).
+                (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
+                keyguardRootView
+            },
+            modifier = Modifier.fillMaxSize(),
+        )
+    }
+}
+
+@Composable
+private fun LongPressSurface(
+    viewModel: KeyguardLongPressViewModel,
+    isSettingsMenuVisible: () -> Boolean,
+    settingsMenuBounds: () -> Rect,
+    modifier: Modifier = Modifier,
+) {
+    val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
+
+    Box(
+        modifier =
+            modifier
+                .combinedClickable(
+                    enabled = isEnabled,
+                    onLongClick = viewModel::onLongPress,
+                    onClick = {},
+                )
+                .pointerInput(Unit) {
+                    awaitEachGesture {
+                        val pointerInputChange = awaitFirstDown()
+                        if (
+                            isSettingsMenuVisible() &&
+                                !settingsMenuBounds().contains(pointerInputChange.position)
+                        ) {
+                            viewModel.onTouchedOutside()
+                        }
+                    }
+                },
     )
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 774c409..40b0b4a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -17,11 +17,7 @@
 package com.android.systemui.scene.ui.composable
 
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
@@ -54,17 +50,6 @@
     override fun SceneScope.Content(
         modifier: Modifier,
     ) {
-        /*
-         * TODO(b/279501596): once we start testing with the real Content Dynamics Framework,
-         *  replace this with an error to make sure it doesn't get rendered.
-         */
-        Box(modifier = modifier) {
-            Column(
-                horizontalAlignment = Alignment.CenterHorizontally,
-                modifier = Modifier.align(Alignment.Center)
-            ) {
-                Text("Gone", style = MaterialTheme.typography.headlineMedium)
-            }
-        }
+        Box(modifier = modifier)
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index ffb20d8..31cbcb9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalComposeUiApi::class)
+
 package com.android.systemui.scene.ui.composable
 
 import androidx.compose.foundation.layout.fillMaxSize
@@ -25,6 +27,9 @@
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.pointer.motionEventSpy
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.motionEventSpy
+import androidx.compose.ui.input.pointer.pointerInput
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.ObservableTransitionState as SceneTransitionObservableTransitionState
 import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
@@ -82,7 +87,18 @@
         onChangeScene = viewModel::onSceneChanged,
         transitions = SceneContainerTransitions,
         state = state,
-        modifier = modifier.fillMaxSize().motionEventSpy { viewModel.onUserInput() },
+        modifier =
+            modifier
+                .fillMaxSize()
+                .motionEventSpy { event -> viewModel.onMotionEvent(event) }
+                .pointerInput(Unit) {
+                    awaitPointerEventScope {
+                        while (true) {
+                            awaitPointerEvent(PointerEventPass.Final)
+                            viewModel.onMotionEventComplete()
+                        }
+                    }
+                }
     ) {
         sceneByKey.forEach { (sceneKey, composableScene) ->
             scene(
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 966e183..054f9ec 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -68,11 +68,11 @@
 private fun <TKey : Any, TVal : Any> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut(
     key: TKey,
     value: TVal,
-    onNew: () -> Unit
+    onNew: (TVal) -> Unit
 ): TVal {
     val result = this.putIfAbsent(key, value)
     if (result == null) {
-        onNew()
+        onNew(value)
     }
     return result ?: value
 }
@@ -148,6 +148,8 @@
             override fun onPluginAttached(
                 manager: PluginLifecycleManager<ClockProviderPlugin>
             ): Boolean {
+                manager.isDebug = true
+
                 if (keepAllLoaded) {
                     // Always load new plugins if requested
                     return true
@@ -177,15 +179,22 @@
                     val info =
                         availableClocks.concurrentGetOrPut(id, ClockInfo(metadata, null, manager)) {
                             isClockListChanged = true
-                            onConnected(id)
+                            onConnected(it)
                         }
 
                     if (manager != info.manager) {
                         logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
-                            { str1 = id },
-                            { "Clock Id conflict on known attach: $str1 is double registered" }
+                            {
+                                str1 = id
+                                str2 = info.manager.toString()
+                                str3 = manager.toString()
+                            },
+                            {
+                                "Clock Id conflict on attach: " +
+                                    "$str1 is double registered by $str2 and $str3"
+                            }
                         )
                         continue
                     }
@@ -217,22 +226,29 @@
                     val info =
                         availableClocks.concurrentGetOrPut(id, ClockInfo(clock, plugin, manager)) {
                             isClockListChanged = true
-                            onConnected(id)
+                            onConnected(it)
                         }
 
                     if (manager != info.manager) {
                         logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
-                            { str1 = id },
-                            { "Clock Id conflict on load: $str1 is double registered" }
+                            {
+                                str1 = id
+                                str2 = info.manager.toString()
+                                str3 = manager.toString()
+                            },
+                            {
+                                "Clock Id conflict on load: " +
+                                    "$str1 is double registered by $str2 and $str3"
+                            }
                         )
                         manager.unloadPlugin()
                         continue
                     }
 
                     info.provider = plugin
-                    onLoaded(id)
+                    onLoaded(info)
                 }
 
                 if (isClockListChanged) {
@@ -252,26 +268,33 @@
                         logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
-                            { str1 = id },
-                            { "Clock Id conflict on unload: $str1 is double registered" }
+                            {
+                                str1 = id
+                                str2 = info?.manager.toString()
+                                str3 = manager.toString()
+                            },
+                            {
+                                "Clock Id conflict on unload: " +
+                                    "$str1 is double registered by $str2 and $str3"
+                            }
                         )
                         continue
                     }
                     info.provider = null
-                    onUnloaded(id)
+                    onUnloaded(info)
                 }
 
                 verifyLoadedProviders()
             }
 
             override fun onPluginDetached(manager: PluginLifecycleManager<ClockProviderPlugin>) {
-                val removed = mutableListOf<ClockId>()
+                val removed = mutableListOf<ClockInfo>()
                 availableClocks.entries.removeAll {
                     if (it.value.manager != manager) {
                         return@removeAll false
                     }
 
-                    removed.add(it.key)
+                    removed.add(it.value)
                     return@removeAll true
                 }
 
@@ -493,6 +516,12 @@
 
         scope.launch(bgDispatcher) {
             if (keepAllLoaded) {
+                logger.tryLog(
+                    TAG,
+                    LogLevel.INFO,
+                    {},
+                    { "verifyLoadedProviders: keepAllLoaded=true" }
+                )
                 // Enforce that all plugins are loaded if requested
                 for ((_, info) in availableClocks) {
                     info.manager?.loadPlugin()
@@ -503,6 +532,12 @@
 
             val currentClock = availableClocks[currentClockId]
             if (currentClock == null) {
+                logger.tryLog(
+                    TAG,
+                    LogLevel.INFO,
+                    {},
+                    { "verifyLoadedProviders: currentClock=null" }
+                )
                 // Current Clock missing, load no plugins and use default
                 for ((_, info) in availableClocks) {
                     info.manager?.unloadPlugin()
@@ -511,12 +546,13 @@
                 return@launch
             }
 
+            logger.tryLog(TAG, LogLevel.INFO, {}, { "verifyLoadedProviders: load currentClock" })
             val currentManager = currentClock.manager
             currentManager?.loadPlugin()
 
             for ((_, info) in availableClocks) {
                 val manager = info.manager
-                if (manager != null && manager.isLoaded && currentManager != manager) {
+                if (manager != null && currentManager != manager) {
                     manager.unloadPlugin()
                 }
             }
@@ -524,57 +560,68 @@
         }
     }
 
-    private fun onConnected(clockId: ClockId) {
-        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" })
-        if (currentClockId == clockId) {
-            logger.tryLog(
-                TAG,
-                LogLevel.INFO,
-                { str1 = clockId },
-                { "Current clock ($str1) was connected" }
-            )
-        }
+    private fun onConnected(info: ClockInfo) {
+        val isCurrent = currentClockId == info.metadata.clockId
+        logger.tryLog(
+            TAG,
+            if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
+            {
+                str1 = info.metadata.clockId
+                str2 = info.manager.toString()
+                bool1 = isCurrent
+            },
+            { "Connected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+        )
     }
 
-    private fun onLoaded(clockId: ClockId) {
-        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" })
+    private fun onLoaded(info: ClockInfo) {
+        val isCurrent = currentClockId == info.metadata.clockId
+        logger.tryLog(
+            TAG,
+            if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
+            {
+                str1 = info.metadata.clockId
+                str2 = info.manager.toString()
+                bool1 = isCurrent
+            },
+            { "Loaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+        )
 
-        if (currentClockId == clockId) {
-            logger.tryLog(
-                TAG,
-                LogLevel.INFO,
-                { str1 = clockId },
-                { "Current clock ($str1) was loaded" }
-            )
+        if (isCurrent) {
             triggerOnCurrentClockChanged()
         }
     }
 
-    private fun onUnloaded(clockId: ClockId) {
-        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" })
+    private fun onUnloaded(info: ClockInfo) {
+        val isCurrent = currentClockId == info.metadata.clockId
+        logger.tryLog(
+            TAG,
+            if (isCurrent) LogLevel.WARNING else LogLevel.DEBUG,
+            {
+                str1 = info.metadata.clockId
+                str2 = info.manager.toString()
+                bool1 = isCurrent
+            },
+            { "Unloaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+        )
 
-        if (currentClockId == clockId) {
-            logger.tryLog(
-                TAG,
-                LogLevel.WARNING,
-                { str1 = clockId },
-                { "Current clock ($str1) was unloaded" }
-            )
+        if (isCurrent) {
             triggerOnCurrentClockChanged()
         }
     }
 
-    private fun onDisconnected(clockId: ClockId) {
-        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" })
-
-        if (currentClockId == clockId) {
-            logger.tryLog(
-                TAG,
-                LogLevel.WARNING,
-                { str1 = clockId },
-                { "Current clock ($str1) was disconnected" }
-            )
-        }
+    private fun onDisconnected(info: ClockInfo) {
+        val isCurrent = currentClockId == info.metadata.clockId
+        logger.tryLog(
+            TAG,
+            if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
+            {
+                str1 = info.metadata.clockId
+                str2 = info.manager.toString()
+                bool1 = isCurrent
+            },
+            { "Disconnected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+        )
     }
 
     fun getClocks(): List<ClockMetadata> {
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
index 56c3f93..3e5e8a0 100644
--- a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
@@ -33,6 +33,12 @@
     /** Returns the currently loaded plugin instance (if plugin is loaded) */
     T getPlugin();
 
+    /** Returns true if the lifecycle manager should log debug messages */
+    boolean getIsDebug();
+
+    /** Sets whether or not hte lifecycle manager should log debug messages */
+    void setIsDebug(boolean debug);
+
     /** returns true if the plugin is currently loaded */
     default boolean isLoaded() {
         return getPlugin() != null;
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index 3194815..75de943 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -70,9 +70,6 @@
 -keep class com.android.systemui.log.core.** {
     *;
 }
--keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
-    *;
-}
 -keep class androidx.core.app.CoreComponentFactory
 
 # Keep the wm shell lib
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index ca30e15..a6517c1 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -50,6 +50,7 @@
         "SystemUIAnimationLib",
         "SystemUIPluginLib",
         "SystemUIUnfoldLib",
+        "SystemUISharedLib-Keyguard",
         "androidx.dynamicanimation_dynamicanimation",
         "androidx.concurrent_concurrent-futures",
         "androidx.lifecycle_lifecycle-runtime-ktx",
diff --git a/packages/SystemUI/shared/keyguard/Android.bp b/packages/SystemUI/shared/keyguard/Android.bp
new file mode 100644
index 0000000..2181439
--- /dev/null
+++ b/packages/SystemUI/shared/keyguard/Android.bp
@@ -0,0 +1,16 @@
+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_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+    name: "SystemUISharedLib-Keyguard",
+    srcs: [
+        "src/**/*.java",
+    ],
+    min_sdk_version: "current",
+}
diff --git a/packages/SystemUI/shared/keyguard/AndroidManifest.xml b/packages/SystemUI/shared/keyguard/AndroidManifest.xml
new file mode 100644
index 0000000..49bee08
--- /dev/null
+++ b/packages/SystemUI/shared/keyguard/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.systemui.shared.keyguard">
+</manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java
new file mode 100644
index 0000000..fe12134
--- /dev/null
+++ b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java
@@ -0,0 +1,239 @@
+/*
+ * 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.keyguard;
+
+import android.annotation.CallSuper;
+import android.content.Context;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+
+/**
+ * A View similar to a textView which contains password text and can animate when the text is
+ * changed
+ */
+public abstract class BasePasswordTextView extends FrameLayout {
+    private String mText = "";
+    private UserActivityListener mUserActivityListener;
+    protected boolean mIsPinHinting;
+    protected PinShapeInput mPinShapeInput;
+    protected boolean mShowPassword = true;
+    protected boolean mUsePinShapes = false;
+    protected static final char DOT = '\u2022';
+
+    /** Listens to user activities like appending, deleting and resetting PIN text */
+    public interface UserActivityListener {
+
+        /** Listens to user activities. */
+        void onUserActivity();
+    }
+
+    public BasePasswordTextView(Context context) {
+        this(context, null);
+    }
+
+    public BasePasswordTextView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BasePasswordTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public BasePasswordTextView(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    protected abstract PinShapeInput inflatePinShapeInput(boolean isPinHinting);
+
+    protected abstract boolean shouldSendAccessibilityEvent();
+
+    protected void onAppend(char c, int newLength) {}
+
+    protected void onDelete(int index) {}
+
+    protected void onReset(boolean animated) {}
+
+    @CallSuper
+    protected void onUserActivity() {
+        if (mUserActivityListener != null) {
+            mUserActivityListener.onUserActivity();
+        }
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
+    /** Appends a PIN text */
+    public void append(char c) {
+        CharSequence textbefore = getTransformedText();
+
+        mText = mText + c;
+        int newLength = mText.length();
+        onAppend(c, newLength);
+
+        if (mPinShapeInput != null) {
+            mPinShapeInput.append();
+        }
+
+        onUserActivity();
+
+        sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length(), 0, 1);
+    }
+
+    /** Sets a listener who is notified on user activity */
+    public void setUserActivityListener(UserActivityListener userActivityListener) {
+        mUserActivityListener = userActivityListener;
+    }
+
+    /** Deletes the last PIN text */
+    public void deleteLastChar() {
+        int length = mText.length();
+        if (length > 0) {
+            CharSequence textbefore = getTransformedText();
+
+            mText = mText.substring(0, length - 1);
+            onDelete(length - 1);
+
+            if (mPinShapeInput != null) {
+                mPinShapeInput.delete();
+            }
+
+            sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length() - 1, 1, 0);
+        }
+        onUserActivity();
+    }
+
+    /** Gets entered PIN text */
+    public String getText() {
+        return mText;
+    }
+
+    /** Gets a transformed text for accessibility event. Called before text changed. */
+    protected CharSequence getTransformedText() {
+        return String.valueOf(DOT).repeat(mText.length());
+    }
+
+    /** Gets a transformed text for accessibility event. Called after text changed. */
+    protected CharSequence getTransformedText(int fromIndex, int removedCount, int addedCount) {
+        return getTransformedText();
+    }
+
+    /** Reset PIN text without error */
+    public void reset(boolean animated, boolean announce) {
+        reset(false /* error */, animated, announce);
+    }
+
+    /** Reset PIN text */
+    public void reset(boolean error, boolean animated, boolean announce) {
+        CharSequence textbefore = getTransformedText();
+
+        mText = "";
+
+        onReset(animated);
+        if (animated) {
+            onUserActivity();
+        }
+
+        if (mPinShapeInput != null) {
+            if (error) {
+                mPinShapeInput.resetWithError();
+            } else {
+                mPinShapeInput.reset();
+            }
+        }
+
+        if (announce) {
+            sendAccessibilityEventTypeViewTextChanged(textbefore, 0, textbefore.length(), 0);
+        }
+    }
+
+    void sendAccessibilityEventTypeViewTextChanged(
+            CharSequence beforeText, int fromIndex, int removedCount, int addedCount) {
+        if (AccessibilityManager.getInstance(mContext).isEnabled()
+                && shouldSendAccessibilityEvent()) {
+            AccessibilityEvent event =
+                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
+            event.setFromIndex(fromIndex);
+            event.setRemovedCount(removedCount);
+            event.setAddedCount(addedCount);
+            event.setBeforeText(beforeText);
+            CharSequence transformedText = getTransformedText(fromIndex, removedCount, addedCount);
+            if (!TextUtils.isEmpty(transformedText)) {
+                event.getText().add(transformedText);
+            }
+            event.setPassword(true);
+            sendAccessibilityEventUnchecked(event);
+        }
+    }
+
+    /** Sets whether to use pin shapes. */
+    public void setUsePinShapes(boolean usePinShapes) {
+        mUsePinShapes = usePinShapes;
+    }
+
+    /** Determines whether AutoConfirmation feature is on. */
+    public void setIsPinHinting(boolean isPinHinting) {
+        // Do not reinflate the view if we are using the same one.
+        if (mPinShapeInput != null && mIsPinHinting == isPinHinting) {
+            return;
+        }
+        mIsPinHinting = isPinHinting;
+
+        if (mPinShapeInput != null) {
+            removeView(mPinShapeInput.getView());
+            mPinShapeInput = null;
+        }
+
+        mPinShapeInput = inflatePinShapeInput(isPinHinting);
+        addView(mPinShapeInput.getView());
+    }
+
+    /** Controls whether the last entered digit is briefly shown after being entered */
+    public void setShowPassword(boolean enabled) {
+        mShowPassword = enabled;
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+
+        event.setClassName(EditText.class.getName());
+        event.setPassword(true);
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+
+        info.setClassName(EditText.class.getName());
+        info.setPassword(true);
+        info.setText(getTransformedText());
+
+        info.setEditable(true);
+
+        info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeInput.java b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/PinShapeInput.java
similarity index 85%
rename from packages/SystemUI/src/com/android/keyguard/PinShapeInput.java
rename to packages/SystemUI/shared/keyguard/src/com/android/keyguard/PinShapeInput.java
index 52ae6ba..d2b4066 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeInput.java
+++ b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/PinShapeInput.java
@@ -44,6 +44,14 @@
     void reset();
 
     /**
+     * This is the method that is triggered for resetting the view with error If it doesn't have to
+     * show something regarding error, just reset
+     */
+    default void resetWithError() {
+        reset();
+    }
+
+    /**
      * This is the method that is triggered for getting the view
      */
     View getView();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 6b67c09..7719e95 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -55,7 +55,9 @@
     private final PluginListener<T> mListener;
     private final ComponentName mComponentName;
     private final PluginFactory<T> mPluginFactory;
+    private final String mTag;
 
+    private boolean mIsDebug = false;
     private Context mPluginContext;
     private T mPlugin;
 
@@ -71,27 +73,51 @@
         mComponentName = componentName;
         mPluginFactory = pluginFactory;
         mPlugin = plugin;
+        mTag = TAG + mComponentName.toShortString()
+                + '@' + Integer.toHexString(hashCode());
 
         if (mPlugin != null) {
             mPluginContext = mPluginFactory.createPluginContext();
         }
     }
 
+    @Override
+    public String toString() {
+        return mTag;
+    }
+
+    public boolean getIsDebug() {
+        return mIsDebug;
+    }
+
+    public void setIsDebug(boolean debug) {
+        mIsDebug = debug;
+    }
+
+    private void logDebug(String message) {
+        if (mIsDebug) {
+            Log.i(mTag, message);
+        }
+    }
+
     /** Alerts listener and plugin that the plugin has been created. */
     public void onCreate() {
         boolean loadPlugin = mListener.onPluginAttached(this);
         if (!loadPlugin) {
             if (mPlugin != null) {
+                logDebug("onCreate: auto-unload");
                 unloadPlugin();
             }
             return;
         }
 
         if (mPlugin == null) {
+            logDebug("onCreate auto-load");
             loadPlugin();
             return;
         }
 
+        logDebug("onCreate: load callbacks");
         mPluginFactory.checkVersion(mPlugin);
         if (!(mPlugin instanceof PluginFragment)) {
             // Only call onCreate for plugins that aren't fragments, as fragments
@@ -103,6 +129,7 @@
 
     /** Alerts listener and plugin that the plugin is being shutdown. */
     public void onDestroy() {
+        logDebug("onDestroy");
         unloadPlugin();
         mListener.onPluginDetached(this);
     }
@@ -118,15 +145,18 @@
      */
     public void loadPlugin() {
         if (mPlugin != null) {
+            logDebug("Load request when already loaded");
             return;
         }
 
         mPlugin = mPluginFactory.createPlugin();
         mPluginContext = mPluginFactory.createPluginContext();
         if (mPlugin == null || mPluginContext == null) {
+            Log.e(mTag, "Requested load, but failed");
             return;
         }
 
+        logDebug("Loaded plugin; running callbacks");
         mPluginFactory.checkVersion(mPlugin);
         if (!(mPlugin instanceof PluginFragment)) {
             // Only call onCreate for plugins that aren't fragments, as fragments
@@ -143,9 +173,11 @@
      */
     public void unloadPlugin() {
         if (mPlugin == null) {
+            logDebug("Unload request when already unloaded");
             return;
         }
 
+        logDebug("Unloading plugin, running callbacks");
         mListener.onPluginUnloaded(mPlugin, this);
         if (!(mPlugin instanceof PluginFragment)) {
             // Only call onDestroy for plugins that aren't fragments, as fragments
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 06b6692..1703b30 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -48,6 +48,7 @@
     private static final long STATUS_AREA_MOVE_UP_MILLIS = 967;
     private static final long STATUS_AREA_MOVE_DOWN_MILLIS = 467;
     private static final float SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER = 1.4f;
+    private static final float SMARTSPACE_TOP_PADDING_MULTIPLIER = 2.625f;
 
     @IntDef({LARGE, SMALL})
     @Retention(RetentionPolicy.SOURCE)
@@ -96,6 +97,14 @@
     private KeyguardClockFrame mLargeClockFrame;
     private ClockController mClock;
 
+    // It's bc_smartspace_view, assigned by KeyguardClockSwitchController
+    // to get the top padding for translating smartspace for weather clock
+    private View mSmartspace;
+
+    // Smartspace in weather clock is translated by this value
+    // to compensate for the position invisible dateWeatherView
+    private int mSmartspaceTop = -1;
+
     private KeyguardStatusAreaView mStatusArea;
     private int mSmartspaceTopOffset;
     private float mWeatherClockSmartspaceScaling = 1f;
@@ -134,8 +143,11 @@
     public void onConfigChanged() {
         mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
                 R.dimen.keyguard_clock_switch_y_shift);
-        mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
-                R.dimen.keyguard_smartspace_top_offset);
+        mSmartspaceTopOffset = (int) (mContext.getResources().getDimensionPixelSize(
+                        R.dimen.keyguard_smartspace_top_offset)
+                * mContext.getResources().getConfiguration().fontScale
+                / mContext.getResources().getDisplayMetrics().density
+                * SMARTSPACE_TOP_PADDING_MULTIPLIER);
         mWeatherClockSmartspaceScaling = ResourcesCompat.getFloat(
                 mContext.getResources(), R.dimen.weather_clock_smartspace_scale);
         mWeatherClockSmartspaceTranslateX = mContext.getResources().getDimensionPixelSize(
@@ -145,6 +157,12 @@
         updateStatusArea(/* animate= */false);
     }
 
+    /** Get bc_smartspace_view from KeyguardClockSwitchController
+     * Use its top to decide the translation value */
+    public void setSmartspace(View smartspace) {
+        mSmartspace = smartspace;
+    }
+
     /** Sets whether the large clock is being shown on a connected display. */
     public void setLargeClockOnSecondaryDisplay(boolean onSecondaryDisplay) {
         if (mClock != null) {
@@ -295,7 +313,7 @@
                     && mClock.getLargeClock().getConfig().getHasCustomWeatherDataDisplay()) {
                 statusAreaClockScale = mWeatherClockSmartspaceScaling;
                 statusAreaClockTranslateX = mWeatherClockSmartspaceTranslateX;
-                statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY;
+                statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY - mSmartspaceTop;
                 if (mSplitShadeCentered) {
                     statusAreaClockTranslateX *= SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER;
                 }
@@ -418,10 +436,14 @@
             post(() -> updateClockTargetRegions());
         }
 
-        if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
+        if (mSmartspace != null && mSmartspaceTop != mSmartspace.getTop()) {
+            mSmartspaceTop = mSmartspace.getTop();
             post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
         }
 
+        if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
+            post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
+        }
         mChildrenAreLaidOut = true;
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 6d2880e..d897960 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -372,6 +372,7 @@
         mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
 
         mKeyguardUnlockAnimationController.setLockscreenSmartspace(mSmartspaceView);
+        mView.setSmartspace(mSmartspaceView);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 8e8ee48..9a2ffe0 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.keyguard;
@@ -30,18 +30,11 @@
 import android.graphics.Typeface;
 import android.os.PowerManager;
 import android.os.SystemClock;
-import android.text.InputType;
-import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.LayoutInflater;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
-import android.widget.EditText;
-import android.widget.FrameLayout;
 
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
@@ -52,12 +45,11 @@
  * A View similar to a textView which contains password text and can animate when the text is
  * changed
  */
-public class PasswordTextView extends FrameLayout {
-
-    private static final float DOT_OVERSHOOT_FACTOR = 1.5f;
-    private static final long DOT_APPEAR_DURATION_OVERSHOOT = 320;
+public class PasswordTextView extends BasePasswordTextView {
     public static final long APPEAR_DURATION = 160;
     public static final long DISAPPEAR_DURATION = 160;
+    private static final float DOT_OVERSHOOT_FACTOR = 1.5f;
+    private static final long DOT_APPEAR_DURATION_OVERSHOOT = 320;
     private static final long RESET_DELAY_PER_ELEMENT = 40;
     private static final long RESET_MAX_DELAY = 200;
 
@@ -82,15 +74,12 @@
      */
     private static final float OVERSHOOT_TIME_POSITION = 0.5f;
 
-    private static char DOT = '\u2022';
-
     /**
      * The raw text size, will be multiplied by the scaled density when drawn
      */
     private int mTextHeightRaw;
     private final int mGravity;
     private ArrayList<CharState> mTextChars = new ArrayList<>();
-    private String mText = "";
     private int mDotSize;
     private PowerManager mPM;
     private int mCharPadding;
@@ -99,15 +88,6 @@
     private Interpolator mAppearInterpolator;
     private Interpolator mDisappearInterpolator;
     private Interpolator mFastOutSlowInInterpolator;
-    private boolean mShowPassword = true;
-    private UserActivityListener mUserActivityListener;
-    private boolean mIsPinHinting;
-    private PinShapeInput mPinShapeInput;
-    private boolean mUsePinShapes = false;
-
-    public interface UserActivityListener {
-        void onUserActivity();
-    }
 
     public PasswordTextView(Context context) {
         this(context, null);
@@ -145,8 +125,7 @@
             mCharPadding = a.getDimensionPixelSize(R.styleable.PasswordTextView_charPadding,
                     getContext().getResources().getDimensionPixelSize(
                             R.dimen.password_char_padding));
-            mDrawColor = a.getColor(R.styleable.PasswordTextView_android_textColor,
-                    Color.WHITE);
+            mDrawColor = a.getColor(R.styleable.PasswordTextView_android_textColor, Color.WHITE);
             mDrawPaint.setColor(mDrawColor);
 
         } finally {
@@ -156,8 +135,7 @@
         mDrawPaint.setFlags(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
         mDrawPaint.setTextAlign(Paint.Align.CENTER);
         mDrawPaint.setTypeface(Typeface.create(
-                context.getString(com.android.internal.R.string.config_headlineFontFamily),
-                0));
+                context.getString(com.android.internal.R.string.config_headlineFontFamily), 0));
         mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
                 android.R.interpolator.linear_out_slow_in);
         mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
@@ -169,9 +147,19 @@
     }
 
     @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        mTextHeightRaw = getContext().getResources().getInteger(
-                R.integer.scaled_password_text_size);
+    protected PinShapeInput inflatePinShapeInput(boolean isPinHinting) {
+        if (isPinHinting) {
+            return (PinShapeInput) LayoutInflater.from(mContext).inflate(
+                    R.layout.keyguard_pin_shape_hinting_view, null);
+        } else {
+            return (PinShapeInput) LayoutInflater.from(mContext).inflate(
+                    R.layout.keyguard_pin_shape_non_hinting_view, null);
+        }
+    }
+
+    @Override
+    protected boolean shouldSendAccessibilityEvent() {
+        return isFocused() || isSelected() && isShown();
     }
 
     @Override
@@ -201,8 +189,8 @@
         int charHeight = (bounds.bottom - bounds.top);
         float yPosition =
                 (getHeight() - getPaddingBottom() - getPaddingTop()) / 2 + getPaddingTop();
-        canvas.clipRect(getPaddingLeft(), getPaddingTop(),
-                getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
+        canvas.clipRect(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
+                getHeight() - getPaddingBottom());
         float charLength = bounds.right - bounds.left;
         for (int i = 0; i < length; i++) {
             CharState charState = mTextChars.get(i);
@@ -212,6 +200,67 @@
         }
     }
 
+    @Override
+    protected void onAppend(char c, int newLength) {
+        int visibleChars = mTextChars.size();
+        CharState charState;
+        if (newLength > visibleChars) {
+            charState = obtainCharState(c);
+            mTextChars.add(charState);
+        } else {
+            charState = mTextChars.get(newLength - 1);
+            charState.whichChar = c;
+        }
+        charState.startAppearAnimation();
+
+        // ensure that the previous element is being swapped
+        if (newLength > 1) {
+            CharState previousState = mTextChars.get(newLength - 2);
+            if (previousState.isDotSwapPending) {
+                previousState.swapToDotWhenAppearFinished();
+            }
+        }
+    }
+
+    @Override
+    protected void onDelete(int index) {
+        CharState charState = mTextChars.get(index);
+        charState.startRemoveAnimation(0, 0);
+    }
+
+    @Override
+    protected void onReset(boolean animated) {
+        if (animated) {
+            int length = mTextChars.size();
+            int middleIndex = (length - 1) / 2;
+            long delayPerElement = RESET_DELAY_PER_ELEMENT;
+            for (int i = 0; i < length; i++) {
+                CharState charState = mTextChars.get(i);
+                int delayIndex;
+                if (i <= middleIndex) {
+                    delayIndex = i * 2;
+                } else {
+                    int distToMiddle = i - middleIndex;
+                    delayIndex = (length - 1) - (distToMiddle - 1) * 2;
+                }
+                long startDelay = delayIndex * delayPerElement;
+                startDelay = Math.min(startDelay, RESET_MAX_DELAY);
+                long maxDelay = delayPerElement * (length - 1);
+                maxDelay = Math.min(maxDelay, RESET_MAX_DELAY) + DISAPPEAR_DURATION;
+                charState.startRemoveAnimation(startDelay, maxDelay);
+                charState.removeDotSwapCallbacks();
+            }
+        } else {
+            mTextChars.clear();
+        }
+    }
+
+    @Override
+    protected void onUserActivity() {
+        mPM.userActivity(SystemClock.uptimeMillis(), false);
+        super.onUserActivity();
+    }
+
     /**
      * Reload colors from resources.
      **/
@@ -225,8 +274,9 @@
     }
 
     @Override
-    public boolean hasOverlappingRendering() {
-        return false;
+    protected void onConfigurationChanged(Configuration newConfig) {
+        mTextHeightRaw = getContext().getResources().getInteger(
+                R.integer.scaled_password_text_size);
     }
 
     private Rect getCharBounds() {
@@ -252,67 +302,14 @@
         return width;
     }
 
-
-    public void append(char c) {
-        int visibleChars = mTextChars.size();
-        CharSequence textbefore = getTransformedText();
-        mText = mText + c;
-        int newLength = mText.length();
-        CharState charState;
-        if (newLength > visibleChars) {
-            charState = obtainCharState(c);
-            mTextChars.add(charState);
-        } else {
-            charState = mTextChars.get(newLength - 1);
-            charState.whichChar = c;
-        }
-        if (mPinShapeInput != null) {
-            mPinShapeInput.append();
-        }
-        charState.startAppearAnimation();
-
-        // ensure that the previous element is being swapped
-        if (newLength > 1) {
-            CharState previousState = mTextChars.get(newLength - 2);
-            if (previousState.isDotSwapPending) {
-                previousState.swapToDotWhenAppearFinished();
-            }
-        }
-        userActivity();
-        sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length(), 0, 1);
+    private CharState obtainCharState(char c) {
+        CharState charState = new CharState();
+        charState.whichChar = c;
+        return charState;
     }
 
-    public void setUserActivityListener(UserActivityListener userActivityListener) {
-        mUserActivityListener = userActivityListener;
-    }
-
-    private void userActivity() {
-        mPM.userActivity(SystemClock.uptimeMillis(), false);
-        if (mUserActivityListener != null) {
-            mUserActivityListener.onUserActivity();
-        }
-    }
-
-    public void deleteLastChar() {
-        int length = mText.length();
-        CharSequence textbefore = getTransformedText();
-        if (length > 0) {
-            mText = mText.substring(0, length - 1);
-            CharState charState = mTextChars.get(length - 1);
-            charState.startRemoveAnimation(0, 0);
-            sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length() - 1, 1, 0);
-            if (mPinShapeInput != null) {
-                mPinShapeInput.delete();
-            }
-        }
-        userActivity();
-    }
-
-    public String getText() {
-        return mText;
-    }
-
-    private CharSequence getTransformedText() {
+    @Override
+    protected CharSequence getTransformedText() {
         int textLength = mTextChars.size();
         StringBuilder stringBuilder = new StringBuilder(textLength);
         for (int i = 0; i < textLength; i++) {
@@ -327,130 +324,6 @@
         return stringBuilder;
     }
 
-    private CharState obtainCharState(char c) {
-        CharState charState = new CharState();
-        charState.whichChar = c;
-        return charState;
-    }
-
-    public void reset(boolean animated, boolean announce) {
-        CharSequence textbefore = getTransformedText();
-        mText = "";
-        int length = mTextChars.size();
-        int middleIndex = (length - 1) / 2;
-        long delayPerElement = RESET_DELAY_PER_ELEMENT;
-        for (int i = 0; i < length; i++) {
-            CharState charState = mTextChars.get(i);
-            if (animated) {
-                int delayIndex;
-                if (i <= middleIndex) {
-                    delayIndex = i * 2;
-                } else {
-                    int distToMiddle = i - middleIndex;
-                    delayIndex = (length - 1) - (distToMiddle - 1) * 2;
-                }
-                long startDelay = delayIndex * delayPerElement;
-                startDelay = Math.min(startDelay, RESET_MAX_DELAY);
-                long maxDelay = delayPerElement * (length - 1);
-                maxDelay = Math.min(maxDelay, RESET_MAX_DELAY) + DISAPPEAR_DURATION;
-                charState.startRemoveAnimation(startDelay, maxDelay);
-                charState.removeDotSwapCallbacks();
-            }
-        }
-        if (!animated) {
-            mTextChars.clear();
-        } else {
-            userActivity();
-        }
-        if (mPinShapeInput != null) {
-            mPinShapeInput.reset();
-        }
-        if (announce) {
-            sendAccessibilityEventTypeViewTextChanged(textbefore, 0, textbefore.length(), 0);
-        }
-    }
-
-    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex,
-                                                   int removedCount, int addedCount) {
-        if (AccessibilityManager.getInstance(mContext).isEnabled() &&
-                (isFocused() || isSelected() && isShown())) {
-            AccessibilityEvent event =
-                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
-            event.setFromIndex(fromIndex);
-            event.setRemovedCount(removedCount);
-            event.setAddedCount(addedCount);
-            event.setBeforeText(beforeText);
-            CharSequence transformedText = getTransformedText();
-            if (!TextUtils.isEmpty(transformedText)) {
-                event.getText().add(transformedText);
-            }
-            event.setPassword(true);
-            sendAccessibilityEventUnchecked(event);
-        }
-    }
-
-    @Override
-    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
-        super.onInitializeAccessibilityEvent(event);
-
-        event.setClassName(EditText.class.getName());
-        event.setPassword(true);
-    }
-
-    @Override
-    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(info);
-
-        info.setClassName(EditText.class.getName());
-        info.setPassword(true);
-        info.setText(getTransformedText());
-
-        info.setEditable(true);
-
-        info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD);
-    }
-
-    /**
-     * Sets whether to use pin shapes.
-     */
-    public void setUsePinShapes(boolean usePinShapes) {
-        mUsePinShapes = usePinShapes;
-    }
-
-    /**
-     * Determines whether AutoConfirmation feature is on.
-     *
-     * @param isPinHinting
-     */
-    public void setIsPinHinting(boolean isPinHinting) {
-        // Do not reinflate the view if we are using the same one.
-        if (mPinShapeInput != null && mIsPinHinting == isPinHinting) {
-            return;
-        }
-        mIsPinHinting = isPinHinting;
-
-        if (mPinShapeInput != null) {
-            removeView(mPinShapeInput.getView());
-            mPinShapeInput = null;
-        }
-
-        if (isPinHinting) {
-            mPinShapeInput = (PinShapeInput) LayoutInflater.from(mContext).inflate(
-                    R.layout.keyguard_pin_shape_hinting_view, null);
-        } else {
-            mPinShapeInput = (PinShapeInput) LayoutInflater.from(mContext).inflate(
-                    R.layout.keyguard_pin_shape_non_hinting_view, null);
-        }
-        addView(mPinShapeInput.getView());
-    }
-
-    /**
-     * Controls whether the last entered digit is briefly shown after being entered
-     */
-    public void setShowPassword(boolean enabled) {
-        mShowPassword = enabled;
-    }
-
     private class CharState {
         char whichChar;
         ValueAnimator textAnimator;
@@ -468,6 +341,7 @@
 
         Animator.AnimatorListener removeEndListener = new AnimatorListenerAdapter() {
             private boolean mCancelled;
+
             @Override
             public void onAnimationCancel(Animator animation) {
                 mCancelled = true;
@@ -516,53 +390,53 @@
             }
         };
 
-        private ValueAnimator.AnimatorUpdateListener dotSizeUpdater
-                = new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                currentDotSizeFactor = (float) animation.getAnimatedValue();
-                invalidate();
-            }
-        };
-
-        private ValueAnimator.AnimatorUpdateListener textSizeUpdater
-                = new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                boolean textVisibleBefore = isCharVisibleForA11y();
-                float beforeTextSizeFactor = currentTextSizeFactor;
-                currentTextSizeFactor = (float) animation.getAnimatedValue();
-                if (textVisibleBefore != isCharVisibleForA11y()) {
-                    currentTextSizeFactor = beforeTextSizeFactor;
-                    CharSequence beforeText = getTransformedText();
-                    currentTextSizeFactor = (float) animation.getAnimatedValue();
-                    int indexOfThisChar = mTextChars.indexOf(CharState.this);
-                    if (indexOfThisChar >= 0) {
-                        sendAccessibilityEventTypeViewTextChanged(
-                                beforeText, indexOfThisChar, 1, 1);
+        private ValueAnimator.AnimatorUpdateListener mDotSizeUpdater =
+                new ValueAnimator.AnimatorUpdateListener() {
+                    @Override
+                    public void onAnimationUpdate(ValueAnimator animation) {
+                        currentDotSizeFactor = (float) animation.getAnimatedValue();
+                        invalidate();
                     }
-                }
-                invalidate();
-            }
-        };
+                };
 
-        private ValueAnimator.AnimatorUpdateListener textTranslationUpdater
-                = new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                currentTextTranslationY = (float) animation.getAnimatedValue();
-                invalidate();
-            }
-        };
+        private ValueAnimator.AnimatorUpdateListener mTextSizeUpdater =
+                new ValueAnimator.AnimatorUpdateListener() {
+                    @Override
+                    public void onAnimationUpdate(ValueAnimator animation) {
+                        boolean textVisibleBefore = isCharVisibleForA11y();
+                        float beforeTextSizeFactor = currentTextSizeFactor;
+                        currentTextSizeFactor = (float) animation.getAnimatedValue();
+                        if (textVisibleBefore != isCharVisibleForA11y()) {
+                            currentTextSizeFactor = beforeTextSizeFactor;
+                            CharSequence beforeText = getTransformedText();
+                            currentTextSizeFactor = (float) animation.getAnimatedValue();
+                            int indexOfThisChar = mTextChars.indexOf(CharState.this);
+                            if (indexOfThisChar >= 0) {
+                                sendAccessibilityEventTypeViewTextChanged(beforeText,
+                                        indexOfThisChar, 1, 1);
+                            }
+                        }
+                        invalidate();
+                    }
+                };
 
-        private ValueAnimator.AnimatorUpdateListener widthUpdater
-                = new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                currentWidthFactor = (float) animation.getAnimatedValue();
-                invalidate();
-            }
-        };
+        private ValueAnimator.AnimatorUpdateListener mTextTranslationUpdater =
+                new ValueAnimator.AnimatorUpdateListener() {
+                    @Override
+                    public void onAnimationUpdate(ValueAnimator animation) {
+                        currentTextTranslationY = (float) animation.getAnimatedValue();
+                        invalidate();
+                    }
+                };
+
+        private ValueAnimator.AnimatorUpdateListener mWidthUpdater =
+                new ValueAnimator.AnimatorUpdateListener() {
+                    @Override
+                    public void onAnimationUpdate(ValueAnimator animation) {
+                        currentWidthFactor = (float) animation.getAnimatedValue();
+                        invalidate();
+                    }
+                };
 
         private Runnable dotSwapperRunnable = new Runnable() {
             @Override
@@ -573,12 +447,15 @@
         };
 
         void startRemoveAnimation(long startDelay, long widthDelay) {
-            boolean dotNeedsAnimation = (currentDotSizeFactor > 0.0f && dotAnimator == null)
-                    || (dotAnimator != null && dotAnimationIsGrowing);
-            boolean textNeedsAnimation = (currentTextSizeFactor > 0.0f && textAnimator == null)
-                    || (textAnimator != null && textAnimationIsGrowing);
-            boolean widthNeedsAnimation = (currentWidthFactor > 0.0f && widthAnimator == null)
-                    || (widthAnimator != null && widthAnimationIsGrowing);
+            boolean dotNeedsAnimation =
+                    (currentDotSizeFactor > 0.0f && dotAnimator == null) || (dotAnimator != null
+                            && dotAnimationIsGrowing);
+            boolean textNeedsAnimation =
+                    (currentTextSizeFactor > 0.0f && textAnimator == null) || (textAnimator != null
+                            && textAnimationIsGrowing);
+            boolean widthNeedsAnimation =
+                    (currentWidthFactor > 0.0f && widthAnimator == null) || (widthAnimator != null
+                            && widthAnimationIsGrowing);
             if (dotNeedsAnimation) {
                 startDotDisappearAnimation(startDelay);
             }
@@ -591,10 +468,10 @@
         }
 
         void startAppearAnimation() {
-            boolean dotNeedsAnimation = !mShowPassword
-                    && (dotAnimator == null || !dotAnimationIsGrowing);
-            boolean textNeedsAnimation = mShowPassword
-                    && (textAnimator == null || !textAnimationIsGrowing);
+            boolean dotNeedsAnimation =
+                    !mShowPassword && (dotAnimator == null || !dotAnimationIsGrowing);
+            boolean textNeedsAnimation =
+                    mShowPassword && (textAnimator == null || !textAnimationIsGrowing);
             boolean widthNeedsAnimation = (widthAnimator == null || !widthAnimationIsGrowing);
             if (dotNeedsAnimation) {
                 startDotAppearAnimation(0);
@@ -628,8 +505,8 @@
         void swapToDotWhenAppearFinished() {
             removeDotSwapCallbacks();
             if (textAnimator != null) {
-                long remainingDuration = textAnimator.getDuration()
-                        - textAnimator.getCurrentPlayTime();
+                long remainingDuration =
+                        textAnimator.getDuration() - textAnimator.getCurrentPlayTime();
                 postDotSwap(remainingDuration + TEXT_REST_DURATION_AFTER_APPEAR);
             } else {
                 performSwap();
@@ -638,14 +515,14 @@
 
         private void performSwap() {
             startTextDisappearAnimation(0);
-            startDotAppearAnimation(DISAPPEAR_DURATION
-                    - DOT_APPEAR_TEXT_DISAPPEAR_OVERLAP_DURATION);
+            startDotAppearAnimation(
+                    DISAPPEAR_DURATION - DOT_APPEAR_TEXT_DISAPPEAR_OVERLAP_DURATION);
         }
 
         private void startWidthDisappearAnimation(long widthDelay) {
             cancelAnimator(widthAnimator);
             widthAnimator = ValueAnimator.ofFloat(currentWidthFactor, 0.0f);
-            widthAnimator.addUpdateListener(widthUpdater);
+            widthAnimator.addUpdateListener(mWidthUpdater);
             widthAnimator.addListener(widthFinishListener);
             widthAnimator.addListener(removeEndListener);
             widthAnimator.setDuration((long) (DISAPPEAR_DURATION * currentWidthFactor));
@@ -657,7 +534,7 @@
         private void startTextDisappearAnimation(long startDelay) {
             cancelAnimator(textAnimator);
             textAnimator = ValueAnimator.ofFloat(currentTextSizeFactor, 0.0f);
-            textAnimator.addUpdateListener(textSizeUpdater);
+            textAnimator.addUpdateListener(mTextSizeUpdater);
             textAnimator.addListener(textFinishListener);
             textAnimator.setInterpolator(mDisappearInterpolator);
             textAnimator.setDuration((long) (DISAPPEAR_DURATION * currentTextSizeFactor));
@@ -669,7 +546,7 @@
         private void startDotDisappearAnimation(long startDelay) {
             cancelAnimator(dotAnimator);
             ValueAnimator animator = ValueAnimator.ofFloat(currentDotSizeFactor, 0.0f);
-            animator.addUpdateListener(dotSizeUpdater);
+            animator.addUpdateListener(mDotSizeUpdater);
             animator.addListener(dotFinishListener);
             animator.setInterpolator(mDisappearInterpolator);
             long duration = (long) (DISAPPEAR_DURATION * Math.min(currentDotSizeFactor, 1.0f));
@@ -683,7 +560,7 @@
         private void startWidthAppearAnimation() {
             cancelAnimator(widthAnimator);
             widthAnimator = ValueAnimator.ofFloat(currentWidthFactor, 1.0f);
-            widthAnimator.addUpdateListener(widthUpdater);
+            widthAnimator.addUpdateListener(mWidthUpdater);
             widthAnimator.addListener(widthFinishListener);
             widthAnimator.setDuration((long) (APPEAR_DURATION * (1f - currentWidthFactor)));
             widthAnimator.start();
@@ -693,7 +570,7 @@
         private void startTextAppearAnimation() {
             cancelAnimator(textAnimator);
             textAnimator = ValueAnimator.ofFloat(currentTextSizeFactor, 1.0f);
-            textAnimator.addUpdateListener(textSizeUpdater);
+            textAnimator.addUpdateListener(mTextSizeUpdater);
             textAnimator.addListener(textFinishListener);
             textAnimator.setInterpolator(mAppearInterpolator);
             textAnimator.setDuration((long) (APPEAR_DURATION * (1f - currentTextSizeFactor)));
@@ -703,7 +580,7 @@
             // handle translation
             if (textTranslateAnimator == null) {
                 textTranslateAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
-                textTranslateAnimator.addUpdateListener(textTranslationUpdater);
+                textTranslateAnimator.addUpdateListener(mTextTranslationUpdater);
                 textTranslateAnimator.addListener(textTranslateFinishListener);
                 textTranslateAnimator.setInterpolator(mAppearInterpolator);
                 textTranslateAnimator.setDuration(APPEAR_DURATION);
@@ -717,14 +594,14 @@
                 // We perform an overshoot animation
                 ValueAnimator overShootAnimator = ValueAnimator.ofFloat(currentDotSizeFactor,
                         DOT_OVERSHOOT_FACTOR);
-                overShootAnimator.addUpdateListener(dotSizeUpdater);
+                overShootAnimator.addUpdateListener(mDotSizeUpdater);
                 overShootAnimator.setInterpolator(mAppearInterpolator);
-                long overShootDuration = (long) (DOT_APPEAR_DURATION_OVERSHOOT
-                        * OVERSHOOT_TIME_POSITION);
+                long overShootDuration =
+                        (long) (DOT_APPEAR_DURATION_OVERSHOOT * OVERSHOOT_TIME_POSITION);
                 overShootAnimator.setDuration(overShootDuration);
                 ValueAnimator settleBackAnimator = ValueAnimator.ofFloat(DOT_OVERSHOOT_FACTOR,
                         1.0f);
-                settleBackAnimator.addUpdateListener(dotSizeUpdater);
+                settleBackAnimator.addUpdateListener(mDotSizeUpdater);
                 settleBackAnimator.setDuration(DOT_APPEAR_DURATION_OVERSHOOT - overShootDuration);
                 settleBackAnimator.addListener(dotFinishListener);
                 AnimatorSet animatorSet = new AnimatorSet();
@@ -734,7 +611,7 @@
                 dotAnimator = animatorSet;
             } else {
                 ValueAnimator growAnimator = ValueAnimator.ofFloat(currentDotSizeFactor, 1.0f);
-                growAnimator.addUpdateListener(dotSizeUpdater);
+                growAnimator.addUpdateListener(mDotSizeUpdater);
                 growAnimator.setDuration((long) (APPEAR_DURATION * (1.0f - currentDotSizeFactor)));
                 growAnimator.addListener(dotFinishListener);
                 growAnimator.setStartDelay(delay);
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
index 56c0953..5f33ef9 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
@@ -61,7 +61,7 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
-        if (getChildCount() > 0) {
+        if (getChildCount() > 2) {
             View firstChild = getChildAt(0);
             boolean isVisible = firstChild.getLocalVisibleRect(mFirstChildVisibleRect);
             boolean clipped = mFirstChildVisibleRect.left > 0
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerScope.java
index 8dbe5e0..d59f51f 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerScope.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerScope.java
@@ -24,7 +24,7 @@
 import javax.inject.Scope;
 
 /**
- * Scope annotation for singleton items within the CentralSurfacesComponent.
+ * Scope annotation for singleton items within the {@link KeyguardBouncerComponent}.
  */
 @Documented
 @Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
index f498ef3..394d63171 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
@@ -24,7 +24,7 @@
 import javax.inject.Scope;
 
 /**
- * Scope annotation for singleton items within the CentralSurfacesComponent.
+ * Scope annotation for singleton items within the {@link KeyguardStatusBarViewComponent}.
  */
 @Documented
 @Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java
index aeae8e3..6c2c9f2 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java
@@ -24,7 +24,7 @@
 import javax.inject.Scope;
 
 /**
- * Scope annotation for singleton items within the CentralSurfacesComponent.
+ * Scope annotation for singleton items within the {@link KeyguardStatusViewComponent}.
  */
 @Documented
 @Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index eec16e6..c9801d7 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -52,6 +52,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -184,6 +185,10 @@
     protected void setListening(boolean listening) {
         mListening = listening;
         if (listening) {
+            // System UI could be restarted while ops are active, so fetch the currently active ops
+            // once System UI starts listening again.
+            fetchCurrentActiveOps();
+
             mAppOps.startWatchingActive(OPS, this);
             mAppOps.startWatchingNoted(OPS, this);
             mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
@@ -216,6 +221,29 @@
         }
     }
 
+    private void fetchCurrentActiveOps() {
+        List<AppOpsManager.PackageOps> packageOps = mAppOps.getPackagesForOps(OPS);
+        for (AppOpsManager.PackageOps op : packageOps) {
+            for (AppOpsManager.OpEntry entry : op.getOps()) {
+                for (Map.Entry<String, AppOpsManager.AttributedOpEntry> attributedOpEntry :
+                        entry.getAttributedOpEntries().entrySet()) {
+                    if (attributedOpEntry.getValue().isRunning()) {
+                        onOpActiveChanged(
+                                entry.getOpStr(),
+                                op.getUid(),
+                                op.getPackageName(),
+                                /* attributionTag= */ attributedOpEntry.getKey(),
+                                /* active= */ true,
+                                // AppOpsManager doesn't have a way to fetch attribution flags or
+                                // chain ID given an op entry, so default them to none.
+                                AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+                                AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Adds a callback that will get notifified when an AppOp of the type the controller tracks
      * changes
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 3c74bf4..066cba23 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -16,24 +16,69 @@
 
 package com.android.systemui.back.domain.interactor
 
+import android.window.BackEvent
+import android.window.OnBackAnimationCallback
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import android.window.WindowOnBackInvokedDispatcher
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.shade.QuickSettingsController
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 /** Handles requests to go back either from a button or gesture. */
 @SysUISingleton
 class BackActionInteractor
 @Inject
 constructor(
+    @Application private val scope: CoroutineScope,
     private val statusBarStateController: StatusBarStateController,
     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
-    private val shadeController: ShadeController
-) {
+    private val shadeController: ShadeController,
+    private val notificationShadeWindowController: NotificationShadeWindowController,
+    private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor,
+    featureFlags: FeatureFlags,
+) : CoreStartable {
+
+    private var isCallbackRegistered = false
+
+    private val callback =
+        if (featureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE)) {
+            /**
+             * New callback that handles back gesture invoked, cancel, progress and provides
+             * feedback via Shade animation.
+             */
+            object : OnBackAnimationCallback {
+                override fun onBackInvoked() {
+                    onBackRequested()
+                }
+
+                override fun onBackProgressed(backEvent: BackEvent) {
+                    if (shouldBackBeHandled() && shadeViewController.canBeCollapsed()) {
+                        shadeViewController.onBackProgressed(backEvent.progress)
+                    }
+                }
+            }
+        } else {
+            OnBackInvokedCallback { onBackRequested() }
+        }
+
+    private val onBackInvokedDispatcher: WindowOnBackInvokedDispatcher?
+        get() =
+            notificationShadeWindowController.windowRootView?.viewRootImpl?.onBackInvokedDispatcher
+
     private lateinit var shadeViewController: ShadeViewController
     private lateinit var qsController: QuickSettingsController
 
@@ -42,6 +87,19 @@
         this.shadeViewController = svController
     }
 
+    override fun start() {
+        scope.launch {
+            windowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive.collect {
+                visible ->
+                if (visible) {
+                    registerBackCallback()
+                } else {
+                    unregisterBackCallback()
+                }
+            }
+        }
+    }
+
     fun shouldBackBeHandled(): Boolean {
         return statusBarStateController.state != StatusBarState.KEYGUARD &&
             statusBarStateController.state != StatusBarState.SHADE_LOCKED &&
@@ -74,4 +132,24 @@
         }
         return false
     }
+
+    private fun registerBackCallback() {
+        if (isCallbackRegistered) {
+            return
+        }
+        onBackInvokedDispatcher?.let {
+            it.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, callback)
+            isCallbackRegistered = true
+        }
+    }
+
+    private fun unregisterBackCallback() {
+        if (!isCallbackRegistered) {
+            return
+        }
+        onBackInvokedDispatcher?.let {
+            it.unregisterOnBackInvokedCallback(callback)
+            isCallbackRegistered = false
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index e8b8f54..be08932 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -78,17 +78,6 @@
 
     private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
-    /**
-     * If the API caller or the user's personal preferences require explicit confirmation after
-     * successful authentication.
-     */
-    val isConfirmationRequired: Flow<Boolean> =
-        combine(_isOverlayTouched, promptSelectorInteractor.isConfirmationRequired) {
-            isOverlayTouched,
-            isConfirmationRequired ->
-            !isOverlayTouched && isConfirmationRequired
-        }
-
     /** The kind of credential the user has. */
     val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind
 
@@ -137,6 +126,15 @@
             }
             .distinctUntilChanged()
 
+    /**
+     * If the API caller or the user's personal preferences require explicit confirmation after
+     * successful authentication. Confirmation always required when in explicit flow.
+     */
+    val isConfirmationRequired: Flow<Boolean> =
+        combine(_isOverlayTouched, size) { isOverlayTouched, size ->
+            !isOverlayTouched && size.isNotSmall
+        }
+
     /** Title for the prompt. */
     val title: Flow<String> =
         promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
@@ -170,12 +168,7 @@
             .distinctUntilChanged()
 
     /** If the icon can be used as a confirmation button. */
-    val isIconConfirmButton: Flow<Boolean> =
-        combine(size, promptSelectorInteractor.isConfirmationRequired) {
-            size,
-            isConfirmationRequired ->
-            size.isNotSmall && isConfirmationRequired
-        }
+    val isIconConfirmButton: Flow<Boolean> = size.map { it.isNotSmall }.distinctUntilChanged()
 
     /** If the negative button should be shown. */
     val isNegativeButtonVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 1bf3a9e..fc32f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.bouncer.data.repository.BouncerRepository
+import com.android.systemui.classifier.FalsingClassifier
+import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
@@ -50,6 +52,7 @@
     private val authenticationInteractor: AuthenticationInteractor,
     private val sceneInteractor: SceneInteractor,
     featureFlags: FeatureFlags,
+    private val falsingInteractor: FalsingInteractor,
 ) {
 
     /** The user-facing message to show in the bouncer. */
@@ -103,6 +106,34 @@
         }
     }
 
+    /** Notifies that the user has places down a pointer, not necessarily dragging just yet. */
+    fun onDown() {
+        falsingInteractor.avoidGesture()
+    }
+
+    /**
+     * Notifies of "intentional" (i.e. non-false) user interaction with the UI which is very likely
+     * to be real user interaction with the bouncer and not the result of a false touch in the
+     * user's pocket or by the user's face while holding their device up to their ear.
+     */
+    fun onIntentionalUserInput() {
+        falsingInteractor.updateFalseConfidence(FalsingClassifier.Result.passed(0.6))
+    }
+
+    /**
+     * Notifies of false input which is very likely to be the result of a false touch in the user's
+     * pocket or by the user's face while holding their device up to their ear.
+     */
+    fun onFalseUserInput() {
+        falsingInteractor.updateFalseConfidence(
+            FalsingClassifier.Result.falsed(
+                /* confidence= */ 0.7,
+                /* context= */ javaClass.simpleName,
+                /* reason= */ "empty pattern input",
+            )
+        )
+    }
+
     /**
      * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index ca15f4e..80a41ce 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -34,6 +34,7 @@
     ) {
 
     private val _password = MutableStateFlow("")
+
     /** The password entered so far. */
     val password: StateFlow<String> = _password.asStateFlow()
 
@@ -48,6 +49,10 @@
             interactor.clearMessage()
         }
 
+        if (password.isNotEmpty()) {
+            interactor.onIntentionalUserInput()
+        }
+
         _password.value = password
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 4425f9f..85eaf0b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -46,10 +46,12 @@
 
     /** The number of columns in the dot grid. */
     val columnCount = 3
+
     /** The number of rows in the dot grid. */
     val rowCount = 3
 
     private val _selectedDots = MutableStateFlow<LinkedHashSet<PatternDotViewModel>>(linkedSetOf())
+
     /** The dots that were selected by the user, in the order of selection. */
     val selectedDots: StateFlow<List<PatternDotViewModel>> =
         _selectedDots
@@ -61,10 +63,12 @@
             )
 
     private val _currentDot = MutableStateFlow<PatternDotViewModel?>(null)
+
     /** The most-recently selected dot that the user selected. */
     val currentDot: StateFlow<PatternDotViewModel?> = _currentDot.asStateFlow()
 
     private val _dots = MutableStateFlow(defaultDots())
+
     /** All dots on the grid. */
     val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow()
 
@@ -76,6 +80,11 @@
         interactor.resetMessage()
     }
 
+    /** Notifies that the user has placed down a pointer, not necessarily dragging just yet. */
+    fun onDown() {
+        interactor.onDown()
+    }
+
     /** Notifies that the user has started a drag gesture across the dot grid. */
     fun onDragStart() {
         interactor.clearMessage()
@@ -124,11 +133,13 @@
                             dot =
                                 PatternDotViewModel(
                                     x =
-                                        if (hitDot.x > dot.x) dot.x + 1
-                                        else if (hitDot.x < dot.x) dot.x - 1 else dot.x,
+                                        if (hitDot.x > dot.x) {
+                                            dot.x + 1
+                                        } else if (hitDot.x < dot.x) dot.x - 1 else dot.x,
                                     y =
-                                        if (hitDot.y > dot.y) dot.y + 1
-                                        else if (hitDot.y < dot.y) dot.y - 1 else dot.y,
+                                        if (hitDot.y > dot.y) {
+                                            dot.y + 1
+                                        } else if (hitDot.y < dot.y) dot.y - 1 else dot.y,
                                 )
                         }
                     }
@@ -148,6 +159,12 @@
     /** Notifies that the user has ended the drag gesture across the dot grid. */
     fun onDragEnd() {
         val pattern = _selectedDots.value.map { it.toCoordinate() }
+
+        if (pattern.size == 1) {
+            // Single dot patterns are treated as erroneous/false taps:
+            interactor.onFalseUserInput()
+        }
+
         _dots.value = defaultDots()
         _currentDot.value = null
         _selectedDots.value = linkedSetOf()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 844cf02..ebf939b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -88,6 +88,11 @@
         interactor.resetMessage()
     }
 
+    /** Notifies that the user has placed down a pointer. */
+    fun onDown() {
+        interactor.onDown()
+    }
+
     /** Notifies that the user clicked on a PIN button with the given digit value. */
     fun onPinButtonClicked(input: Int) {
         val pinInput = mutablePinInput.value
@@ -95,6 +100,8 @@
             interactor.clearMessage()
         }
 
+        interactor.onIntentionalUserInput()
+
         mutablePinInput.value = pinInput.append(input)
         tryAuthenticate(useAutoConfirm = true)
     }
@@ -148,8 +155,10 @@
 enum class ActionButtonAppearance {
     /** Button must not be shown. */
     Hidden,
+
     /** Button is shown, but with no background to make it less prominent. */
     Subtle,
+
     /** Button is shown. */
     Shown,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index 08e1e9a..f77f989 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -29,78 +29,21 @@
     void setShowingAod(boolean showingAod);
 
     /** */
-    void onNotificationStartDraggingDown();
-
-    /** */
-    void onNotificationStopDraggingDown();
-
-    /** */
-    void setNotificationExpanded();
-
-    /** */
-    void onQsDown();
-
-    /** */
     boolean shouldEnforceBouncer();
 
     /** */
-    void onTrackingStarted(boolean secure);
-
-    /** */
-    void onTrackingStopped();
-
-    /** */
-    void onLeftAffordanceOn();
-
-    /** */
-    void onCameraOn();
-
-    /** */
-    void onAffordanceSwipingStarted(boolean rightCorner);
-
-    /** */
-    void onAffordanceSwipingAborted();
-
-    /** */
-    void onStartExpandingFromPulse();
-
-    /** */
-    void onExpansionFromPulseStopped();
-
-    /** */
     void onScreenOnFromTouch();
 
     /** */
     boolean isReportingEnabled();
 
     /** */
-    void onUnlockHintStarted();
-
-    /** */
-    void onCameraHintStarted();
-
-    /** */
-    void onLeftAffordanceHintStarted();
-
-    /** */
     void onScreenTurningOn();
 
     /** */
     void onScreenOff();
 
     /** */
-    void onNotificationStopDismissing();
-
-    /** */
-    void onNotificationDismissed();
-
-    /** */
-    void onNotificationStartDismissing();
-
-    /** */
-    void onNotificationDoubleTap(boolean accepted, float dx, float dy);
-
-    /** */
     void onBouncerShown();
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorActual.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorActual.kt
new file mode 100644
index 0000000..3eaad1f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorActual.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.classifier
+
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FalsingCollectorActual
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index f335d1d..c0ee71c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -29,59 +29,11 @@
     }
 
     @Override
-    public void onNotificationStartDraggingDown() {
-    }
-
-    @Override
-    public void onNotificationStopDraggingDown() {
-    }
-
-    @Override
-    public void setNotificationExpanded() {
-    }
-
-    @Override
-    public void onQsDown() {
-    }
-
-    @Override
     public boolean shouldEnforceBouncer() {
         return false;
     }
 
     @Override
-    public void onTrackingStarted(boolean secure) {
-    }
-
-    @Override
-    public void onTrackingStopped() {
-    }
-
-    @Override
-    public void onLeftAffordanceOn() {
-    }
-
-    @Override
-    public void onCameraOn() {
-    }
-
-    @Override
-    public void onAffordanceSwipingStarted(boolean rightCorner) {
-    }
-
-    @Override
-    public void onAffordanceSwipingAborted() {
-    }
-
-    @Override
-    public void onStartExpandingFromPulse() {
-    }
-
-    @Override
-    public void onExpansionFromPulseStopped() {
-    }
-
-    @Override
     public void onScreenOnFromTouch() {
     }
 
@@ -91,18 +43,6 @@
     }
 
     @Override
-    public void onUnlockHintStarted() {
-    }
-
-    @Override
-    public void onCameraHintStarted() {
-    }
-
-    @Override
-    public void onLeftAffordanceHintStarted() {
-    }
-
-    @Override
     public void onScreenTurningOn() {
     }
 
@@ -111,22 +51,6 @@
     }
 
     @Override
-    public void onNotificationStopDismissing() {
-    }
-
-    @Override
-    public void onNotificationDismissed() {
-    }
-
-    @Override
-    public void onNotificationStartDismissing() {
-    }
-
-    @Override
-    public void onNotificationDoubleTap(boolean accepted, float dx, float dy) {
-    }
-
-    @Override
     public void onBouncerShown() {
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 6a021f6..39c01f7 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -103,10 +103,6 @@
 
     private final BatteryStateChangeCallback mBatteryListener = new BatteryStateChangeCallback() {
         @Override
-        public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
-        }
-
-        @Override
         public void onWirelessChargingChanged(boolean isWirelessCharging) {
             if (isWirelessCharging || mDockManager.isDocked()) {
                 mProximitySensor.pause();
@@ -169,34 +165,21 @@
 
     @Override
     public void onSuccessfulUnlock() {
+        logDebug("REAL: onSuccessfulUnlock");
         mFalsingManager.onSuccessfulUnlock();
         sessionEnd();
     }
 
     @Override
     public void setShowingAod(boolean showingAod) {
+        logDebug("REAL: setShowingAod(" + showingAod + ")");
         mShowingAod = showingAod;
         updateSessionActive();
     }
 
-    @Override
-    public void onNotificationStartDraggingDown() {
-    }
-
-    @Override
-    public void onNotificationStopDraggingDown() {
-    }
-
-    @Override
-    public void setNotificationExpanded() {
-    }
-
-    @Override
-    public void onQsDown() {
-    }
-
     @VisibleForTesting
     void onQsExpansionChanged(Boolean expanded) {
+        logDebug("REAL: onQsExpansionChanged(" + expanded + ")");
         if (expanded) {
             unregisterSensors();
         } else if (mSessionStarted) {
@@ -210,39 +193,8 @@
     }
 
     @Override
-    public void onTrackingStarted(boolean secure) {
-    }
-
-    @Override
-    public void onTrackingStopped() {
-    }
-
-    @Override
-    public void onLeftAffordanceOn() {
-    }
-
-    @Override
-    public void onCameraOn() {
-    }
-
-    @Override
-    public void onAffordanceSwipingStarted(boolean rightCorner) {
-    }
-
-    @Override
-    public void onAffordanceSwipingAborted() {
-    }
-
-    @Override
-    public void onStartExpandingFromPulse() {
-    }
-
-    @Override
-    public void onExpansionFromPulseStopped() {
-    }
-
-    @Override
     public void onScreenOnFromTouch() {
+        logDebug("REAL: onScreenOnFromTouch");
         onScreenTurningOn();
     }
 
@@ -252,52 +204,28 @@
     }
 
     @Override
-    public void onUnlockHintStarted() {
-    }
-
-    @Override
-    public void onCameraHintStarted() {
-    }
-
-    @Override
-    public void onLeftAffordanceHintStarted() {
-    }
-
-    @Override
     public void onScreenTurningOn() {
+        logDebug("REAL: onScreenTurningOn");
         mScreenOn = true;
         updateSessionActive();
     }
 
     @Override
     public void onScreenOff() {
+        logDebug("REAL: onScreenOff");
         mScreenOn = false;
         updateSessionActive();
     }
 
     @Override
-    public void onNotificationStopDismissing() {
-    }
-
-    @Override
-    public void onNotificationDismissed() {
-    }
-
-    @Override
-    public void onNotificationStartDismissing() {
-    }
-
-    @Override
-    public void onNotificationDoubleTap(boolean accepted, float dx, float dy) {
-    }
-
-    @Override
     public void onBouncerShown() {
+        logDebug("REAL: onBouncerShown");
         unregisterSensors();
     }
 
     @Override
     public void onBouncerHidden() {
+        logDebug("REAL: onBouncerHidden");
         if (mSessionStarted) {
             registerSensors();
         }
@@ -305,6 +233,7 @@
 
     @Override
     public void onTouchEvent(MotionEvent ev) {
+        logDebug("REAL: onTouchEvent(" + ev.getActionMasked() + ")");
         if (!mKeyguardStateController.isShowing()) {
             avoidGesture();
             return;
@@ -334,6 +263,7 @@
 
     @Override
     public void onMotionEventComplete() {
+        logDebug("REAL: onMotionEventComplete");
         // We must delay processing the completion because of the way Android handles click events.
         // It generally delays executing them immediately, instead choosing to give the UI a chance
         // to respond to touch events before acknowledging the click. As such, we must also delay,
@@ -350,6 +280,7 @@
 
     @Override
     public void avoidGesture() {
+        logDebug("REAL: avoidGesture");
         mAvoidGesture = true;
         if (mPendingDownEvent != null) {
             mPendingDownEvent.recycle();
@@ -359,6 +290,7 @@
 
     @Override
     public void cleanup() {
+        logDebug("REAL: cleanup");
         unregisterSensors();
         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
         mStatusBarStateController.removeCallback(mStatusBarStateListener);
@@ -368,11 +300,13 @@
 
     @Override
     public void updateFalseConfidence(FalsingClassifier.Result result) {
+        logDebug("REAL: updateFalseConfidence(" + result.isFalse() + ")");
         mHistoryTracker.addResults(Collections.singleton(result), mSystemClock.uptimeMillis());
     }
 
     @Override
     public void onA11yAction() {
+        logDebug("REAL: onA11yAction");
         if (mPendingDownEvent != null) {
             mPendingDownEvent.recycle();
             mPendingDownEvent = null;
@@ -427,17 +361,13 @@
 
 
     static void logDebug(String msg) {
-        logDebug(msg, null);
-    }
-
-    static void logDebug(String msg, Throwable throwable) {
         if (DEBUG) {
-            Log.d(TAG, msg, throwable);
+            logDebug(msg);
         }
     }
 
     private static class ProximityEventImpl implements FalsingManager.ProximityEvent {
-        private ThresholdSensorEvent mThresholdSensorEvent;
+        private final ThresholdSensorEvent mThresholdSensorEvent;
 
         ProximityEventImpl(ThresholdSensorEvent thresholdSensorEvent) {
             mThresholdSensorEvent = thresholdSensorEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
new file mode 100644
index 0000000..e5b404f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.classifier
+
+import android.view.MotionEvent
+import com.android.systemui.classifier.FalsingCollectorImpl.logDebug
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class FalsingCollectorNoOp @Inject constructor() : FalsingCollector {
+    override fun onSuccessfulUnlock() {
+        logDebug("NOOP: onSuccessfulUnlock")
+    }
+
+    override fun setShowingAod(showingAod: Boolean) {
+        logDebug("NOOP: setShowingAod($showingAod)")
+    }
+
+    override fun shouldEnforceBouncer(): Boolean = false
+
+    override fun onScreenOnFromTouch() {
+        logDebug("NOOP: onScreenOnFromTouch")
+    }
+
+    override fun isReportingEnabled(): Boolean = false
+
+    override fun onScreenTurningOn() {
+        logDebug("NOOP: onScreenTurningOn")
+    }
+
+    override fun onScreenOff() {
+        logDebug("NOOP: onScreenOff")
+    }
+
+    override fun onBouncerShown() {
+        logDebug("NOOP: onBouncerShown")
+    }
+
+    override fun onBouncerHidden() {
+        logDebug("NOOP: onBouncerHidden")
+    }
+
+    override fun onTouchEvent(ev: MotionEvent) {
+        logDebug("NOOP: onTouchEvent(${ev.actionMasked})")
+    }
+
+    override fun onMotionEventComplete() {
+        logDebug("NOOP: onMotionEventComplete")
+    }
+
+    override fun avoidGesture() {
+        logDebug("NOOP: avoidGesture")
+    }
+
+    override fun cleanup() {
+        logDebug("NOOP: cleanup")
+    }
+
+    override fun updateFalseConfidence(result: FalsingClassifier.Result) {
+        logDebug("NOOP: updateFalseConfidence(${result.isFalse})")
+    }
+
+    override fun onA11yAction() {
+        logDebug("NOOP: onA11yAction")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
index c7f3b2d..3195d09 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
@@ -22,19 +22,21 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.phone.NotificationTapHelper;
 
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.ElementsIntoSet;
+
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
 import javax.inject.Named;
 
-import dagger.Binds;
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.ElementsIntoSet;
-
 /** Dagger Module for Falsing. */
 @Module
 public interface FalsingModule {
@@ -45,10 +47,20 @@
     String DOUBLE_TAP_TIMEOUT_MS = "falsing_double_tap_timeout_ms";
     String IS_FOLDABLE_DEVICE = "falsing_foldable_device";
 
-    /** */
-    @Binds
+    /** Provides the actual {@link FalsingCollector} if the scene container feature is off. */
+    @Provides
     @SysUISingleton
-    FalsingCollector bindsFalsingCollector(FalsingCollectorImpl impl);
+    static FalsingCollector providesFalsingCollectorLegacy(
+            FalsingCollectorImpl impl,
+            FalsingCollectorNoOp noOp,
+            FeatureFlagsClassic featureFlags) {
+        return featureFlags.isEnabled(Flags.SCENE_CONTAINER) ? noOp : impl;
+    }
+
+    /** Provides the actual {@link FalsingCollector}. */
+    @Binds
+    @FalsingCollectorActual
+    FalsingCollector bindsFalsingCollectorActual(FalsingCollectorImpl impl);
 
     /** */
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
new file mode 100644
index 0000000..2e861c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.classifier.domain.interactor
+
+import android.view.MotionEvent
+import com.android.systemui.classifier.FalsingClassifier
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.FalsingCollectorActual
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * Exposes the subset of the [FalsingCollector] API that's required by external callers.
+ *
+ * E.g. methods of [FalsingCollector] that are not exposed by this class don't need to be invoked by
+ * external callers as they're already called by the scene framework.
+ */
+@SysUISingleton
+class FalsingInteractor
+@Inject
+constructor(
+    @FalsingCollectorActual private val collector: FalsingCollector,
+) {
+    /**
+     * Notifies of a [MotionEvent] that passed through the UI.
+     *
+     * Must call [onMotionEventComplete] when done with this event.
+     */
+    fun onTouchEvent(event: MotionEvent) = collector.onTouchEvent(event)
+
+    /**
+     * Notifies that a [MotionEvent] has finished being dispatched through the UI.
+     *
+     * Must be called after each call to [onTouchEvent].
+     */
+    fun onMotionEventComplete() = collector.onMotionEventComplete()
+
+    /**
+     * Instructs the falsing system to ignore the rest of the current input gesture; automatically
+     * resets when another gesture is started (with the next down event).
+     */
+    fun avoidGesture() = collector.avoidGesture()
+
+    /**
+     * Inserts the given [result] into the falsing system, affecting future runs of the classifier
+     * as if this was a result that had organically happened before.
+     */
+    fun updateFalseConfidence(
+        result: FalsingClassifier.Result,
+    ) = collector.updateFalseConfidence(result)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 94e5633..88b9612 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.controls.dagger
 
-import android.content.Context
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
 import com.android.systemui.controls.controller.ControlsController
@@ -44,10 +43,9 @@
 @Inject
 constructor(
     @ControlsFeatureEnabled private val featureEnabled: Boolean,
-    private val context: Context,
-    private val lazyControlsController: Lazy<ControlsController>,
-    private val lazyControlsUiController: Lazy<ControlsUiController>,
-    private val lazyControlsListingController: Lazy<ControlsListingController>,
+    lazyControlsController: Lazy<ControlsController>,
+    lazyControlsUiController: Lazy<ControlsUiController>,
+    lazyControlsListingController: Lazy<ControlsListingController>,
     private val lockPatternUtils: LockPatternUtils,
     private val keyguardStateController: KeyguardStateController,
     private val userTracker: UserTracker,
@@ -55,27 +53,25 @@
     optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration>
 ) {
 
+    private val controlsController: Optional<ControlsController> =
+        optionalIf(isEnabled(), lazyControlsController)
+    private val controlsUiController: Optional<ControlsUiController> =
+        optionalIf(isEnabled(), lazyControlsUiController)
+    private val controlsListingController: Optional<ControlsListingController> =
+        optionalIf(isEnabled(), lazyControlsListingController)
+
     val canShowWhileLockedSetting: StateFlow<Boolean> =
         controlsSettingsRepository.canShowControlsInLockscreen
 
     private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
         optionalControlsTileResourceConfiguration.orElse(ControlsTileResourceConfigurationImpl())
 
-    fun getControlsController(): Optional<ControlsController> {
-        return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
-    }
+    fun getControlsController(): Optional<ControlsController> = controlsController
 
-    fun getControlsUiController(): Optional<ControlsUiController> {
-        return if (featureEnabled) Optional.of(lazyControlsUiController.get()) else Optional.empty()
-    }
+    fun getControlsUiController(): Optional<ControlsUiController> = controlsUiController
 
-    fun getControlsListingController(): Optional<ControlsListingController> {
-        return if (featureEnabled) {
-            Optional.of(lazyControlsListingController.get())
-        } else {
-            Optional.empty()
-        }
-    }
+    fun getControlsListingController(): Optional<ControlsListingController> =
+        controlsListingController
 
     /** @return true if controls are feature-enabled and the user has the setting enabled */
     fun isEnabled() = featureEnabled
@@ -118,4 +114,11 @@
     fun getTileImageId(): Int {
         return controlsTileResourceConfiguration.getTileImageId()
     }
+
+    private fun <T : Any> optionalIf(condition: Boolean, provider: Lazy<T>): Optional<T> =
+        if (condition) {
+            Optional.of(provider.get())
+        } else {
+            Optional.empty()
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 3562477..968c10e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -57,6 +57,7 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentStartableModule;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.AospPolicyModule;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -95,6 +96,7 @@
 @Module(includes = {
         AospPolicyModule.class,
         BatterySaverModule.class,
+        CollapsedStatusBarFragmentStartableModule.class,
         GestureModule.class,
         MediaModule.class,
         MultiUserUtilsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 1b2a9eb..7ce7ce94 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.SliceBroadcastRelayHandler
 import com.android.systemui.accessibility.SystemActions
 import com.android.systemui.accessibility.WindowMagnification
+import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.BiometricNotificationService
 import com.android.systemui.clipboardoverlay.ClipboardListener
@@ -346,4 +347,9 @@
     abstract fun bindStatusBarHeadsUpChangeListener(
         impl: StatusBarHeadsUpChangeListener
     ): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(BackActionInteractor::class)
+    abstract fun bindBackActionInteractor(impl: BackActionInteractor): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index ade7684..96ec654 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -109,7 +109,6 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.LetterboxModule;
 import com.android.systemui.statusbar.phone.NotificationIconAreaControllerModule;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -219,7 +218,6 @@
             WalletModule.class
         },
         subcomponents = {
-            CentralSurfacesComponent.class,
             ComplicationComponent.class,
             NavigationBarComponent.class,
             NotificationRowComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 0c8e293..e7bbf97 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -35,6 +35,7 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.stateIn
 
@@ -48,7 +49,7 @@
      *
      * When `null`, it means there is no pending display waiting to be enabled.
      */
-    val pendingDisplay: Flow<Int?>
+    val pendingDisplayId: Flow<Int?>
 }
 
 @SysUISingleton
@@ -98,7 +99,7 @@
             displayManager.displays?.toSet() ?: emptySet()
         }
 
-    override val pendingDisplay: Flow<Int?> =
+    override val pendingDisplayId: Flow<Int?> =
         conflatedCallbackFlow {
                 val callback =
                     object : DisplayConnectionListener {
@@ -128,6 +129,7 @@
                 )
                 awaitClose { displayManager.unregisterDisplayListener(callback) }
             }
+            .distinctUntilChanged()
             .flowOn(backgroundCoroutineDispatcher)
             .stateIn(
                 applicationScope,
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index 308b7d7..ef6fa26 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -22,9 +22,11 @@
 import com.android.systemui.display.data.repository.DisplayRepository
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.util.traceSection
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 
@@ -65,6 +67,7 @@
 @Inject
 constructor(
     private val displayManager: DisplayManager,
+    keyguardRepository: KeyguardRepository,
     displayRepository: DisplayRepository,
 ) : ConnectedDisplayInteractor {
 
@@ -87,8 +90,17 @@
             }
             .distinctUntilChanged()
 
+    // Provides the pending display only if the lockscreen is unlocked
     override val pendingDisplay: Flow<PendingDisplay?> =
-        displayRepository.pendingDisplay.distinctUntilChanged().map { it?.toPendingDisplay() }
+        displayRepository.pendingDisplayId.combine(keyguardRepository.isKeyguardUnlocked) {
+            pendingDisplayId,
+            keyguardUnlocked ->
+            if (pendingDisplayId != null && keyguardUnlocked) {
+                pendingDisplayId.toPendingDisplay()
+            } else {
+                null
+            }
+        }
 
     private fun Int.toPendingDisplay() =
         object : PendingDisplay {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 42acca2..0fb1b48 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -39,6 +39,10 @@
     @JvmField val TEAMFOOD = unreleasedFlag("teamfood")
 
     // 100 - notification
+    // TODO(b/297792660): Tracking Bug
+    val ADD_TRANSIENT_HUN_IN_STACK_STATE_ANIMATOR =
+        unreleasedFlag("add_transient_hun_in_stack_state_animator", teamfood = false)
+
     // TODO(b/254512751): Tracking Bug
     val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
         unreleasedFlag("notification_pipeline_developer_logging")
@@ -206,7 +210,8 @@
 
     /** Inflate and bind views upon emitting a blueprint value . */
     // TODO(b/297365780): Tracking Bug
-    @JvmField val LAZY_INFLATE_KEYGUARD = unreleasedFlag("lazy_inflate_keyguard")
+    @JvmField
+    val LAZY_INFLATE_KEYGUARD = unreleasedFlag("lazy_inflate_keyguard", teamfood = true)
 
     /** Enables UI updates for AI wallpapers in the wallpaper picker. */
     // TODO(b/267722622): Tracking Bug
@@ -496,11 +501,6 @@
 
     @Keep
     @JvmField
-    val WM_DESKTOP_WINDOWING =
-        sysPropBooleanFlag("persist.wm.debug.desktop_mode", default = false)
-
-    @Keep
-    @JvmField
     val WM_CAPTION_ON_SHELL =
         sysPropBooleanFlag("persist.wm.debug.caption_on_shell", default = true)
 
@@ -768,7 +768,7 @@
 
     /** Enable the share wifi button in Quick Settings internet dialog. */
     @JvmField
-    val SHARE_WIFI_QS_BUTTON = unreleasedFlag("share_wifi_qs_button")
+    val SHARE_WIFI_QS_BUTTON = unreleasedFlag("share_wifi_qs_button", teamfood = true)
 
     /** Enable haptic slider component in the brightness slider */
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
new file mode 100644
index 0000000..629b361
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.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.haptics.slider
+
+import android.widget.SeekBar
+import android.widget.SeekBar.OnSeekBarChangeListener
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+/** An event producer for a Seekable element such as the Android [SeekBar] */
+class SeekableSliderEventProducer : SliderEventProducer, OnSeekBarChangeListener {
+
+    /** The current event reported by a SeekBar */
+    private val _currentEvent = MutableStateFlow(SliderEvent(SliderEventType.NOTHING, 0f))
+
+    override fun produceEvents(): Flow<SliderEvent> = _currentEvent.asStateFlow()
+
+    override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+        val eventType =
+            if (fromUser) SliderEventType.PROGRESS_CHANGE_BY_USER
+            else SliderEventType.PROGRESS_CHANGE_BY_PROGRAM
+
+        _currentEvent.value = SliderEvent(eventType, normalizeProgress(seekBar, progress))
+    }
+
+    /**
+     * Normalize the integer progress of a SeekBar to the range from 0F to 1F.
+     *
+     * @param[seekBar] The SeekBar that reports a progress.
+     * @param[progress] The integer progress of the SeekBar within its min and max values.
+     * @return The progress in the range from 0F to 1F.
+     */
+    private fun normalizeProgress(seekBar: SeekBar, progress: Int): Float {
+        if (seekBar.max == seekBar.min) {
+            return 1.0f
+        }
+        val range = seekBar.max - seekBar.min
+        return (progress - seekBar.min) / range.toFloat()
+    }
+
+    override fun onStartTrackingTouch(seekBar: SeekBar) {
+        _currentEvent.update { previousEvent ->
+            SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, previousEvent.currentProgress)
+        }
+    }
+
+    override fun onStopTrackingTouch(seekBar: SeekBar) {
+        _currentEvent.update { previousEvent ->
+            SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, previousEvent.currentProgress)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEvent.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEvent.kt
new file mode 100644
index 0000000..1377b29
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEvent.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.haptics.slider
+
+import androidx.annotation.FloatRange
+
+/**
+ * An event arising from a slider.
+ *
+ * @property[type] The type of event. Must be one of [SliderEventType].
+ * @property[currentProgress] The current progress of the slider normalized to the range between 0F
+ *   and 1F (inclusive).
+ */
+data class SliderEvent(
+    val type: SliderEventType,
+    @FloatRange(from = 0.0, to = 1.0) val currentProgress: Float
+)
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventProducer.kt
new file mode 100644
index 0000000..8b17e86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventProducer.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.haptics.slider
+
+import kotlinx.coroutines.flow.Flow
+
+/** Defines a producer of [SliderEvent] to be consumed as a [Flow] */
+interface SliderEventProducer {
+
+    /**
+     * Produce a stream of [SliderEvent]
+     *
+     * @return A [Flow] of [SliderEvent] produced from the operation of a slider.
+     */
+    fun produceEvents(): Flow<SliderEvent>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
new file mode 100644
index 0000000..413e277
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
@@ -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.haptics.slider
+
+/** The type of a [SliderEvent]. */
+enum class SliderEventType {
+    /* No event. */
+    NOTHING,
+    /* The slider has captured a touch input and is tracking touch events. */
+    STARTED_TRACKING_TOUCH,
+    /* The slider progress is changing due to user touch input. */
+    PROGRESS_CHANGE_BY_USER,
+    /* The slider progress is changing programmatically. */
+    PROGRESS_CHANGE_BY_PROGRAM,
+    /* The slider has stopped tracking touch events. */
+    STOPPED_TRACKING_TOUCH,
+    /* The external (not touch) stimulus that was modifying the slider progress has stopped. */
+    EXTERNAL_STIMULUS_RELEASE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e983a05..2b4dc81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3207,7 +3207,7 @@
      *                        visible
      */
     public void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean showKeyguard) {
-        Log.d(TAG, "onKeyguardExitRemoteAnimationFinished");
+        Log.d(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation");
         if (!mSurfaceBehindRemoteAnimationRunning && !mSurfaceBehindRemoteAnimationRequested) {
             Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished showKeyguard=" + showKeyguard
                     + " surfaceAnimationRunning=" + mSurfaceBehindRemoteAnimationRunning
@@ -3222,6 +3222,13 @@
 
         // Post layout changes to the next frame, so we don't hang at the end of the animation.
         DejankUtils.postAfterTraversal(() -> {
+            if (!mPM.isInteractive()) {
+                Log.e(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation#postAfterTraversal" +
+                        "Not interactive after traversal. Don't hide the keyguard. This means we " +
+                        "re-locked the device during unlock.");
+                return;
+            }
+
             onKeyguardExitFinished();
 
             if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 42cd3a5..d399e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -158,7 +158,7 @@
     val lastDozeTapToWakePosition: StateFlow<Point?>
 
     /** Observable for the [StatusBarState] */
-    val statusBarState: Flow<StatusBarState>
+    val statusBarState: StateFlow<StatusBarState>
 
     /** Observable for device wake/sleep state */
     val wakefulness: StateFlow<WakefulnessModel>
@@ -520,23 +520,29 @@
         return keyguardBypassController.bypassEnabled
     }
 
-    override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
-        val callback =
-            object : StatusBarStateController.StateListener {
-                override fun onStateChanged(state: Int) {
-                    trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state")
-                }
+    // TODO(b/297345631): Expose this at the interactor level instead so that it can be powered by
+    // [SceneInteractor] when scenes are ready.
+    override val statusBarState: StateFlow<StatusBarState> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : StatusBarStateController.StateListener {
+                        override fun onStateChanged(state: Int) {
+                            trySendWithFailureLogging(
+                                statusBarStateIntToObject(state),
+                                TAG,
+                                "state"
+                            )
+                        }
+                    }
+
+                statusBarStateController.addCallback(callback)
+                awaitClose { statusBarStateController.removeCallback(callback) }
             }
-
-        statusBarStateController.addCallback(callback)
-        trySendWithFailureLogging(
-            statusBarStateIntToObject(statusBarStateController.getState()),
-            TAG,
-            "initial state"
-        )
-
-        awaitClose { statusBarStateController.removeCallback(callback) }
-    }
+            .stateIn(
+                scope,
+                SharingStarted.Eagerly,
+                statusBarStateIntToObject(statusBarStateController.state)
+            )
 
     override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
         fun dispatchUpdate() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index ff0db34..714add4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -50,14 +50,28 @@
         listenForOccludedToAodOrDozing()
         listenForOccludedToGone()
         listenForOccludedToAlternateBouncer()
+        listenForOccludedToPrimaryBouncer()
+    }
+
+    private fun listenForOccludedToPrimaryBouncer() {
+        scope.launch {
+            keyguardInteractor.primaryBouncerShowing
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { (isBouncerShowing, lastStartedTransitionStep) ->
+                    if (
+                        isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.OCCLUDED
+                    ) {
+                        startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+                    }
+                }
+        }
     }
 
     private fun listenForOccludedToDreaming() {
         scope.launch {
             keyguardInteractor.isAbleToDream
                 .sample(transitionInteractor.finishedKeyguardState, ::Pair)
-                .collect { pair ->
-                    val (isAbleToDream, keyguardState) = pair
+                .collect { (isAbleToDream, keyguardState) ->
                     if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
                         startTransitionTo(KeyguardState.DREAMING)
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index e13f675..0c05a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -78,28 +78,37 @@
     /** Position information for the shared notification container. */
     val sharedNotificationContainerPosition =
         MutableStateFlow(SharedNotificationContainerPosition())
+
     /**
      * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
      * all.
      */
     val dozeAmount: Flow<Float> = repository.linearDozeAmount
+
     /** Whether the system is in doze mode. */
     val isDozing: Flow<Boolean> = repository.isDozing
+
     /** Receive an event for doze time tick */
     val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
+
     /** Whether Always-on Display mode is available. */
     val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
+
     /** Doze transition information. */
     val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
+
     /**
      * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
      * but not vice-versa.
      */
     val isDreaming: Flow<Boolean> = repository.isDreaming
+
     /** Whether the system is dreaming with an overlay active */
     val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
+
     /** Whether the system is dreaming and the active dream is hosted in lockscreen */
     val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
+
     /** Event for when the camera gesture is detected */
     val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = conflatedCallbackFlow {
         val callback =
@@ -148,18 +157,25 @@
 
     /** Whether the keyguard is showing or not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+
     /** Whether the keyguard is unlocked or not. */
     val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked
+
     /** Whether the keyguard is occluded (covered by an activity). */
     val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
+
     /** Whether the keyguard is going away. */
     val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
+
     /** Whether the primary bouncer is showing or not. */
     val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
+
     /** Whether the alternate bouncer is showing or not. */
     val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
+
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState> = repository.statusBarState
+
     /**
      * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
      * side, under display) is used to unlock the device.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
index fb685da..c8a04fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
@@ -19,18 +19,20 @@
 import android.os.PowerManager
 
 /** The reason we're waking up or going to sleep, such as pressing the power button. */
-enum class WakeSleepReason {
+enum class WakeSleepReason(
+    val isTouch: Boolean,
+) {
     /** The physical power button was pressed to wake up or sleep the device. */
-    POWER_BUTTON,
+    POWER_BUTTON(isTouch = false),
 
     /** The user has tapped or double tapped to wake the screen. */
-    TAP,
+    TAP(isTouch = true),
 
     /** The user performed some sort of gesture to wake the screen. */
-    GESTURE,
+    GESTURE(isTouch = true),
 
     /** Something else happened to wake up or sleep the device. */
-    OTHER;
+    OTHER(isTouch = false);
 
     companion object {
         fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 2814732..8b0b0ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -78,6 +78,22 @@
                     }
 
                     if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                        launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
+                    }
+
+                    if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                        launch {
+                            viewModel.translationY.collect {
+                                val statusView =
+                                    view.requireViewById<View>(R.id.keyguard_status_view)
+                                statusView.translationY = it
+                            }
+                        }
+                    }
+                }
+
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
                         launch {
                             viewModel.keyguardRootViewVisibilityState.collect { visibilityState ->
                                 view.animate().cancel()
@@ -111,18 +127,6 @@
                                 }
                             }
                         }
-
-                        launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
-                    }
-
-                    if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
-                        launch {
-                            viewModel.translationY.collect {
-                                val statusView =
-                                    view.requireViewById<View>(R.id.keyguard_status_view)
-                                statusView.translationY = it
-                            }
-                        }
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 93c4902..05c9323 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.shared.model.SceneKey
 import javax.inject.Inject
@@ -30,7 +29,7 @@
 @Inject
 constructor(
     authenticationInteractor: AuthenticationInteractor,
-    private val bouncerInteractor: BouncerInteractor,
+    val longPress: KeyguardLongPressViewModel,
 ) {
     /** The key of the scene we should switch to when swiping up. */
     val upDestinationSceneKey: Flow<SceneKey> =
@@ -41,9 +40,4 @@
                 SceneKey.Bouncer
             }
         }
-
-    /** Notifies that the lock button on the lock screen was clicked. */
-    fun onLockButtonClicked() {
-        bouncerInteractor.showOrUnlockDevice()
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 8f884d24..053c9b5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -206,6 +206,11 @@
 
     override fun bind(recentTasks: List<RecentTask>) {
         recentsViewController.bind(recentTasks)
+        if (!hasWorkProfile()) {
+            // Make sure to refresh the adapter, to show/hide the recents view depending on whether
+            // there are recents or not.
+            mMultiProfilePagerAdapter.personalListAdapter.notifyDataSetChanged()
+        }
     }
 
     override fun returnSelectedApp(launchCookie: IBinder) {
@@ -248,9 +253,20 @@
 
     override fun shouldGetOnlyDefaultActivities() = false
 
-    override fun shouldShowContentPreview() = true
+    override fun shouldShowContentPreview() =
+        if (hasWorkProfile()) {
+            // When the user has a work profile, we can always set this to true, and the layout is
+            // adjusted automatically, and hide the recents view.
+            true
+        } else {
+            // When there is no work profile, we should only show the content preview if there are
+            // recents, otherwise the collapsed app selector will look empty.
+            recentsViewController.hasRecentTasks
+        }
 
-    override fun shouldShowContentPreviewWhenEmpty(): Boolean = true
+    override fun shouldShowContentPreviewWhenEmpty() = shouldShowContentPreview()
+
+    private fun hasWorkProfile() = mMultiProfilePagerAdapter.count > 1
 
     override fun createMyUserIdProvider(): MyUserIdProvider =
         object : MyUserIdProvider() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 398dcf2..38a6a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -39,7 +39,6 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.Dumpable
 import com.android.systemui.R
-import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
@@ -59,6 +58,11 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
 import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.shared.system.SysUiStatsLog.SMARTSPACE_CARD_REPORTED
+import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD
+import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY as SSPACE_CARD_REPORTED__DREAM_OVERLAY
+import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN as SSPACE_CARD_REPORTED__LOCKSCREEN
+import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE
 import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -100,7 +104,6 @@
     @Main executor: DelayableExecutor,
     private val mediaManager: MediaDataManager,
     configurationController: ConfigurationController,
-    falsingCollector: FalsingCollector,
     falsingManager: FalsingManager,
     dumpManager: DumpManager,
     private val logger: MediaUiEventLogger,
@@ -122,6 +125,7 @@
 
     /** Is the player currently visible (at the end of the transformation */
     private var playersVisible: Boolean = false
+
     /**
      * The desired location where we'll be at the end of the transformation. Usually this matches
      * the end location, except when we're still waiting on a state update call.
@@ -152,6 +156,7 @@
     @VisibleForTesting var mediaCarousel: MediaScrollView
     val mediaCarouselScrollHandler: MediaCarouselScrollHandler
     val mediaFrame: ViewGroup
+
     @VisibleForTesting
     lateinit var settingsButton: View
         private set
@@ -280,7 +285,6 @@
                 this::updatePageIndicatorLocation,
                 this::updateSeekbarListening,
                 this::closeGuts,
-                falsingCollector,
                 falsingManager,
                 this::logSmartspaceImpression,
                 logger
@@ -327,23 +331,18 @@
                     if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
                         // Log card received if a new resumable media card is added
                         MediaPlayerData.getMediaPlayer(key)?.let {
-                            /* ktlint-disable max-line-length */
                             logSmartspaceCardReported(
                                 759, // SMARTSPACE_CARD_RECEIVED
                                 it.mSmartspaceId,
                                 it.mUid,
                                 surfaces =
                                     intArrayOf(
-                                        SysUiStatsLog
-                                            .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
-                                        SysUiStatsLog
-                                            .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
-                                        SysUiStatsLog
-                                            .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+                                        SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+                                        SSPACE_CARD_REPORTED__LOCKSCREEN,
+                                        SSPACE_CARD_REPORTED__DREAM_OVERLAY,
                                     ),
                                 rank = MediaPlayerData.getMediaPlayerIndex(key)
                             )
-                            /* ktlint-disable max-line-length */
                         }
                         if (
                             mediaCarouselScrollHandler.visibleToUser &&
@@ -362,24 +361,20 @@
                                         it.mUid + systemClock.currentTimeMillis().toInt()
                                     )
                                 it.mIsImpressed = false
-                                /* ktlint-disable max-line-length */
+
                                 logSmartspaceCardReported(
                                     759, // SMARTSPACE_CARD_RECEIVED
                                     it.mSmartspaceId,
                                     it.mUid,
                                     surfaces =
                                         intArrayOf(
-                                            SysUiStatsLog
-                                                .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
-                                            SysUiStatsLog
-                                                .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
-                                            SysUiStatsLog
-                                                .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+                                            SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+                                            SSPACE_CARD_REPORTED__LOCKSCREEN,
+                                            SSPACE_CARD_REPORTED__DREAM_OVERLAY,
                                         ),
                                     rank = index,
                                     receivedLatencyMillis = receivedSmartspaceCardLatency
                                 )
-                                /* ktlint-disable max-line-length */
                             }
                         }
                         // If media container area already visible to the user, log impression for
@@ -431,19 +426,16 @@
                                             it.mUid + systemClock.currentTimeMillis().toInt()
                                         )
                                     it.mIsImpressed = false
-                                    /* ktlint-disable max-line-length */
+
                                     logSmartspaceCardReported(
                                         759, // SMARTSPACE_CARD_RECEIVED
                                         it.mSmartspaceId,
                                         it.mUid,
                                         surfaces =
                                             intArrayOf(
-                                                SysUiStatsLog
-                                                    .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
-                                                SysUiStatsLog
-                                                    .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
-                                                SysUiStatsLog
-                                                    .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+                                                SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+                                                SSPACE_CARD_REPORTED__LOCKSCREEN,
+                                                SSPACE_CARD_REPORTED__DREAM_OVERLAY,
                                             ),
                                         rank = index,
                                         receivedLatencyMillis =
@@ -451,25 +443,20 @@
                                                     data.headphoneConnectionTimeMillis)
                                                 .toInt()
                                     )
-                                    /* ktlint-disable max-line-length */
                                 }
                             }
                         }
                         addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
                         MediaPlayerData.getMediaPlayer(key)?.let {
-                            /* ktlint-disable max-line-length */
                             logSmartspaceCardReported(
                                 759, // SMARTSPACE_CARD_RECEIVED
                                 it.mSmartspaceId,
                                 it.mUid,
                                 surfaces =
                                     intArrayOf(
-                                        SysUiStatsLog
-                                            .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
-                                        SysUiStatsLog
-                                            .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
-                                        SysUiStatsLog
-                                            .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+                                        SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+                                        SSPACE_CARD_REPORTED__LOCKSCREEN,
+                                        SSPACE_CARD_REPORTED__DREAM_OVERLAY,
                                     ),
                                 rank = MediaPlayerData.getMediaPlayerIndex(key),
                                 receivedLatencyMillis =
@@ -477,7 +464,6 @@
                                             data.headphoneConnectionTimeMillis)
                                         .toInt()
                             )
-                            /* ktlint-disable max-line-length */
                         }
                         if (
                             mediaCarouselScrollHandler.visibleToUser &&
@@ -560,7 +546,10 @@
         mediaCarouselScrollHandler.onSettingsButtonUpdated(settings)
         settingsButton.setOnClickListener {
             logger.logCarouselSettings()
-            activityStarter.startActivity(settingsIntent, true /* dismissShade */)
+            activityStarter.startActivity(
+                settingsIntent,
+                /* dismissShade= */ true,
+            )
         }
     }
 
@@ -1108,7 +1097,6 @@
         }
     }
 
-    @JvmOverloads
     /**
      * Log Smartspace events
      *
@@ -1127,6 +1115,7 @@
      *   between headphone connection to sysUI displays media recommendation card
      * @param isSwipeToDismiss whether is to log swipe-to-dismiss event
      */
+    @JvmOverloads
     fun logSmartspaceCardReported(
         eventId: Int,
         instanceId: Int,
@@ -1154,21 +1143,24 @@
 
         val cardinality = mediaContent.getChildCount()
         surfaces.forEach { surface ->
-            /* ktlint-disable max-line-length */
             SysUiStatsLog.write(
-                SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
+                SMARTSPACE_CARD_REPORTED,
                 eventId,
                 instanceId,
                 // Deprecated, replaced with AiAi feature type so we don't need to create logging
                 // card type for each new feature.
-                SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
+                SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
                 surface,
                 // Use -1 as rank value to indicate user swipe to dismiss the card
                 if (isSwipeToDismiss) -1 else rank,
                 cardinality,
-                if (mediaControlKey.isSsMediaRec) 15 // MEDIA_RECOMMENDATION
-                else if (mediaControlKey.isSsReactivated) 43 // MEDIA_RESUME_SS_ACTIVATED
-                else 31, // MEDIA_RESUME
+                if (mediaControlKey.isSsMediaRec) {
+                    15 // MEDIA_RECOMMENDATION
+                } else if (mediaControlKey.isSsReactivated) {
+                    43 // MEDIA_RESUME_SS_ACTIVATED
+                } else {
+                    31
+                }, // MEDIA_RESUME
                 uid,
                 interactedSubcardRank,
                 interactedSubcardCardinality,
@@ -1176,7 +1168,7 @@
                 null, // Media cards cannot have subcards.
                 null // Media cards don't have dimensions today.
             )
-            /* ktlint-disable max-line-length */
+
             if (DEBUG) {
                 Log.d(
                     TAG,
@@ -1259,6 +1251,7 @@
             instanceId = InstanceId.fakeInstanceId(-1),
             appUid = -1
         )
+
     // Whether should prioritize Smartspace card.
     internal var shouldPrioritizeSs: Boolean = false
         private set
@@ -1291,6 +1284,7 @@
 
     private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
     private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
+
     // A map that tracks order of visible media players before they get reordered.
     private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>()
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index ce50a11..bbb61b4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
 import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
-import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
@@ -59,7 +58,6 @@
     private var translationChangedListener: () -> Unit,
     private var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit,
     private val closeGuts: (immediate: Boolean) -> Unit,
-    private val falsingCollector: FalsingCollector,
     private val falsingManager: FalsingManager,
     private val logSmartspaceImpression: (Boolean) -> Unit,
     private val logger: MediaUiEventLogger
@@ -67,8 +65,10 @@
     /** Is the view in RTL */
     val isRtl: Boolean
         get() = scrollView.isLayoutRtl
+
     /** Do we need falsing protection? */
     var falsingProtectionNeeded: Boolean = false
+
     /** The width of the carousel */
     private var carouselWidth: Int = 0
 
@@ -80,6 +80,7 @@
 
     /** The content where the players are added */
     private var mediaContent: ViewGroup
+
     /** The gesture detector to detect touch gestures */
     private val gestureDetector: GestureDetectorCompat
 
@@ -140,9 +141,6 @@
             ) = onScroll(down!!, lastMotion, distanceX)
 
             override fun onDown(e: MotionEvent): Boolean {
-                if (falsingProtectionNeeded) {
-                    falsingCollector.onNotificationStartDismissing()
-                }
                 return false
             }
         }
@@ -263,9 +261,6 @@
 
     private fun onTouch(motionEvent: MotionEvent): Boolean {
         val isUp = motionEvent.action == MotionEvent.ACTION_UP
-        if (isUp && falsingProtectionNeeded) {
-            falsingCollector.onNotificationStopDismissing()
-        }
         if (gestureDetector.onTouchEvent(motionEvent)) {
             if (isUp) {
                 // If this is an up and we're flinging, we don't want to have this touch reach
@@ -482,8 +477,11 @@
         }
         val relativeLocation =
             visibleMediaIndex.toFloat() +
-                if (playerWidthPlusPadding > 0) scrollInAmount.toFloat() / playerWidthPlusPadding
-                else 0f
+                if (playerWidthPlusPadding > 0) {
+                    scrollInAmount.toFloat() / playerWidthPlusPadding
+                } else {
+                    0f
+                }
         // Fix the location, because PageIndicator does not handle RTL internally
         val location =
             if (isRtl) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index 64de9bd..5d732fb 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -35,8 +35,8 @@
 import javax.inject.Inject
 
 /**
- * Controller that handles view of the recent apps selector in the media projection activity.
- * It is responsible for creating and updating recent apps view.
+ * Controller that handles view of the recent apps selector in the media projection activity. It is
+ * responsible for creating and updating recent apps view.
  */
 @MediaProjectionAppSelectorScope
 class MediaProjectionRecentsViewController
@@ -51,15 +51,21 @@
     private var views: Views? = null
     private var lastBoundData: List<RecentTask>? = null
 
+    val hasRecentTasks: Boolean
+        get() = lastBoundData?.isNotEmpty() ?: false
+
     init {
         taskViewSizeProvider.addCallback(this)
     }
 
     fun createView(parent: ViewGroup): ViewGroup =
-        views?.root ?: createRecentViews(parent).also {
-            views = it
-            lastBoundData?.let { recents -> bind(recents) }
-        }.root
+        views?.root
+            ?: createRecentViews(parent)
+                .also {
+                    views = it
+                    lastBoundData?.let { recents -> bind(recents) }
+                }
+                .root
 
     fun bind(recentTasks: List<RecentTask>) {
         views?.apply {
@@ -88,7 +94,8 @@
                 .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
                 as ViewGroup
 
-        val container = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
+        val container =
+            recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
         container.setTaskHeightSize()
 
         val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 5a1ad96..e1e1aae 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -35,11 +35,14 @@
 import android.os.Temperature;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Slog;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.fuelgauge.Estimate;
@@ -51,17 +54,13 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
-import java.util.Optional;
 import java.util.concurrent.Future;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 @SysUISingleton
 public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
 
@@ -107,12 +106,15 @@
     @VisibleForTesting int mBatteryLevel = 100;
     @VisibleForTesting int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
 
+    private boolean mInVrMode;
+
     private IThermalEventListener mSkinThermalEventListener;
     private IThermalEventListener mUsbThermalEventListener;
     private final Context mContext;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final CommandQueue mCommandQueue;
-    private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+    @Nullable
+    private final IVrManager mVrManager;
     private final WakefulnessLifecycle.Observer mWakefulnessObserver =
             new WakefulnessLifecycle.Observer() {
                 @Override
@@ -134,17 +136,28 @@
                 }
             };
 
+    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+        @Override
+        public void onVrStateChanged(boolean enabled) {
+            mInVrMode = enabled;
+        }
+    };
+
     @Inject
-    public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher,
-            CommandQueue commandQueue, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
-            WarningsUI warningsUI, EnhancedEstimates enhancedEstimates,
+    public PowerUI(
+            Context context,
+            BroadcastDispatcher broadcastDispatcher,
+            CommandQueue commandQueue,
+            @Nullable IVrManager vrManager,
+            WarningsUI warningsUI,
+            EnhancedEstimates enhancedEstimates,
             WakefulnessLifecycle wakefulnessLifecycle,
             PowerManager powerManager,
             UserTracker userTracker) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mCommandQueue = commandQueue;
-        mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+        mVrManager = vrManager;
         mWarnings = warningsUI;
         mEnhancedEstimates = enhancedEstimates;
         mPowerManager = powerManager;
@@ -164,7 +177,7 @@
         };
         final ContentResolver resolver = mContext.getContentResolver();
         resolver.registerContentObserver(Settings.Global.getUriFor(
-                Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
+                        Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
                 false, obs, UserHandle.USER_ALL);
         updateBatteryWarningLevels();
         mReceiver.init();
@@ -199,6 +212,14 @@
                 });
         initThermalEventListeners();
         mCommandQueue.addCallback(this);
+
+        if (mVrManager != null) {
+            try {
+                mVrManager.registerListener(mVrStateCallbacks);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to register VR mode state listener: " + e);
+            }
+        }
     }
 
     @Override
@@ -718,10 +739,7 @@
             int status = temp.getStatus();
 
             if (status >= Temperature.THROTTLING_EMERGENCY) {
-                final Optional<CentralSurfaces> centralSurfacesOptional =
-                        mCentralSurfacesOptionalLazy.get();
-                if (!centralSurfacesOptional.map(CentralSurfaces::isDeviceInVrMode)
-                        .orElse(false)) {
+                if (!mInVrMode) {
                     mWarnings.showHighTemperatureWarning();
                     Slog.d(TAG, "SkinThermalEventListener: notifyThrottling was called "
                             + ", current skin status = " + status
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 809edc0..16885ed 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -19,6 +19,7 @@
 
 import android.os.PowerManager
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.FalsingCollectorActual
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -34,7 +35,7 @@
 constructor(
     private val repository: PowerRepository,
     private val keyguardRepository: KeyguardRepository,
-    private val falsingCollector: FalsingCollector,
+    @FalsingCollectorActual private val falsingCollector: FalsingCollector,
     private val screenOffAnimationController: ScreenOffAnimationController,
     private val statusBarStateController: StatusBarStateController,
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt
new file mode 100644
index 0000000..d833e56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.scene.data.repository
+
+import android.os.RemoteException
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.UiBackground
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Source of truth for the visibility of various parts of the window root view. */
+@SysUISingleton
+class WindowRootViewVisibilityRepository
+@Inject
+constructor(
+    private val statusBarService: IStatusBarService,
+    @UiBackground private val uiBgExecutor: Executor,
+) {
+    private val _isLockscreenOrShadeVisible = MutableStateFlow(false)
+    val isLockscreenOrShadeVisible: StateFlow<Boolean> = _isLockscreenOrShadeVisible.asStateFlow()
+
+    fun setIsLockscreenOrShadeVisible(visible: Boolean) {
+        _isLockscreenOrShadeVisible.value = visible
+    }
+
+    /**
+     * Called when the lockscreen or shade has been shown and can be interacted with so that SysUI
+     * can notify external services.
+     */
+    fun onLockscreenOrShadeInteractive(
+        shouldClearNotificationEffects: Boolean,
+        notificationCount: Int,
+    ) {
+        executeServiceCallOnUiBg {
+            statusBarService.onPanelRevealed(shouldClearNotificationEffects, notificationCount)
+        }
+    }
+
+    /**
+     * Called when the lockscreen or shade no longer can be interactecd with so that SysUI can
+     * notify external services.
+     */
+    fun onLockscreenOrShadeNotInteractive() {
+        executeServiceCallOnUiBg { statusBarService.onPanelHidden() }
+    }
+
+    private fun executeServiceCallOnUiBg(runnable: () -> Unit) {
+        uiBgExecutor.execute {
+            try {
+                runnable.invoke()
+            } catch (ex: RemoteException) {
+                // Won't fail unless the world has ended
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
new file mode 100644
index 0000000..16ffcc2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.scene.domain.interactor
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Business logic about the visibility of various parts of the window root view. */
+@SysUISingleton
+class WindowRootViewVisibilityInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val windowRootViewVisibilityRepository: WindowRootViewVisibilityRepository,
+    private val keyguardRepository: KeyguardRepository,
+    private val headsUpManager: HeadsUpManager,
+) : CoreStartable {
+
+    private var notificationPresenter: NotificationPresenter? = null
+    private var notificationsController: NotificationsController? = null
+
+    private val isNotifPresenterFullyCollapsed: Boolean
+        get() = notificationPresenter?.isPresenterFullyCollapsed ?: true
+
+    /**
+     * True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably,
+     * false if the bouncer is visible.
+     *
+     * TODO(b/297080059): Use [SceneInteractor] as the source of truth if the scene flag is on.
+     */
+    val isLockscreenOrShadeVisible: StateFlow<Boolean> =
+        windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+
+    /**
+     * True if lockscreen (including AOD) or the shade is visible **and** the user is currently
+     * interacting with the device, false otherwise. Notably, false if the bouncer is visible and
+     * false if the device is asleep.
+     */
+    val isLockscreenOrShadeVisibleAndInteractive: StateFlow<Boolean> =
+        combine(
+                isLockscreenOrShadeVisible,
+                keyguardRepository.wakefulness,
+            ) { isKeyguardAodOrShadeVisible, wakefulness ->
+                isKeyguardAodOrShadeVisible && wakefulness.isDeviceInteractive()
+            }
+            .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
+
+    /**
+     * Sets classes that aren't easily injectable on this class.
+     *
+     * TODO(b/277762009): Inject these directly instead.
+     */
+    fun setUp(
+        presenter: NotificationPresenter?,
+        notificationsController: NotificationsController?,
+    ) {
+        this.notificationPresenter = presenter
+        this.notificationsController = notificationsController
+    }
+
+    override fun start() {
+        scope.launch {
+            isLockscreenOrShadeVisibleAndInteractive.collect { interactive ->
+                if (interactive) {
+                    windowRootViewVisibilityRepository.onLockscreenOrShadeInteractive(
+                        getShouldClearNotificationEffects(keyguardRepository.statusBarState.value),
+                        getNotificationLoad(),
+                    )
+                } else {
+                    windowRootViewVisibilityRepository.onLockscreenOrShadeNotInteractive()
+                }
+            }
+        }
+    }
+
+    fun setIsLockscreenOrShadeVisible(visible: Boolean) {
+        windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(visible)
+    }
+
+    private fun getShouldClearNotificationEffects(statusBarState: StatusBarState): Boolean {
+        return !isNotifPresenterFullyCollapsed &&
+            (statusBarState == StatusBarState.SHADE ||
+                statusBarState == StatusBarState.SHADE_LOCKED)
+    }
+
+    private fun getNotificationLoad(): Int {
+        return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) {
+            1
+        } else {
+            notificationsController?.getActiveNotificationsCount() ?: 0
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 1747099..7f77acc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -14,11 +14,15 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.scene.domain.startable
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.FalsingCollectorActual
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.DisplayId
@@ -40,7 +44,12 @@
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.launch
@@ -61,6 +70,7 @@
     private val sysUiState: SysUiState,
     @DisplayId private val displayId: Int,
     private val sceneLogger: SceneLogger,
+    @FalsingCollectorActual private val falsingCollector: FalsingCollector,
 ) : CoreStartable {
 
     override fun start() {
@@ -69,6 +79,7 @@
             hydrateVisibility()
             automaticallySwitchScenes()
             hydrateSystemUiState()
+            collectFalsingSignals()
         } else {
             sceneLogger.logFrameworkEnabled(isEnabled = false)
         }
@@ -225,6 +236,66 @@
         }
     }
 
+    /** Collects and reports signals into the falsing system. */
+    private fun collectFalsingSignals() {
+        applicationScope.launch {
+            authenticationInteractor.isLockscreenDismissed.collect { isLockscreenDismissed ->
+                if (isLockscreenDismissed) {
+                    falsingCollector.onSuccessfulUnlock()
+                }
+            }
+        }
+
+        applicationScope.launch {
+            keyguardInteractor.isDozing.distinctUntilChanged().collect { isDozing ->
+                falsingCollector.setShowingAod(isDozing)
+            }
+        }
+
+        applicationScope.launch {
+            keyguardInteractor.isAodAvailable
+                .flatMapLatest { isAodAvailable ->
+                    if (!isAodAvailable) {
+                        keyguardInteractor.wakefulnessModel
+                    } else {
+                        emptyFlow()
+                    }
+                }
+                .map { wakefulnessModel ->
+                    val wakeChange: Boolean? =
+                        when (wakefulnessModel.state) {
+                            WakefulnessState.STARTING_TO_WAKE -> true
+                            WakefulnessState.ASLEEP -> false
+                            else -> null
+                        }
+                    (wakeChange to wakefulnessModel.lastWakeReason).takeIf { wakeChange != null }
+                }
+                .filterNotNull()
+                .distinctUntilChangedBy { it.first }
+                .collect { (wakeChange, wakeReason) ->
+                    when {
+                        wakeChange == true && wakeReason.isTouch ->
+                            falsingCollector.onScreenOnFromTouch()
+                        wakeChange == true -> falsingCollector.onScreenTurningOn()
+                        wakeChange == false -> falsingCollector.onScreenOff()
+                    }
+                }
+        }
+
+        applicationScope.launch {
+            sceneInteractor.desiredScene
+                .map { it.key == SceneKey.Bouncer }
+                .distinctUntilChanged()
+                .collect { switchedToBouncerScene ->
+                    if (switchedToBouncerScene) {
+                        falsingCollector.onBouncerShown()
+                    } else {
+                        falsingCollector.onBouncerHidden()
+                    }
+                }
+        }
+    }
+
     private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
         sceneInteractor.changeScene(
             scene = SceneModel(targetSceneKey),
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
index 8da1803..fcfdceb 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.scene.domain.startable
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -29,4 +30,11 @@
     @IntoMap
     @ClassKey(SceneContainerStartable::class)
     fun bind(impl: SceneContainerStartable): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(WindowRootViewVisibilityInteractor::class)
+    fun bindWindowRootViewVisibilityInteractor(
+        impl: WindowRootViewVisibilityInteractor
+    ): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index f9324a9..3e76607 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -110,6 +110,7 @@
     //  SysUI altogether.
     private fun createVisibilityToggleView(otherView: View): View {
         val toggleView = View(otherView.context)
+        otherView.isVisible = false
         toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL)
         toggleView.setOnClickListener {
             val now = Instant.now()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index c9a73e6..ef688a8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.scene.ui.view
 
 import android.annotation.SuppressLint
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index c5b605a..2431660 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.scene.ui.viewmodel
 
+import android.view.MotionEvent
+import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.ObservableTransitionState
@@ -30,7 +32,8 @@
 class SceneContainerViewModel
 @Inject
 constructor(
-    private val interactor: SceneInteractor,
+    private val sceneInteractor: SceneInteractor,
+    private val falsingInteractor: FalsingInteractor,
 ) {
     /**
      * Keys of all scenes in the container.
@@ -38,17 +41,17 @@
      * The scenes will be sorted in z-order such that the last one is the one that should be
      * rendered on top of all previous ones.
      */
-    val allSceneKeys: List<SceneKey> = interactor.allSceneKeys()
+    val allSceneKeys: List<SceneKey> = sceneInteractor.allSceneKeys()
 
     /** The scene that should be rendered. */
-    val currentScene: StateFlow<SceneModel> = interactor.desiredScene
+    val currentScene: StateFlow<SceneModel> = sceneInteractor.desiredScene
 
     /** Whether the container is visible. */
-    val isVisible: StateFlow<Boolean> = interactor.isVisible
+    val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible
 
     /** Notifies that the UI has transitioned sufficiently to the given scene. */
     fun onSceneChanged(scene: SceneModel) {
-        interactor.onSceneChanged(
+        sceneInteractor.onSceneChanged(
             scene = scene,
             loggingReason = SCENE_TRANSITION_LOGGING_REASON,
         )
@@ -60,12 +63,27 @@
      * Note that you must call is with `null` when the UI is done or risk a memory leak.
      */
     fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
-        interactor.setTransitionState(transitionState)
+        sceneInteractor.setTransitionState(transitionState)
     }
 
-    /** Handles an event representing user input. */
-    fun onUserInput() {
-        interactor.onUserInput()
+    /**
+     * Notifies that a [MotionEvent] is first seen at the top of the scene container UI.
+     *
+     * Call this before the [MotionEvent] starts to propagate through the UI hierarchy.
+     */
+    fun onMotionEvent(event: MotionEvent) {
+        sceneInteractor.onUserInput()
+        falsingInteractor.onTouchEvent(event)
+    }
+
+    /**
+     * Notifies that a [MotionEvent] that was previously sent to [onMotionEvent] has passed through
+     * the scene container UI.
+     *
+     * Call this after the [MotionEvent] propagates completely through the UI hierarchy.
+     */
+    fun onMotionEventComplete() {
+        falsingInteractor.onMotionEventComplete()
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 3d5f9e0..8db7abf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2823,7 +2823,6 @@
     }
 
     private void onTrackingStarted() {
-        mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
         endClosing();
         mTracking = true;
         mTrackingStartedListener.onTrackingStarted();
@@ -2839,7 +2838,6 @@
     }
 
     private void onTrackingStopped(boolean expand) {
-        mFalsingCollector.onTrackingStopped();
         mTracking = false;
         maybeStopTrackingExpansionFromStatusBar(expand);
 
@@ -2893,7 +2891,6 @@
 
     @VisibleForTesting
     void onUnlockHintStarted() {
-        mFalsingCollector.onUnlockHintStarted();
         mKeyguardIndicationController.showActionToUnlock();
         mScrimController.setExpansionAffectsAlpha(false);
         mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 54b806d..0f85c76 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -35,6 +35,7 @@
 import com.android.keyguard.KeyguardMessageAreaController;
 import com.android.keyguard.LockIconViewController;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.back.domain.interactor.BackActionInteractor;
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
@@ -43,6 +44,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor;
@@ -83,7 +85,7 @@
  * Controller for {@link NotificationShadeWindowView}.
  */
 @SysUISingleton
-public class NotificationShadeWindowViewController {
+public class NotificationShadeWindowViewController implements Dumpable {
     private static final String TAG = "NotifShadeWindowVC";
     private final FalsingCollector mFalsingCollector;
     private final SysuiStatusBarStateController mStatusBarStateController;
@@ -166,6 +168,7 @@
             NotificationInsetsController notificationInsetsController,
             AmbientState ambientState,
             ShadeLogger shadeLogger,
+            DumpManager dumpManager,
             PulsingGestureListener pulsingGestureListener,
             LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
             KeyguardBouncerViewModel keyguardBouncerViewModel,
@@ -235,6 +238,7 @@
         }
 
         lockIconViewController.setLockIconView(mView.findViewById(R.id.lock_icon_view));
+        dumpManager.registerDumpable(this);
     }
 
     /**
@@ -542,6 +546,7 @@
         mAmbientState.setSwipingUp(false);
     }
 
+    @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.print("  mExpandAnimationRunning=");
         pw.println(mExpandAnimationRunning);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index c9c911b..b2bbffd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -67,7 +67,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.classifier.Classifier;
-import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
@@ -147,7 +146,6 @@
     private final MediaHierarchyManager mMediaHierarchyManager;
     private final AmbientState mAmbientState;
     private final RecordingController mRecordingController;
-    private final FalsingCollector mFalsingCollector;
     private final LockscreenGestureLogger mLockscreenGestureLogger;
     private final ShadeLogger mShadeLog;
     private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
@@ -335,7 +333,6 @@
             AmbientState ambientState,
             RecordingController recordingController,
             FalsingManager falsingManager,
-            FalsingCollector falsingCollector,
             AccessibilityManager accessibilityManager,
             LockscreenGestureLogger lockscreenGestureLogger,
             MetricsLogger metricsLogger,
@@ -381,7 +378,6 @@
         mAmbientState = ambientState;
         mRecordingController = recordingController;
         mFalsingManager = falsingManager;
-        mFalsingCollector = falsingCollector;
         mAccessibilityManager = accessibilityManager;
 
         mLockscreenGestureLogger = lockscreenGestureLogger;
@@ -1660,7 +1656,6 @@
                 }
             }
             if (shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
-                mFalsingCollector.onQsDown();
                 mShadeLog.logMotionEvent(event,
                         "handleQsDown: down action, QS tracking enabled");
                 mTracking = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d7a3392..9a3e4e5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -30,6 +30,7 @@
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.dagger.ShadeTouchLog;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -59,6 +60,7 @@
     private final CommandQueue mCommandQueue;
     private final Executor mMainExecutor;
     private final LogBuffer mTouchLog;
+    private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarStateController mStatusBarStateController;
@@ -83,6 +85,7 @@
             CommandQueue commandQueue,
             @Main Executor mainExecutor,
             @ShadeTouchLog LogBuffer touchLog,
+            WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
             KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -97,6 +100,7 @@
         mCommandQueue = commandQueue;
         mMainExecutor = mainExecutor;
         mTouchLog = touchLog;
+        mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
         mShadeViewControllerLazy = shadeViewControllerLazy;
         mStatusBarStateController = statusBarStateController;
         mStatusBarWindowController = statusBarWindowController;
@@ -391,6 +395,7 @@
 
     private void notifyVisibilityChanged(boolean visible) {
         mShadeVisibilityListener.visibilityChanged(visible);
+        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(visible);
     }
 
     private void notifyExpandedVisibleChanged(boolean expandedVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 2532bad..b553f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -99,9 +99,6 @@
     /** Sets the view's alpha to max. */
     fun resetAlpha()
 
-    /** Sets progress of the predictive back animation. */
-    fun onBackProgressed(progressFraction: Float)
-
     /** @see com.android.systemui.keyguard.ScreenLifecycle.Observer.onScreenTurningOn */
     fun onScreenTurningOn()
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 182a676..1121834 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -155,6 +155,9 @@
     /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
     fun onBackPressed()
 
+    /** Sets progress of the predictive back animation. */
+    fun onBackProgressed(progressFraction: Float)
+
     /** Sets whether the status bar launch animation is currently running. */
     fun setIsLaunchAnimationRunning(running: Boolean)
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 09b74b2..6a2bef2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -58,6 +58,7 @@
         return false
     }
     override fun onBackPressed() {}
+    override fun onBackProgressed(progressFraction: Float) {}
     override fun setIsLaunchAnimationRunning(running: Boolean) {}
     override fun setAlpha(alpha: Int, animate: Boolean) {}
     override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 25bb204..f004982 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -81,6 +81,7 @@
     private val powerInteractor: PowerInteractor,
 ) : Dumpable {
     private var pulseHeight: Float = 0f
+
     @get:VisibleForTesting
     var fractionToShade: Float = 0f
         private set
@@ -255,17 +256,17 @@
 
     private fun updateResources() {
         fullTransitionDistance = context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_full_transition_distance)
+            R.dimen.lockscreen_shade_full_transition_distance)
         fullTransitionDistanceByTap = context.resources.getDimensionPixelSize(
             R.dimen.lockscreen_shade_transition_by_tap_distance)
         notificationShelfTransitionDistance = context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_notif_shelf_transition_distance)
+            R.dimen.lockscreen_shade_notif_shelf_transition_distance)
         depthControllerTransitionDistance = context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_depth_controller_transition_distance)
+            R.dimen.lockscreen_shade_depth_controller_transition_distance)
         udfpsTransitionDistance = context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_udfps_keyguard_transition_distance)
+            R.dimen.lockscreen_shade_udfps_keyguard_transition_distance)
         statusBarTransitionDistance = context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_status_bar_transition_distance)
+            R.dimen.lockscreen_shade_status_bar_transition_distance)
         useSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources)
     }
 
@@ -292,8 +293,8 @@
      */
     internal fun canDragDown(): Boolean {
         return (statusBarStateController.state == StatusBarState.KEYGUARD ||
-                nsslController.isInLockedDownShade()) &&
-                (qS.isFullyCollapsed || useSplitShade)
+            nsslController.isInLockedDownShade()) &&
+            (qS.isFullyCollapsed || useSplitShade)
     }
 
     /**
@@ -308,10 +309,15 @@
             if (nsslController.isInLockedDownShade()) {
                 logger.logDraggedDownLockDownShade(startingChild)
                 statusBarStateController.setLeaveOpenOnKeyguardHide(true)
-                activityStarter.dismissKeyguardThenExecute(OnDismissAction {
-                    nextHideKeyguardNeedsNoAnimation = true
-                    false
-                }, cancelRunnable, false /* afterKeyguardGone */)
+                activityStarter.dismissKeyguardThenExecute(
+                    {
+                        nextHideKeyguardNeedsNoAnimation = true
+                        false
+                    },
+                    cancelRunnable,
+                    /* afterKeyguardGone= */
+                    false,
+                )
             } else {
                 logger.logDraggedDown(startingChild, dragLengthY)
                 if (!ambientState.isDozing() || startingChild != null) {
@@ -320,11 +326,18 @@
                     val animationHandler = { delay: Long ->
                         if (startingChild is ExpandableNotificationRow) {
                             startingChild.onExpandedByGesture(
-                                    true /* drag down is always an open */)
+                                true /* drag down is always an open */
+                            )
                         }
                         shadeViewController.transitionToExpandedShade(delay)
-                        callbacks.forEach { it.setTransitionToFullShadeAmount(0f,
-                                true /* animated */, delay) }
+                        callbacks.forEach {
+                            it.setTransitionToFullShadeAmount(
+                                0f,
+                                /* animated= */
+                                true,
+                                delay
+                            )
+                        }
 
                         // Let's reset ourselves, ready for the next animation
 
@@ -350,7 +363,12 @@
      */
     internal fun onDragDownReset() {
         logger.logDragDownAborted()
-        nsslController.setDimmed(true /* dimmed */, true /* animated */)
+        nsslController.setDimmed(
+            /* dimmed= */
+            true,
+            /* animate= */
+            true,
+        )
         nsslController.resetScrollPosition()
         nsslController.resetCheckSnoozeLeavebehind()
         setDragDownAmountAnimated(0f)
@@ -361,7 +379,12 @@
      * @param above whether they dragged above it
      */
     internal fun onCrossedThreshold(above: Boolean) {
-        nsslController.setDimmed(!above /* dimmed */, true /* animate */)
+        nsslController.setDimmed(
+            /* dimmed= */
+            !above,
+            /* animate= */
+            true,
+        )
     }
 
     /**
@@ -411,8 +434,8 @@
      */
     internal val isDragDownAnywhereEnabled: Boolean
         get() = (statusBarStateController.getState() == StatusBarState.KEYGUARD &&
-                !keyguardBypassController.bypassEnabled &&
-                (qS.isFullyCollapsed || useSplitShade))
+            !keyguardBypassController.bypassEnabled &&
+            (qS.isFullyCollapsed || useSplitShade))
 
     /**
      * The amount in pixels that the user has dragged down.
@@ -429,8 +452,15 @@
 
                     qsTransitionController.dragDownAmount = value
 
-                    callbacks.forEach { it.setTransitionToFullShadeAmount(field,
-                            false /* animate */, 0 /* delay */) }
+                    callbacks.forEach {
+                        it.setTransitionToFullShadeAmount(
+                            field,
+                            /* animate= */
+                            false,
+                            /* delay= */
+                            0,
+                        )
+                    }
 
                     mediaHierarchyManager.setTransitionToFullShadeAmount(field)
                     scrimTransitionController.dragDownAmount = value
@@ -538,8 +568,11 @@
                     shadeViewController.transitionToExpandedShade(delay)
                 }
             }
-            goToLockedShadeInternal(expandedView, animationHandler,
-                    cancelAction = null)
+            goToLockedShadeInternal(
+                expandedView,
+                animationHandler,
+                cancelAction = null
+            )
         }
     }
 
@@ -570,14 +603,19 @@
         var entry: NotificationEntry? = null
         if (expandView is ExpandableNotificationRow) {
             entry = expandView.entry
-            entry.setUserExpanded(true /* userExpanded */, true /* allowChildExpansion */)
+            entry.setUserExpanded(
+                /* userExpanded= */
+                true,
+                /* allowChildExpansion= */
+                true,
+            )
             // Indicate that the group expansion is changing at this time -- this way the group
             // and children backgrounds / divider animations will look correct.
             entry.setGroupExpansionChanging(true)
             userId = entry.sbn.userId
         }
         var fullShadeNeedsBouncer = (
-                !lockScreenUserManager.shouldShowLockscreenNotifications() ||
+            !lockScreenUserManager.shouldShowLockscreenNotifications() ||
                 falsingCollector.shouldEnforceBouncer())
         if (keyguardBypassController.bypassEnabled) {
             fullShadeNeedsBouncer = false
@@ -596,7 +634,10 @@
             val cancelHandler = Runnable {
                 draggedDownEntry?.apply {
                     setUserLocked(false)
-                    notifyHeightChanged(false /* needsAnimation */)
+                    notifyHeightChanged(
+                        /* needsAnimation= */
+                        false,
+                    )
                     draggedDownEntry = null
                 }
                 cancelAction?.run()
@@ -614,7 +655,10 @@
             // This call needs to be after updating the shade state since otherwise
             // the scrimstate resets too early
             if (animationHandler != null) {
-                animationHandler.invoke(0 /* delay */)
+                animationHandler.invoke(
+                    /* delay= */
+                    0,
+                )
             } else {
                 performDefaultGoToFullShadeAnimation(0)
             }
@@ -721,12 +765,13 @@
             it.println("isDragDownAnywhereEnabled: $isDragDownAnywhereEnabled")
             it.println("isFalsingCheckNeeded: $isFalsingCheckNeeded")
             it.println("isWakingToShadeLocked: $isWakingToShadeLocked")
-            it.println("hasPendingHandlerOnKeyguardDismiss: " +
-                "${animationHandlerOnKeyguardDismiss != null}")
+            it.println(
+                "hasPendingHandlerOnKeyguardDismiss: " +
+                    "${animationHandlerOnKeyguardDismiss != null}"
+            )
         }
     }
 
-
     fun addCallback(callback: Callback) {
         if (!callbacks.contains(callback)) {
             callbacks.add(callback)
@@ -791,7 +836,7 @@
 
     fun updateResources(context: Context) {
         minDragDistance = context.resources.getDimensionPixelSize(
-                R.dimen.keyguard_drag_down_min_distance)
+            R.dimen.keyguard_drag_down_min_distance)
         val configuration = ViewConfiguration.get(context)
         touchSlop = configuration.scaledTouchSlop.toFloat()
         slopMultiplier = configuration.scaledAmbiguousGestureMultiplier
@@ -808,16 +853,17 @@
                 initialTouchY = y
                 initialTouchX = x
             }
+
             MotionEvent.ACTION_MOVE -> {
                 val h = y - initialTouchY
                 // Adjust the touch slop if another gesture may be being performed.
                 val touchSlop = if (event.classification
-                        == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE)
+                    == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) {
                     touchSlop * slopMultiplier
-                else
+                } else {
                     touchSlop
+                }
                 if (h > touchSlop && h > Math.abs(x - initialTouchX)) {
-                    falsingCollector.onNotificationStartDraggingDown()
                     isDraggingDown = true
                     captureStartingChild(initialTouchX, initialTouchY)
                     initialTouchY = y
@@ -858,8 +904,9 @@
                 }
                 return true
             }
+
             MotionEvent.ACTION_UP -> if (!falsingManager.isUnlockingDisabled && !isFalseTouch &&
-                    dragDownCallback.canDragDown()) {
+                dragDownCallback.canDragDown()) {
                 dragDownCallback.onDraggedDown(startingChild, (y - initialTouchY).toInt())
                 if (startingChild != null) {
                     expandCallback.setUserLockedChild(startingChild, false)
@@ -870,6 +917,7 @@
                 stopDragging()
                 return false
             }
+
             MotionEvent.ACTION_CANCEL -> {
                 stopDragging()
                 return false
@@ -913,8 +961,8 @@
 
     @VisibleForTesting
     fun cancelChildExpansion(
-            child: ExpandableView,
-            animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS
+        child: ExpandableView,
+        animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS
     ) {
         if (child.actualHeight == child.collapsedHeight) {
             expandCallback.setUserLockedChild(child, false)
@@ -936,7 +984,6 @@
     }
 
     private fun stopDragging() {
-        falsingCollector.onNotificationStopDraggingDown()
         if (startingChild != null) {
             cancelChildExpansion(startingChild!!)
             startingChild = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index fc6eaa8..2c0741e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -28,10 +28,10 @@
 import android.view.VelocityTracker
 import android.view.ViewConfiguration
 import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
 import com.android.systemui.Dumpable
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
 import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
@@ -76,6 +76,7 @@
     companion object {
         private val SPRING_BACK_ANIMATION_LENGTH_MS = 375
     }
+
     private val mPowerManager: PowerManager?
 
     private var mInitialTouchX: Float = 0.0f
@@ -94,7 +95,10 @@
                         pulseExpandAbortListener?.run()
                     }
                 }
-                headsUpManager.unpinAll(true /* userUnPinned */)
+                headsUpManager.unpinAll(
+                    /*userUnPinned= */
+                    true,
+                )
             }
         }
     var leavingLockscreen: Boolean = false
@@ -168,7 +172,6 @@
             MotionEvent.ACTION_MOVE -> {
                 val h = y - mInitialTouchY
                 if (h > touchSlop && h > Math.abs(x - mInitialTouchX)) {
-                    falsingCollector.onStartExpandingFromPulse()
                     isExpanding = true
                     captureStartingChild(mInitialTouchX, mInitialTouchY)
                     mInitialTouchY = y
@@ -200,14 +203,14 @@
             event.action == MotionEvent.ACTION_UP) && isExpanding
 
         val isDraggingNotificationOrCanBypass = mStartingChild?.showingPulsing() == true ||
-                bypassController.canBypass()
+            bypassController.canBypass()
         if ((!canHandleMotionEvent() || !isDraggingNotificationOrCanBypass) && !finishExpanding) {
             // We allow cancellations/finishing to still go through here to clean up the state
             return false
         }
 
         if (velocityTracker == null || !isExpanding ||
-                event.actionMasked == MotionEvent.ACTION_DOWN) {
+            event.actionMasked == MotionEvent.ACTION_DOWN) {
             return startExpansion(event)
         }
         velocityTracker!!.addMovement(event)
@@ -217,9 +220,12 @@
         when (event.actionMasked) {
             MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
             MotionEvent.ACTION_UP -> {
-                velocityTracker!!.computeCurrentVelocity(1000 /* units */)
+                velocityTracker!!.computeCurrentVelocity(
+                    /* units= */
+                    1000,
+                )
                 val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
-                        statusBarStateController.state != StatusBarState.SHADE
+                    statusBarStateController.state != StatusBarState.SHADE
                 if (!falsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
                     finishExpansion()
                 } else {
@@ -227,6 +233,7 @@
                 }
                 recycleVelocityTracker()
             }
+
             MotionEvent.ACTION_CANCEL -> {
                 cancelExpansion()
                 recycleVelocityTracker()
@@ -243,17 +250,25 @@
         }
         if (statusBarStateController.isDozing) {
             wakeUpCoordinator.willWakeUp = true
-            mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
-                    "com.android.systemui:PULSEDRAG")
+            mPowerManager!!.wakeUp(
+                SystemClock.uptimeMillis(),
+                PowerManager.WAKE_REASON_GESTURE,
+                "com.android.systemui:PULSEDRAG"
+            )
         }
-        lockscreenShadeTransitionController.goToLockedShade(startingChild,
-                needsQSAnimation = false)
+        lockscreenShadeTransitionController.goToLockedShade(
+            startingChild,
+            needsQSAnimation = false
+        )
         lockscreenShadeTransitionController.finishPulseAnimation(cancelled = false)
         leavingLockscreen = true
         isExpanding = false
         if (mStartingChild is ExpandableNotificationRow) {
             val row = mStartingChild as ExpandableNotificationRow?
-            row!!.onExpandedByGesture(true /* userExpanded */)
+            row!!.onExpandedByGesture(
+                /*userExpanded= */
+                true,
+            )
         }
     }
 
@@ -261,15 +276,20 @@
         var expansionHeight = max(height, 0.0f)
         if (mStartingChild != null) {
             val child = mStartingChild!!
-            val newHeight = Math.min((child.collapsedHeight + expansionHeight).toInt(),
-                    child.maxContentHeight)
+            val newHeight = Math.min(
+                (child.collapsedHeight + expansionHeight).toInt(),
+                child.maxContentHeight
+            )
             child.actualHeight = newHeight
         } else {
             wakeUpCoordinator.setNotificationsVisibleForExpansion(
                 height
                     > lockscreenShadeTransitionController.distanceUntilShowingPulsingNotifications,
-                true /* animate */,
-                true /* increaseSpeed */)
+                /*animate= */
+                true,
+                /*increaseSpeed= */
+                true
+            )
         }
         lockscreenShadeTransitionController.setPulseHeight(expansionHeight, animate = false)
     }
@@ -285,8 +305,8 @@
 
     @VisibleForTesting
     fun reset(
-            child: ExpandableView,
-            animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
+        child: ExpandableView,
+        animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
     ) {
         if (child.actualHeight == child.collapsedHeight) {
             setUserLocked(child, false)
@@ -315,15 +335,19 @@
 
     private fun cancelExpansion() {
         isExpanding = false
-        falsingCollector.onExpansionFromPulseStopped()
         if (mStartingChild != null) {
             reset(mStartingChild!!)
             mStartingChild = null
         }
         lockscreenShadeTransitionController.finishPulseAnimation(cancelled = true)
-        wakeUpCoordinator.setNotificationsVisibleForExpansion(false /* visible */,
-                true /* animate */,
-                false /* increaseSpeed */)
+        wakeUpCoordinator.setNotificationsVisibleForExpansion(
+            /*visible= */
+            false,
+            /*animate= */
+            true,
+            /*increaseSpeed= */
+            false
+        )
     }
 
     private fun findView(x: Float, y: Float): ExpandableView? {
@@ -335,7 +359,9 @@
         val childAtRawPosition = stackScrollerController.getChildAtRawPosition(totalX, totalY)
         return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) {
             childAtRawPosition
-        } else null
+        } else {
+            null
+        }
     }
 
     fun setUp(stackScrollerController: NotificationStackScrollLayoutController) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 2ad71e7..80274bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import java.lang.IllegalStateException
 import javax.inject.Inject
+import javax.inject.Provider
 
 /**
  * Responsible for creating the status bar window and initializing the root components of that
@@ -35,6 +36,7 @@
 @SysUISingleton
 class StatusBarInitializer @Inject constructor(
     private val windowController: StatusBarWindowController,
+    private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
     private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
 ) {
 
@@ -43,11 +45,9 @@
     /**
      * Creates the status bar window and root views, and initializes the component.
      *
-     * TODO(b/277762009): Inject StatusBarFragmentCreator and make this class a CoreStartable.
+     * TODO(b/277764509): Initialize the status bar via [CoreStartable#start].
      */
-    fun initializeStatusBar(
-        statusBarFragmentCreator: () -> CollapsedStatusBarFragment,
-    ) {
+    fun initializeStatusBar() {
         windowController.fragmentHostManager.addTagListener(
                 CollapsedStatusBarFragment.TAG,
                 object : FragmentHostManager.FragmentListener {
@@ -67,11 +67,14 @@
                     override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
                         // nop
                     }
-                }).fragmentManager
+                }
+        ).fragmentManager
                 .beginTransaction()
-                .replace(R.id.status_bar_container,
-                        statusBarFragmentCreator.invoke(),
-                        CollapsedStatusBarFragment.TAG)
+                .replace(
+                    R.id.status_bar_container,
+                    collapsedStatusBarFragmentProvider.get(),
+                    CollapsedStatusBarFragment.TAG
+                )
                 .commit()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index f40f570..a3bc002 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -132,8 +132,9 @@
     override fun onStatusEvent(event: StatusEvent) {
         Assert.isMainThread()
 
-        // Ignore any updates until the system is up and running
-        if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
+        // Ignore any updates until the system is up and running. However, for important events that
+        // request to be force visible (like privacy), ignore whether it's too early.
+        if ((isTooEarly() && !event.forceVisible) || !isImmersiveIndicatorEnabled()) {
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
index 5fa83ef..6b5a548 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
@@ -93,8 +93,9 @@
     @SystemAnimationState override fun getAnimationState() = animationState
 
     override fun onStatusEvent(event: StatusEvent) {
-        // Ignore any updates until the system is up and running
-        if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
+        // Ignore any updates until the system is up and running. However, for important events that
+        // request to be force visible (like privacy), ignore whether it's too early.
+        if ((isTooEarly() && !event.forceVisible) || !isImmersiveIndicatorEnabled()) {
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 637637d..09be41b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -19,12 +19,13 @@
 import android.content.Context;
 
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.shade.ShadeEventsModule;
-import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -76,10 +77,13 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
 
 import java.util.concurrent.Executor;
 
@@ -110,6 +114,13 @@
     @Binds
     NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
 
+    /** Binds {@link NotificationGutsManager} as a {@link CoreStartable}. */
+    @Binds
+    @IntoMap
+    @ClassKey(NotificationGutsManager.class)
+    CoreStartable bindsNotificationGutsManager(NotificationGutsManager notificationGutsManager);
+
+
     /** Provides an instance of {@link VisibilityLocationProvider} */
     @Binds
     VisibilityLocationProvider bindVisibilityLocationProvider(
@@ -125,7 +136,8 @@
             NotificationVisibilityProvider visibilityProvider,
             NotifPipeline notifPipeline,
             StatusBarStateController statusBarStateController,
-            ShadeExpansionStateManager shadeExpansionStateManager,
+            WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
+            JavaAdapter javaAdapter,
             NotificationLogger.ExpansionStateLogger expansionStateLogger,
             NotificationPanelLogger notificationPanelLogger) {
         return new NotificationLogger(
@@ -135,11 +147,18 @@
                 visibilityProvider,
                 notifPipeline,
                 statusBarStateController,
-                shadeExpansionStateManager,
+                windowRootViewVisibilityInteractor,
+                javaAdapter,
                 expansionStateLogger,
                 notificationPanelLogger);
     }
 
+    /** Binds {@link NotificationLogger} as a {@link CoreStartable}. */
+    @Binds
+    @IntoMap
+    @ClassKey(NotificationLogger.class)
+    CoreStartable bindsNotificationLogger(NotificationLogger notificationLogger);
+
     /** Provides an instance of {@link NotificationPanelLogger} */
     @SysUISingleton
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 26f97de..8d2a63e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -33,10 +33,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
@@ -48,6 +49,7 @@
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.util.Compile;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -62,7 +64,7 @@
  * Handles notification logging, in particular, logging which notifications are visible and which
  * are not.
  */
-public class NotificationLogger implements StateListener {
+public class NotificationLogger implements StateListener, CoreStartable {
     static final String TAG = "NotificationLogger";
     private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
 
@@ -81,6 +83,8 @@
     private final NotifPipeline mNotifPipeline;
     private final NotificationPanelLogger mNotificationPanelLogger;
     private final ExpansionStateLogger mExpansionStateLogger;
+    private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
+    private final JavaAdapter mJavaAdapter;
 
     protected Handler mHandler = new Handler();
     protected IStatusBarService mBarService;
@@ -88,10 +92,7 @@
     private NotificationListContainer mListContainer;
     private final Object mDozingLock = new Object();
     @GuardedBy("mDozingLock")
-    private Boolean mDozing = null;  // Use null to indicate state is not yet known
-    @GuardedBy("mDozingLock")
     private Boolean mLockscreen = null;  // Use null to indicate state is not yet known
-    private Boolean mPanelExpanded = null;  // Use null to indicate state is not yet known
     private boolean mLogging = false;
 
     // Tracks notifications currently visible in mNotificationStackScroller and
@@ -202,7 +203,8 @@
             NotificationVisibilityProvider visibilityProvider,
             NotifPipeline notifPipeline,
             StatusBarStateController statusBarStateController,
-            ShadeExpansionStateManager shadeExpansionStateManager,
+            WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
+            JavaAdapter javaAdapter,
             ExpansionStateLogger expansionStateLogger,
             NotificationPanelLogger notificationPanelLogger) {
         mNotificationListener = notificationListener;
@@ -214,9 +216,10 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mExpansionStateLogger = expansionStateLogger;
         mNotificationPanelLogger = notificationPanelLogger;
+        mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
+        mJavaAdapter = javaAdapter;
         // Not expected to be destroyed, don't need to unsubscribe
         statusBarStateController.addCallback(this);
-        shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
 
         registerNewPipelineListener();
     }
@@ -239,6 +242,22 @@
         mListContainer = listContainer;
     }
 
+    @Override
+    public void start() {
+        mJavaAdapter.alwaysCollectFlow(
+                mWindowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive(),
+                this::onLockscreenOrShadeVisibleAndInteractiveChanged);
+    }
+
+    private void onLockscreenOrShadeVisibleAndInteractiveChanged(boolean visible) {
+        if (visible) {
+            startNotificationLogging();
+        } else {
+            // Ensure we stop notification logging when the device isn't interactive.
+            stopNotificationLogging();
+        }
+    }
+
     public void stopNotificationLogging() {
         if (mLogging) {
             mLogging = false;
@@ -263,10 +282,15 @@
             if (DEBUG) {
                 Log.i(TAG, "startNotificationLogging");
             }
+            boolean lockscreen;
+            synchronized (mDozingLock) {
+                lockscreen = mLockscreen != null && mLockscreen;
+            }
+            mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications());
             mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
-            // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
-            // cause the scroller to emit child location events. Hence generate
-            // one ourselves to guarantee that we're reporting visible
+            // Sometimes, the transition from lockscreenOrShadeVisible=false ->
+            // lockscreenOrShadeVisible=true doesn't cause the scroller to emit child location
+            // events. Hence generate one ourselves to guarantee that we're reporting visible
             // notifications.
             // (Note that in cases where the scroller does emit events, this
             // additional event doesn't break anything.)
@@ -274,13 +298,6 @@
         }
     }
 
-    private void setDozing(boolean dozing) {
-        synchronized (mDozingLock) {
-            mDozing = dozing;
-            maybeUpdateLoggingStatus();
-        }
-    }
-
     private void logNotificationVisibilityChanges(
             Collection<NotificationVisibility> newlyVisible,
             Collection<NotificationVisibility> noLongerVisible) {
@@ -362,39 +379,6 @@
         }
     }
 
-    @Override
-    public void onDozingChanged(boolean isDozing) {
-        if (DEBUG) {
-            Log.i(TAG, "onDozingChanged: new=" + isDozing);
-        }
-        setDozing(isDozing);
-    }
-
-    @GuardedBy("mDozingLock")
-    private void maybeUpdateLoggingStatus() {
-        if (mPanelExpanded == null || mDozing == null) {
-            if (DEBUG) {
-                Log.i(TAG, "Panel status unclear: panelExpandedKnown="
-                        + (mPanelExpanded == null) + " dozingKnown=" + (mDozing == null));
-            }
-            return;
-        }
-        // Once we know panelExpanded and Dozing, turn logging on & off when appropriate
-        boolean lockscreen = mLockscreen == null ? false : mLockscreen;
-        if (mPanelExpanded && !mDozing) {
-            mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications());
-            if (DEBUG) {
-                Log.i(TAG, "Notification panel shown, lockscreen=" + lockscreen);
-            }
-            startNotificationLogging();
-        } else {
-            if (DEBUG) {
-                Log.i(TAG, "Notification panel hidden, lockscreen=" + lockscreen);
-            }
-            stopNotificationLogging();
-        }
-    }
-
     /**
      * Called when the notification is expanded / collapsed.
      */
@@ -404,20 +388,6 @@
     }
 
     @VisibleForTesting
-    void onShadeExpansionFullyChanged(Boolean isExpanded) {
-        // mPanelExpanded is initialized as null
-        if (mPanelExpanded == null || !mPanelExpanded.equals(isExpanded)) {
-            if (DEBUG) {
-                Log.i(TAG, "onPanelExpandedChanged: new=" + isExpanded);
-            }
-            mPanelExpanded = isExpanded;
-            synchronized (mDozingLock) {
-                maybeUpdateLoggingStatus();
-            }
-        }
-    }
-
-    @VisibleForTesting
     void onChildLocationsChanged() {
         if (mHandler.hasCallbacks(mVisibilityReporter)) {
             // Visibilities will be reported when the existing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index d92d11b..c02382d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -74,7 +74,6 @@
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.CallLayout;
 import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.flags.ViewRefactorFlag;
@@ -245,7 +244,6 @@
     private NotificationEntry mEntry;
     private String mAppName;
     private FalsingManager mFalsingManager;
-    private FalsingCollector mFalsingCollector;
 
     /**
      * Whether or not the notification is using the heads up view and should peek from the top.
@@ -1711,7 +1709,6 @@
             OnExpandClickListener onExpandClickListener,
             CoordinateOnClickListener onFeedbackClickListener,
             FalsingManager falsingManager,
-            FalsingCollector falsingCollector,
             StatusBarStateController statusBarStateController,
             PeopleNotificationIdentifier peopleNotificationIdentifier,
             OnUserInteractionCallback onUserInteractionCallback,
@@ -1743,7 +1740,6 @@
         mOnExpandClickListener = onExpandClickListener;
         setOnFeedbackClickListener(onFeedbackClickListener);
         mFalsingManager = falsingManager;
-        mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
         mPeopleNotificationIdentifier = peopleNotificationIdentifier;
         for (NotificationContentView l : mLayouts) {
@@ -2471,7 +2467,6 @@
      * @param allowChildExpansion whether a call to this method allows expanding children
      */
     public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
-        mFalsingCollector.setNotificationExpanded();
         if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion
                 && !mChildrenContainer.showingAsLowPriority()) {
             final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 80f5d19..af55f44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -34,7 +34,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
@@ -102,7 +101,6 @@
     private final NotificationGutsManager mNotificationGutsManager;
     private final OnUserInteractionCallback mOnUserInteractionCallback;
     private final FalsingManager mFalsingManager;
-    private final FalsingCollector mFalsingCollector;
     private final FeatureFlags mFeatureFlags;
     private final boolean mAllowLongPress;
     private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@@ -222,7 +220,6 @@
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             OnUserInteractionCallback onUserInteractionCallback,
             FalsingManager falsingManager,
-            FalsingCollector falsingCollector,
             FeatureFlags featureFlags,
             PeopleNotificationIdentifier peopleNotificationIdentifier,
             Optional<BubblesManager> bubblesManagerOptional,
@@ -251,7 +248,6 @@
         mFalsingManager = falsingManager;
         mOnFeedbackClickListener = mNotificationGutsManager::openGuts;
         mAllowLongPress = allowLongPress;
-        mFalsingCollector = falsingCollector;
         mFeatureFlags = featureFlags;
         mPeopleNotificationIdentifier = peopleNotificationIdentifier;
         mBubblesManagerOptional = bubblesManagerOptional;
@@ -285,7 +281,6 @@
                 mOnExpandClickListener,
                 mOnFeedbackClickListener,
                 mFalsingManager,
-                mFalsingCollector,
                 mStatusBarStateController,
                 mPeopleNotificationIdentifier,
                 mOnUserInteractionCallback,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6f79ea8..44ead26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -45,6 +45,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settingslib.notification.ConversationIconFactory;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -53,6 +54,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -69,6 +71,7 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.wmshell.BubblesManager;
 
 import java.util.Optional;
@@ -80,7 +83,7 @@
  * closing guts, and keeping track of the currently exposed notification guts.
  */
 @SysUISingleton
-public class NotificationGutsManager implements NotifGutsViewManager {
+public class NotificationGutsManager implements NotifGutsViewManager, CoreStartable {
     private static final String TAG = "NotificationGutsManager";
 
     // Must match constant in Settings. Used to highlight preferences when linking to Settings.
@@ -109,6 +112,7 @@
 
     private final Handler mMainHandler;
     private final Handler mBgHandler;
+    private final JavaAdapter mJavaAdapter;
     private final Optional<BubblesManager> mBubblesManagerOptional;
     private Runnable mOpenRunnable;
     private final INotificationManager mNotificationManager;
@@ -121,6 +125,7 @@
     private final UserContextProvider mContextTracker;
     private final UiEventLogger mUiEventLogger;
     private final ShadeController mShadeController;
+    private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
     private NotifGutsViewListener mGutsListener;
     private final HeadsUpManagerPhone mHeadsUpManagerPhone;
     private final ActivityStarter mActivityStarter;
@@ -129,6 +134,7 @@
     public NotificationGutsManager(Context context,
             @Main Handler mainHandler,
             @Background Handler bgHandler,
+            JavaAdapter javaAdapter,
             AccessibilityManager accessibilityManager,
             HighPriorityProvider highPriorityProvider,
             INotificationManager notificationManager,
@@ -143,6 +149,7 @@
             UiEventLogger uiEventLogger,
             OnUserInteractionCallback onUserInteractionCallback,
             ShadeController shadeController,
+            WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
             StatusBarStateController statusBarStateController,
             DeviceProvisionedController deviceProvisionedController,
@@ -152,6 +159,7 @@
         mContext = context;
         mMainHandler = mainHandler;
         mBgHandler = bgHandler;
+        mJavaAdapter = javaAdapter;
         mAccessibilityManager = accessibilityManager;
         mHighPriorityProvider = highPriorityProvider;
         mNotificationManager = notificationManager;
@@ -166,6 +174,7 @@
         mUiEventLogger = uiEventLogger;
         mOnUserInteractionCallback = onUserInteractionCallback;
         mShadeController = shadeController;
+        mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
         mLockscreenUserManager = notificationLockscreenUserManager;
         mStatusBarStateController = statusBarStateController;
         mDeviceProvisionedController = deviceProvisionedController;
@@ -187,6 +196,25 @@
         mNotificationActivityStarter = notificationActivityStarter;
     }
 
+    @Override
+    public void start() {
+        mJavaAdapter.alwaysCollectFlow(
+                mWindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible(),
+                this::onLockscreenShadeVisibilityChanged);
+    }
+
+    private void onLockscreenShadeVisibilityChanged(boolean visible) {
+        if (!visible) {
+            closeAndSaveGuts(
+                    /* removeLeavebehind= */ true ,
+                    /* force= */ true,
+                    /* removeControls= */ true,
+                    /* x= */ -1,
+                    /* y= */ -1,
+                    /* resetMenu= */ true);
+        }
+    }
+
     public void onDensityOrFontScaleChanged(NotificationEntry entry) {
         setExposedGuts(entry.getGuts());
         bindGuts(entry.getRow());
@@ -512,7 +540,7 @@
             mNotificationGutsExposed.removeCallbacks(mOpenRunnable);
             mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
         }
-        if (resetMenu) {
+        if (resetMenu && mListContainer != null) {
             mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 6db8df9..d8f513c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -455,7 +455,6 @@
 
                 @Override
                 public void onDragCancelled(View v) {
-                    mFalsingCollector.onNotificationStopDismissing();
                 }
 
                 /**
@@ -501,7 +500,6 @@
                     }
 
                     mView.addSwipedOutView(view);
-                    mFalsingCollector.onNotificationDismissed();
                     if (mFalsingCollector.shouldEnforceBouncer()) {
                         mActivityStarter.executeRunnableDismissingKeyguard(
                                 null,
@@ -549,7 +547,6 @@
 
                 @Override
                 public void onBeginDrag(View v) {
-                    mFalsingCollector.onNotificationStartDismissing();
                     mView.onSwipeBegin(v);
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index cbb3915..67c0c94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -44,7 +44,6 @@
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
-import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.util.Compile;
 
@@ -183,8 +182,6 @@
         return contextForUser.getPackageManager();
     }
 
-    void start();
-
     boolean updateIsKeyguard();
 
     boolean updateIsKeyguard(boolean forceStateChange);
@@ -200,10 +197,6 @@
 
     void onKeyguardViewManagerStatesUpdated();
 
-    boolean isDeviceInVrMode();
-
-    NotificationPresenter getPresenter();
-
     /**
      * Used to dispatch initial touch events before crossing the threshold to pull down the
      * notification shade. After that, since the launcher window is set to slippery, input
@@ -222,8 +215,6 @@
     /** */
     boolean getCommandQueuePanelsEnabled();
 
-    BiometricUnlockController getBiometricUnlockController();
-
     void showWirelessChargingAnimation(int batteryLevel);
 
     void checkBarModes();
@@ -232,9 +223,6 @@
 
     void setInteracting(int barWindow, boolean interacting);
 
-    @Override
-    void dump(PrintWriter pwOriginal, String[] args);
-
     /** @deprecated Use {@link DisplayMetricsRepository} instead. */
     @Deprecated
     float getDisplayWidth();
@@ -283,8 +271,6 @@
 
     void setBouncerShowing(boolean bouncerShowing);
 
-    int getWakefulnessState();
-
     boolean isScreenFullyOff();
 
     void showScreenPinningRequest(int taskId, boolean allowCancel);
@@ -325,8 +311,6 @@
 
     boolean isBouncerShowingOverDream();
 
-    boolean isKeyguardSecure();
-
     void updateNotificationPanelTouchState();
 
     int getRotation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 10422e3..98ba6d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -23,9 +23,7 @@
 import com.android.systemui.navigationbar.NavigationBarView
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.qs.QSPanelController
-import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import java.io.PrintWriter
 
 /**
  * Empty implementation of [CentralSurfaces] for variants only need to override portions of the
@@ -41,16 +39,12 @@
     override fun getKeyguardMessageArea(): AuthKeyguardMessageArea? = null
     override fun isLaunchingActivityOverLockscreen() = false
     override fun onKeyguardViewManagerStatesUpdated() {}
-    override fun isDeviceInVrMode() = false
-    override fun getPresenter(): NotificationPresenter? = null
     override fun onInputFocusTransfer(start: Boolean, cancel: Boolean, velocity: Float) {}
     override fun getCommandQueuePanelsEnabled() = false
-    override fun getBiometricUnlockController(): BiometricUnlockController? = null
     override fun showWirelessChargingAnimation(batteryLevel: Int) {}
     override fun checkBarModes() {}
     override fun updateBubblesVisibility() {}
     override fun setInteracting(barWindow: Int, interacting: Boolean) {}
-    override fun dump(pwOriginal: PrintWriter, args: Array<String>) {}
     override fun getDisplayWidth() = 0f
     override fun getDisplayHeight() = 0f
     override fun showKeyguard() {}
@@ -78,7 +72,6 @@
     override fun showPinningEnterExitToast(entering: Boolean) {}
     override fun showPinningEscapeToast() {}
     override fun setBouncerShowing(bouncerShowing: Boolean) {}
-    override fun getWakefulnessState() = 0
     override fun isScreenFullyOff() = false
     override fun showScreenPinningRequest(taskId: Int, allowCancel: Boolean) {}
     override fun getEmergencyActionIntent(): Intent? = null
@@ -96,7 +89,6 @@
     override fun isBouncerShowing() = false
     override fun isBouncerShowingScrimmed() = false
     override fun isBouncerShowingOverDream() = false
-    override fun isKeyguardSecure() = false
     override fun updateNotificationPanelTouchState() {}
     override fun getRotation() = 0
     override fun setBarStateForTest(state: Int) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 605b3d2..0277a36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -92,17 +92,12 @@
 import android.view.MotionEvent;
 import android.view.ThreadedRenderer;
 import android.view.View;
-import android.view.ViewRootImpl;
 import android.view.WindowInsets;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.DateTimeView;
-import android.window.BackEvent;
-import android.window.OnBackAnimationCallback;
-import android.window.OnBackInvokedCallback;
-import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
 import androidx.lifecycle.Lifecycle;
@@ -176,6 +171,7 @@
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -221,15 +217,12 @@
 import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -459,9 +452,9 @@
             mNotificationShadeWindowViewControllerLazy;
     private final DozeParameters mDozeParameters;
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
-    private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
     private final PluginManager mPluginManager;
     private final ShadeController mShadeController;
+    private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
     private final InitController mInitController;
     private final Lazy<CameraLauncher> mCameraLauncherLazy;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -489,13 +482,11 @@
     private View mReportRejectedTouch;
 
     private final NotificationGutsManager mGutsManager;
-    private final NotificationLogger mNotificationLogger;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
     private final KeyguardViewMediator mKeyguardViewMediator;
     protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final BrightnessSliderController.Factory mBrightnessSliderFactory;
     private final FeatureFlags mFeatureFlags;
-    private final boolean mAnimateBack;
     private final FragmentService mFragmentService;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final WallpaperController mWallpaperController;
@@ -506,15 +497,6 @@
     private final Provider<FingerprintManager> mFingerprintManager;
     private final ActivityStarter mActivityStarter;
 
-    private CentralSurfacesComponent mCentralSurfacesComponent;
-
-    /**
-     * This keeps track of whether we have (or haven't) registered the predictive back callback.
-     * Since we can have visible -> visible transitions, we need to avoid
-     * double-registering (or double-unregistering) our callback.
-     */
-    private boolean mIsBackCallbackRegistered = false;
-
     /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
     private @Appearance int mAppearance;
 
@@ -640,31 +622,6 @@
 
     private final InteractionJankMonitor mJankMonitor;
 
-    /** Existing callback that handles back gesture invoked for the Shade. */
-    private final OnBackInvokedCallback mOnBackInvokedCallback;
-
-    /**
-     *  New callback that handles back gesture invoked, cancel, progress
-     *  and provides feedback via Shade animation.
-     *  (enabled via the WM_SHADE_ANIMATE_BACK_GESTURE flag)
-     */
-    private final OnBackAnimationCallback mOnBackAnimationCallback = new OnBackAnimationCallback() {
-        @Override
-        public void onBackInvoked() {
-            mBackActionInteractor.onBackRequested();
-        }
-
-        @Override
-        public void onBackProgressed(BackEvent event) {
-            if (mBackActionInteractor.shouldBackBeHandled()) {
-                if (mShadeSurface.canBeCollapsed()) {
-                    float fraction = event.getProgress();
-                    mShadeSurface.onBackProgressed(fraction);
-                }
-            }
-        }
-    };
-
     /**
      * Public constructor for CentralSurfaces.
      *
@@ -694,7 +651,6 @@
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
             NotificationGutsManager notificationGutsManager,
-            NotificationLogger notificationLogger,
             NotificationInterruptStateProvider notificationInterruptStateProvider,
             ShadeExpansionStateManager shadeExpansionStateManager,
             KeyguardViewMediator keyguardViewMediator,
@@ -741,10 +697,10 @@
             DozeScrimController dozeScrimController,
             VolumeComponent volumeComponent,
             CommandQueue commandQueue,
-            CentralSurfacesComponent.Factory centralSurfacesComponentFactory,
             Lazy<CentralSurfacesCommandQueueCallbacks> commandQueueCallbacksLazy,
             PluginManager pluginManager,
             ShadeController shadeController,
+            WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
@@ -803,7 +759,6 @@
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
         mGutsManager = notificationGutsManager;
-        mNotificationLogger = notificationLogger;
         mNotificationInterruptStateProvider = notificationInterruptStateProvider;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mKeyguardViewMediator = keyguardViewMediator;
@@ -852,10 +807,10 @@
         mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy;
         mVolumeComponent = volumeComponent;
         mCommandQueue = commandQueue;
-        mCentralSurfacesComponentFactory = centralSurfacesComponentFactory;
         mCommandQueueCallbacksLazy = commandQueueCallbacksLazy;
         mPluginManager = pluginManager;
         mShadeController = shadeController;
+        mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
@@ -926,14 +881,6 @@
         if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
             mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
         }
-        // Based on teamfood flag, enable predictive back animation for the Shade.
-        mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
-        mOnBackInvokedCallback = () -> {
-            if (DEBUG) {
-                Log.d(TAG, "mOnBackInvokedCallback() called");
-            }
-            mBackActionInteractor.onBackRequested();
-        };
     }
 
     private void initBubbles(Bubbles bubbles) {
@@ -1171,7 +1118,10 @@
                 new FoldStateListener(mContext, this::onFoldedStateChanged));
     }
 
-    @VisibleForTesting
+    /**
+     * @deprecated use {@link
+     * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead.
+     */    @VisibleForTesting
     void initShadeVisibilityListener() {
         mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
             @Override
@@ -1236,10 +1186,15 @@
         updateResources();
         updateTheme();
 
-        inflateStatusBarWindow();
+        setUpShade();
         getNotificationShadeWindowView().setOnTouchListener(getStatusBarWindowTouchListener());
         mWallpaperController.setRootView(getNotificationShadeWindowView());
 
+        mDemoModeController.addCallback(mDemoModeCallback);
+
+        mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get();
+        mCommandQueue.addCallback(mCommandQueueCallbacks);
+
         // TODO: Deal with the ugliness that comes from having some of the status bar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
@@ -1270,8 +1225,7 @@
                     setBouncerShowingForStatusBarComponents(mBouncerShowing);
                     checkBarModes();
                 });
-        mStatusBarInitializer.initializeStatusBar(
-                mCentralSurfacesComponent::createCollapsedStatusBarFragment);
+        mStatusBarInitializer.initializeStatusBar();
 
         mStatusBarTouchableRegionManager.setup(this, getNotificationShadeWindowView());
 
@@ -1563,6 +1517,7 @@
                 mNotifListContainer,
                 mStackScrollerController.getNotifStackController(),
                 mNotificationActivityStarter);
+        mWindowRootViewVisibilityInteractor.setUp(mPresenter, mNotificationsController);
     }
 
     /**
@@ -1593,15 +1548,7 @@
         };
     }
 
-    private void inflateStatusBarWindow() {
-        if (mCentralSurfacesComponent != null) {
-            Log.e(TAG, "CentralSurfacesComponent being recreated; this is unexpected.");
-        }
-        mCentralSurfacesComponent = mCentralSurfacesComponentFactory.create();
-        mFragmentService.addFragmentInstantiationProvider(
-                CollapsedStatusBarFragment.class,
-                mCentralSurfacesComponent::createCollapsedStatusBarFragment);
-
+    private void setUpShade() {
         // Ideally, NotificationShadeWindowController could automatically fetch the window root view
         // in #attach or a CoreStartable.start method or something similar. But for now, to avoid
         // regressions, we'll continue standing up the root view in CentralSurfaces.
@@ -1610,12 +1557,6 @@
         mShadeController.setNotificationShadeWindowViewController(
                 getNotificationShadeWindowViewController());
         mBackActionInteractor.setup(mQsController, mShadeSurface);
-
-        // Listen for demo mode changes
-        mDemoModeController.addCallback(mDemoModeCallback);
-
-        mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get();
-        mCommandQueue.addCallback(mCommandQueueCallbacks);
     }
 
     protected NotificationShadeWindowViewController getNotificationShadeWindowViewController() {
@@ -1708,16 +1649,6 @@
         logStateToEventlog();
     }
 
-    @Override
-    public boolean isDeviceInVrMode() {
-        return mPresenter.isDeviceInVrMode();
-    }
-
-    @Override
-    public NotificationPresenter getPresenter() {
-        return mPresenter;
-    }
-
     @VisibleForTesting
     @Override
     public void setBarStateForTest(int state) {
@@ -1792,11 +1723,6 @@
     }
 
     @Override
-    public BiometricUnlockController getBiometricUnlockController() {
-        return mBiometricUnlockController;
-    }
-
-    @Override
     public void showTransientUnchecked() {
         if (!mTransientShown) {
             mTransientShown = true;
@@ -1947,8 +1873,6 @@
         pw.print("  mDozing="); pw.println(mDozing);
         pw.print("  mWallpaperSupported= "); pw.println(mWallpaperSupported);
 
-        pw.println("  ShadeWindowView: ");
-        getNotificationShadeWindowViewController().dump(pw, args);
         CentralSurfaces.dumpBarTransitions(
                 pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
 
@@ -2149,82 +2073,6 @@
                 com.android.systemui.R.dimen.physical_power_button_center_screen_location_y));
     }
 
-    protected void handleVisibleToUserChanged(boolean visibleToUser) {
-        if (visibleToUser) {
-            onVisibleToUser();
-            mNotificationLogger.startNotificationLogging();
-
-            if (!mIsBackCallbackRegistered) {
-                ViewRootImpl viewRootImpl = getViewRootImpl();
-                if (viewRootImpl != null) {
-                    viewRootImpl.getOnBackInvokedDispatcher()
-                            .registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-                                    mAnimateBack ? mOnBackAnimationCallback
-                                            : mOnBackInvokedCallback);
-                    mIsBackCallbackRegistered = true;
-                    if (DEBUG) Log.d(TAG, "is now VISIBLE to user AND callback registered");
-                }
-            } else {
-                if (DEBUG) Log.d(TAG, "is now VISIBLE to user, BUT callback ALREADY unregistered");
-            }
-        } else {
-            mNotificationLogger.stopNotificationLogging();
-            onInvisibleToUser();
-
-            if (mIsBackCallbackRegistered) {
-                ViewRootImpl viewRootImpl = getViewRootImpl();
-                if (viewRootImpl != null) {
-                    viewRootImpl.getOnBackInvokedDispatcher()
-                            .unregisterOnBackInvokedCallback(
-                                    mAnimateBack ? mOnBackAnimationCallback
-                                            : mOnBackInvokedCallback);
-                    mIsBackCallbackRegistered = false;
-                    if (DEBUG) Log.d(TAG, "is NOT VISIBLE to user, AND callback unregistered");
-                }
-            } else {
-                if (DEBUG) {
-                    Log.d(TAG,
-                            "is NOT VISIBLE to user, BUT NO callback (or callback ALREADY "
-                                    + "unregistered)");
-                }
-            }
-        }
-    }
-
-    void onVisibleToUser() {
-        /* The LEDs are turned off when the notification panel is shown, even just a little bit.
-         * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
-         * this.
-         */
-        boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
-        boolean clearNotificationEffects =
-                !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE
-                        || mState == StatusBarState.SHADE_LOCKED);
-        int notificationLoad = mNotificationsController.getActiveNotificationsCount();
-        if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
-            notificationLoad = 1;
-        }
-        final int finalNotificationLoad = notificationLoad;
-        mUiBgExecutor.execute(() -> {
-            try {
-                mBarService.onPanelRevealed(clearNotificationEffects,
-                        finalNotificationLoad);
-            } catch (RemoteException ex) {
-                // Won't fail unless the world has ended.
-            }
-        });
-    }
-
-    void onInvisibleToUser() {
-        mUiBgExecutor.execute(() -> {
-            try {
-                mBarService.onPanelHidden();
-            } catch (RemoteException ex) {
-                // Won't fail unless the world has ended.
-            }
-        });
-    }
-
     private void logStateToEventlog() {
         boolean isShowing = mKeyguardStateController.isShowing();
         boolean isOccluded = mKeyguardStateController.isOccluded();
@@ -2628,7 +2476,7 @@
                 mStatusBarKeyguardViewManager.reset(true);
             } else if (mState == StatusBarState.KEYGUARD
                     && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
-                    && isKeyguardSecure()) {
+                    && mStatusBarKeyguardViewManager.isSecure()) {
                 mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
             }
         }
@@ -2708,11 +2556,6 @@
         mNavigationBarController.showPinningEscapeToast(mDisplayId);
     }
 
-    protected ViewRootImpl getViewRootImpl()  {
-        View root = mNotificationShadeWindowController.getWindowRootView();
-        if (root != null) return root.getViewRootImpl();
-        return null;
-    }
     /**
      * Propagation of the bouncer state, indicating that it's fully visible.
      */
@@ -2759,7 +2602,6 @@
             releaseGestureWakeLock();
             mLaunchCameraWhenFinishedWaking = false;
             mDeviceInteractive = false;
-            updateVisibleToUser();
 
             updateNotificationPanelTouchState();
             getNotificationShadeWindowViewController().cancelCurrentTouch();
@@ -2858,7 +2700,6 @@
                         /* wakingUp= */ true,
                         mShouldDelayWakeUpAnimation);
 
-                updateVisibleToUser();
                 updateIsKeyguard();
                 mShouldDelayLockscreenTransitionFromAod = mDozeParameters.getAlwaysOn()
                         && mFeatureFlags.isEnabled(
@@ -2974,11 +2815,6 @@
         }
     };
 
-    @Override
-    public int getWakefulnessState() {
-        return mWakefulnessLifecycle.getWakefulness();
-    }
-
     /**
      * @return true if the screen is currently fully off, i.e. has finished turning off and has
      * since not started turning on.
@@ -3044,7 +2880,7 @@
         if (mDevicePolicyManager.getCameraDisabled(null,
                 mLockscreenUserManager.getCurrentUserId())) {
             return false;
-        } else if (isKeyguardShowing() && isKeyguardSecure()) {
+        } else if (isKeyguardShowing() && mStatusBarKeyguardViewManager.isSecure()) {
             // Check if the admin has disabled the camera specifically for the keyguard
             return (mDevicePolicyManager.getKeyguardDisabledFeatures(null,
                     mLockscreenUserManager.getCurrentUserId())
@@ -3187,9 +3023,6 @@
 
     protected boolean mVisible;
 
-    // mScreenOnFromKeyguard && mVisible.
-    private boolean mVisibleToUser;
-
     protected DevicePolicyManager mDevicePolicyManager;
     private final PowerManager mPowerManager;
     protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -3313,21 +3146,8 @@
             if (visible) {
                 DejankUtils.notifyRendererOfExpensiveFrame(
                         getNotificationShadeWindowView(), "onShadeVisibilityChanged");
-            } else {
-                mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
-                        true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
             }
         }
-        updateVisibleToUser();
-    }
-
-    protected void updateVisibleToUser() {
-        boolean oldVisibleToUser = mVisibleToUser;
-        mVisibleToUser = mVisible && mDeviceInteractive;
-
-        if (oldVisibleToUser != mVisibleToUser) {
-            handleVisibleToUserChanged(mVisibleToUser);
-        }
     }
 
     /**
@@ -3363,11 +3183,6 @@
         return mBouncerShowingOverDream;
     }
 
-    @Override
-    public boolean isKeyguardSecure() {
-        return mStatusBarKeyguardViewManager.isSecure();
-    }
-
     // End Extra BaseStatusBarMethods.
 
     boolean isTransientShown() {
@@ -3503,7 +3318,7 @@
                     // If we're visible and switched to SHADE_LOCKED (the user dragged
                     // down on the lockscreen), clear notification LED, vibration,
                     // ringing.
-                    // Other transitions are covered in handleVisibleToUserChanged().
+                    // Other transitions are covered in WindowRootViewVisibilityInteractor.
                     if (mVisible && (newState == StatusBarState.SHADE_LOCKED
                             || mStatusBarStateController.goingToFullShade())) {
                         clearNotificationEffects();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index b0f8276..40432ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -1480,7 +1480,7 @@
 
     public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
         // TODO: remove this. This is necessary because of an order-of-operations limitation.
-        // The fix is to move more of these class into @CentralSurfacesScope
+        // The fix is to move more of these class into @SysUISingleton.
         if (mScrimBehind == null) {
             mScrimBehindChangeRunnable = changeRunnable;
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index d5cb6b6..4878d45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -81,11 +81,7 @@
     /** Removes an icon that had come from an active tile service. */
     void removeIconForTile(String slot);
 
-    /**
-     * Adds or updates an icon for the given slot for **internal system icons**.
-     *
-     * TODO(b/265307726): Re-name this to `setInternalIcon`.
-     */
+    /** Adds or updates an icon for the given slot for **internal system icons**. */
     void setIcon(String slot, int resourceId, CharSequence contentDescription);
 
     /**
@@ -127,11 +123,6 @@
      */
     void removeIcon(String slot, int tag);
 
-    /**
-     * TODO(b/265307726): Re-name this to `removeAllIconsForInternalSlot`.
-     */
-    void removeAllIconsForSlot(String slot);
-
     // TODO: See if we can rename this tunable name.
     String ICON_HIDE_LIST = "icon_blacklist";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 553cbc5..0f4d68c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -385,13 +385,7 @@
     }
 
     private void removeAllIconsForExternalSlot(String slotName) {
-        removeAllIconsForSlot(createExternalSlotName(slotName));
-    }
-
-    /** */
-    @Override
-    public void removeAllIconsForSlot(String slotName) {
-        removeAllIconsForSlot(slotName, /* fromNewPipeline */ false);
+        removeAllIconsForSlot(createExternalSlotName(slotName), /* fromNewPipeline= */ false);
     }
 
     private void removeAllIconsForSlot(String slotName, boolean fromNewPipeline) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
deleted file mode 100644
index 1a04b91..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.dagger;
-
-import static com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.STATUS_BAR_FRAGMENT;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import com.android.systemui.shade.ShadeHeaderController;
-import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
-
-import dagger.Subcomponent;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Named;
-import javax.inject.Scope;
-
-/**
- * Dagger subcomponent for classes (semi-)related to the status bar. The component is created once
- * inside {@link CentralSurfacesImpl} and never re-created.
- *
- * TODO(b/197137564): This should likely be re-factored a bit. It includes classes that aren't
- * directly related to status bar functionality, like multiple notification classes. And, the fact
- * that it has many getter methods indicates that we need to access many of these classes from
- * outside the component. Should more items be moved *into* this component to avoid so many getters?
- */
-@Subcomponent(modules = {
-        StatusBarViewModule.class,
-})
-@CentralSurfacesComponent.CentralSurfacesScope
-public interface CentralSurfacesComponent {
-    /**
-     * Builder for {@link CentralSurfacesComponent}.
-     */
-    @Subcomponent.Factory
-    interface Factory {
-        CentralSurfacesComponent create();
-    }
-
-    /**
-     * Scope annotation for singleton items within the CentralSurfacesComponent.
-     */
-    @Documented
-    @Retention(RUNTIME)
-    @Scope
-    @interface CentralSurfacesScope {}
-
-    /**
-     * Creates a {@link ShadeHeaderController}.
-     */
-    ShadeHeaderController getLargeScreenShadeHeaderController();
-
-    /**
-     * Creates a new {@link CollapsedStatusBarFragment} each time it's called. See
-     * {@link StatusBarViewModule#createCollapsedStatusBarFragment}.
-     */
-    @Named(STATUS_BAR_FRAGMENT)
-    CollapsedStatusBarFragment createCollapsedStatusBarFragment();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
deleted file mode 100644
index ebdde78..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.dagger;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.OperatorNameViewController;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
-import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
-import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder;
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.window.StatusBarWindowStateController;
-import com.android.systemui.util.CarrierConfigTracker;
-import com.android.systemui.util.settings.SecureSettings;
-
-import dagger.Module;
-import dagger.Provides;
-
-import java.util.concurrent.Executor;
-
-import javax.inject.Named;
-
-/**
- * A module for {@link CentralSurfacesComponent.CentralSurfacesScope} components.
- *
- * @deprecated CentralSurfacesScope will be removed shortly (b/277762009). Classes should be
- *   annotated with @SysUISingleton instead.
- */
-@Module(subcomponents = StatusBarFragmentComponent.class)
-@Deprecated
-public abstract class StatusBarViewModule {
-
-    public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment";
-
-    /**
-     * Creates a new {@link CollapsedStatusBarFragment}.
-     *
-     * **IMPORTANT**: This method intentionally does not have
-     * {@link CentralSurfacesComponent.CentralSurfacesScope}, which means a new fragment *will* be
-     * created each time this method is called. This is intentional because we need fragments to
-     * re-created in certain lifecycle scenarios.
-     *
-     * This provider is {@link Named} such that it does not conflict with the provider inside of
-     * {@link StatusBarFragmentComponent}.
-     */
-    @Provides
-    @Named(STATUS_BAR_FRAGMENT)
-    public static CollapsedStatusBarFragment createCollapsedStatusBarFragment(
-            StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
-            OngoingCallController ongoingCallController,
-            SystemStatusAnimationScheduler animationScheduler,
-            StatusBarLocationPublisher locationPublisher,
-            NotificationIconAreaController notificationIconAreaController,
-            ShadeExpansionStateManager shadeExpansionStateManager,
-            FeatureFlags featureFlags,
-            StatusBarIconController statusBarIconController,
-            StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
-            CollapsedStatusBarViewModel collapsedStatusBarViewModel,
-            CollapsedStatusBarViewBinder collapsedStatusBarViewBinder,
-            StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
-            KeyguardStateController keyguardStateController,
-            ShadeViewController shadeViewController,
-            StatusBarStateController statusBarStateController,
-            CommandQueue commandQueue,
-            CarrierConfigTracker carrierConfigTracker,
-            CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
-            OperatorNameViewController.Factory operatorNameViewControllerFactory,
-            SecureSettings secureSettings,
-            @Main Executor mainExecutor,
-            DumpManager dumpManager,
-            StatusBarWindowStateController statusBarWindowStateController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor
-    ) {
-        return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory,
-                ongoingCallController,
-                animationScheduler,
-                locationPublisher,
-                notificationIconAreaController,
-                shadeExpansionStateManager,
-                featureFlags,
-                statusBarIconController,
-                darkIconManagerFactory,
-                collapsedStatusBarViewModel,
-                collapsedStatusBarViewBinder,
-                statusBarHideIconsForBouncerManager,
-                keyguardStateController,
-                shadeViewController,
-                statusBarStateController,
-                commandQueue,
-                carrierConfigTracker,
-                collapsedStatusBarFragmentLogger,
-                operatorNameViewControllerFactory,
-                secureSettings,
-                mainExecutor,
-                dumpManager,
-                statusBarWindowStateController,
-                keyguardUpdateMonitor);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 2efdf8a..66f0f59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -29,7 +29,6 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewStub;
 import android.widget.LinearLayout;
 
 import androidx.annotation.VisibleForTesting;
@@ -85,6 +84,8 @@
 import java.util.Set;
 import java.util.concurrent.Executor;
 
+import javax.inject.Inject;
+
 /**
  * Contains the collapsed status bar and handles hiding/showing based on disable flags
  * and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -203,7 +204,7 @@
         mTransitionFromLockscreenToDreamStarted = false;
     };
 
-    @SuppressLint("ValidFragment")
+    @Inject
     public CollapsedStatusBarFragment(
             StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
             OngoingCallController ongoingCallController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentStartable.kt
new file mode 100644
index 0000000..55af0e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentStartable.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.statusbar.phone.fragment
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.fragments.FragmentService
+import com.android.systemui.qs.QSFragmentStartable
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import javax.inject.Provider
+
+/**
+ * Provides [FragmentService] with a way to automatically inflate [CollapsedStatusBarFragment],
+ * similar to [QSFragmentStartable].
+ */
+@SysUISingleton
+class CollapsedStatusBarFragmentStartable
+@Inject
+constructor(
+    private val fragmentService: FragmentService,
+    private val collapsedstatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>
+) : CoreStartable {
+    override fun start() {
+        fragmentService.addFragmentInstantiationProvider(
+            CollapsedStatusBarFragment::class.java,
+            collapsedstatusBarFragmentProvider,
+        )
+    }
+}
+
+@Module(subcomponents = [StatusBarFragmentComponent::class])
+interface CollapsedStatusBarFragmentStartableModule {
+    @Binds
+    @IntoMap
+    @ClassKey(CollapsedStatusBarFragmentStartable::class)
+    fun bindsCollapsedStatusBarFragmentStartable(
+        startable: CollapsedStatusBarFragmentStartable
+    ): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 4b2fb43..249ca35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -26,6 +26,7 @@
 import android.widget.Space
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.settingslib.graph.SignalDrawable
 import com.android.systemui.R
@@ -64,7 +65,7 @@
         val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
         val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
 
-        view.isVisible = true
+        view.isVisible = viewModel.isVisible.value
         iconView.isVisible = true
 
         // TODO(b/238425913): We should log this visibility state.
@@ -77,108 +78,122 @@
         var isCollecting = false
 
         view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.STARTED) {
-                logger.logCollectionStarted(view, viewModel)
-                isCollecting = true
-
-                launch {
-                    visibilityState.collect { state ->
-                        when (state) {
-                            STATE_ICON -> {
-                                mobileGroupView.visibility = VISIBLE
-                                dotView.visibility = GONE
-                            }
-                            STATE_DOT -> {
-                                mobileGroupView.visibility = INVISIBLE
-                                dotView.visibility = VISIBLE
-                            }
-                            STATE_HIDDEN -> {
-                                mobileGroupView.visibility = INVISIBLE
-                                dotView.visibility = INVISIBLE
-                            }
+            lifecycleScope.launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    // isVisible controls the visibility state of the outer group, and thus it needs
+                    // to run in the CREATED lifecycle so it can continue to watch while invisible
+                    // See (b/291031862) for details
+                    launch {
+                        viewModel.isVisible.collect { isVisible ->
+                            viewModel.verboseLogger?.logBinderReceivedVisibility(
+                                view,
+                                viewModel.subscriptionId,
+                                isVisible
+                            )
+                            view.isVisible = isVisible
+                            // [StatusIconContainer] can get out of sync sometimes. Make sure to
+                            // request another layout when this changes.
+                            view.requestLayout()
                         }
                     }
                 }
+            }
 
-                launch {
-                    viewModel.isVisible.collect { isVisible ->
-                        viewModel.verboseLogger?.logBinderReceivedVisibility(
-                            view,
-                            viewModel.subscriptionId,
-                            isVisible
-                        )
-                        view.isVisible = isVisible
+            lifecycleScope.launch {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    logger.logCollectionStarted(view, viewModel)
+                    isCollecting = true
+
+                    launch {
+                        visibilityState.collect { state ->
+                            when (state) {
+                                STATE_ICON -> {
+                                    mobileGroupView.visibility = VISIBLE
+                                    dotView.visibility = GONE
+                                }
+                                STATE_DOT -> {
+                                    mobileGroupView.visibility = INVISIBLE
+                                    dotView.visibility = VISIBLE
+                                }
+                                STATE_HIDDEN -> {
+                                    mobileGroupView.visibility = INVISIBLE
+                                    dotView.visibility = INVISIBLE
+                                }
+                            }
+                        }
                     }
-                }
 
-                // Set the icon for the triangle
-                launch {
-                    viewModel.icon.distinctUntilChanged().collect { icon ->
-                        viewModel.verboseLogger?.logBinderReceivedSignalIcon(
-                            view,
-                            viewModel.subscriptionId,
-                            icon,
-                        )
-                        mobileDrawable.level = icon.toSignalDrawableState()
+                    // Set the icon for the triangle
+                    launch {
+                        viewModel.icon.distinctUntilChanged().collect { icon ->
+                            viewModel.verboseLogger?.logBinderReceivedSignalIcon(
+                                view,
+                                viewModel.subscriptionId,
+                                icon,
+                            )
+                            mobileDrawable.level = icon.toSignalDrawableState()
+                        }
                     }
-                }
 
-                launch {
-                    viewModel.contentDescription.distinctUntilChanged().collect {
-                        ContentDescriptionViewBinder.bind(it, view)
+                    launch {
+                        viewModel.contentDescription.distinctUntilChanged().collect {
+                            ContentDescriptionViewBinder.bind(it, view)
+                        }
                     }
-                }
 
-                // Set the network type icon
-                launch {
-                    viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
-                        viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon(
-                            view,
-                            viewModel.subscriptionId,
-                            dataTypeId,
-                        )
-                        dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
-                        networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+                    // Set the network type icon
+                    launch {
+                        viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
+                            viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon(
+                                view,
+                                viewModel.subscriptionId,
+                                dataTypeId,
+                            )
+                            dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
+                            networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+                        }
                     }
-                }
 
-                // Set the roaming indicator
-                launch {
-                    viewModel.roaming.distinctUntilChanged().collect { isRoaming ->
-                        roamingView.isVisible = isRoaming
-                        roamingSpace.isVisible = isRoaming
+                    // Set the roaming indicator
+                    launch {
+                        viewModel.roaming.distinctUntilChanged().collect { isRoaming ->
+                            roamingView.isVisible = isRoaming
+                            roamingSpace.isVisible = isRoaming
+                        }
                     }
-                }
 
-                // Set the activity indicators
-                launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
+                    // Set the activity indicators
+                    launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
 
-                launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } }
+                    launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } }
 
-                launch {
-                    viewModel.activityContainerVisible.collect { activityContainer.isVisible = it }
-                }
-
-                // Set the tint
-                launch {
-                    iconTint.collect { tint ->
-                        val tintList = ColorStateList.valueOf(tint)
-                        iconView.imageTintList = tintList
-                        networkTypeView.imageTintList = tintList
-                        roamingView.imageTintList = tintList
-                        activityIn.imageTintList = tintList
-                        activityOut.imageTintList = tintList
-                        dotView.setDecorColor(tint)
+                    launch {
+                        viewModel.activityContainerVisible.collect {
+                            activityContainer.isVisible = it
+                        }
                     }
-                }
 
-                launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+                    // Set the tint
+                    launch {
+                        iconTint.collect { tint ->
+                            val tintList = ColorStateList.valueOf(tint)
+                            iconView.imageTintList = tintList
+                            networkTypeView.imageTintList = tintList
+                            roamingView.imageTintList = tintList
+                            activityIn.imageTintList = tintList
+                            activityOut.imageTintList = tintList
+                            dotView.setDecorColor(tint)
+                        }
+                    }
 
-                try {
-                    awaitCancellation()
-                } finally {
-                    isCollecting = false
-                    logger.logCollectionStopped(view, viewModel)
+                    launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+
+                    try {
+                        awaitCancellation()
+                    } finally {
+                        isCollecting = false
+                        logger.logCollectionStopped(view, viewModel)
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
index 120ba4e..b6f1677 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
@@ -65,7 +65,7 @@
     // [DefaultConnectionModel]
     private val wifiIconFlow: Flow<InternetTileModel> =
         wifiInteractor.wifiNetwork.flatMapLatest {
-            val wifiIcon = WifiIcon.fromModel(it, context)
+            val wifiIcon = WifiIcon.fromModel(it, context, showHotspotInfo = true)
             if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) {
                 flowOf(
                     InternetTileModel.Active(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
index 8156500..668c5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -65,8 +65,18 @@
         @VisibleForTesting
         internal val NO_INTERNET = R.string.data_connection_no_internet
 
-        /** Mapping from a [WifiNetworkModel] to the appropriate [WifiIcon] */
-        fun fromModel(model: WifiNetworkModel, context: Context): WifiIcon =
+        /**
+         * Mapping from a [WifiNetworkModel] to the appropriate [WifiIcon].
+         *
+         * @param showHotspotInfo true if the wifi icon should represent the hotspot device (if it
+         *   exists) and false if the wifi icon should only ever show the wifi level and *not* the
+         *   hotspot device.
+         */
+        fun fromModel(
+            model: WifiNetworkModel,
+            context: Context,
+            showHotspotInfo: Boolean,
+        ): WifiIcon =
             when (model) {
                 is WifiNetworkModel.Unavailable -> Hidden
                 is WifiNetworkModel.Invalid -> Hidden
@@ -82,22 +92,50 @@
                     )
                 is WifiNetworkModel.Active -> {
                     val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[model.level])
-                    when {
-                        model.isValidated ->
-                            Visible(
-                                WifiIcons.WIFI_FULL_ICONS[model.level],
-                                ContentDescription.Loaded(levelDesc),
-                            )
-                        else ->
-                            Visible(
-                                WifiIcons.WIFI_NO_INTERNET_ICONS[model.level],
-                                ContentDescription.Loaded(
-                                    "$levelDesc,${context.getString(NO_INTERNET)}"
-                                ),
-                            )
-                    }
+                    val contentDescription =
+                        ContentDescription.Loaded(
+                            if (model.isValidated) {
+                                (levelDesc)
+                            } else {
+                                "$levelDesc,${context.getString(NO_INTERNET)}"
+                            }
+                        )
+                    Visible(model.toIcon(showHotspotInfo), contentDescription)
                 }
             }
+
+        @DrawableRes
+        private fun WifiNetworkModel.Active.toIcon(showHotspotInfo: Boolean): Int {
+            return if (!showHotspotInfo) {
+                this.toBasicIcon()
+            } else {
+                when (this.hotspotDeviceType) {
+                    WifiNetworkModel.HotspotDeviceType.NONE -> this.toBasicIcon()
+                    WifiNetworkModel.HotspotDeviceType.TABLET ->
+                        com.android.settingslib.R.drawable.ic_hotspot_tablet
+                    WifiNetworkModel.HotspotDeviceType.LAPTOP ->
+                        com.android.settingslib.R.drawable.ic_hotspot_laptop
+                    WifiNetworkModel.HotspotDeviceType.WATCH ->
+                        com.android.settingslib.R.drawable.ic_hotspot_watch
+                    WifiNetworkModel.HotspotDeviceType.AUTO ->
+                        com.android.settingslib.R.drawable.ic_hotspot_auto
+                    // Use phone as the default drawable
+                    WifiNetworkModel.HotspotDeviceType.PHONE,
+                    WifiNetworkModel.HotspotDeviceType.UNKNOWN,
+                    WifiNetworkModel.HotspotDeviceType.INVALID ->
+                        com.android.settingslib.R.drawable.ic_hotspot_phone
+                }
+            }
+        }
+
+        @DrawableRes
+        private fun WifiNetworkModel.Active.toBasicIcon(): Int {
+            return if (this.isValidated) {
+                WifiIcons.WIFI_FULL_ICONS[this.level]
+            } else {
+                WifiIcons.WIFI_NO_INTERNET_ICONS[this.level]
+            }
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 27ac7b9..d099c8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -66,11 +66,6 @@
     @Application private val scope: CoroutineScope,
     wifiConstants: WifiConstants,
 ) : WifiViewModelCommon {
-    /** Returns the icon to use based on the given network. */
-    private fun WifiNetworkModel.icon(): WifiIcon {
-        return WifiIcon.fromModel(this, context)
-    }
-
     override val wifiIcon: StateFlow<WifiIcon> =
         combine(
                 interactor.isEnabled,
@@ -82,7 +77,8 @@
                     return@combine WifiIcon.Hidden
                 }
 
-                val icon = wifiNetwork.icon()
+                // Don't show any hotspot info in the status bar.
+                val icon = WifiIcon.fromModel(wifiNetwork, context, showHotspotInfo = false)
 
                 return@combine when {
                     isDefault -> icon
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index b100336..f9830b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -71,6 +71,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -163,6 +164,204 @@
     }
 
     @Test
+    public void startListening_fetchesCurrentActive_none() {
+        when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+                .thenReturn(List.of());
+
+        mController.setListening(true);
+
+        assertThat(mController.getActiveAppOps()).isEmpty();
+    }
+
+    /** Regression test for b/294104969. */
+    @Test
+    public void startListening_fetchesCurrentActive_oneActive() {
+        AppOpsManager.PackageOps packageOps = createPackageOp(
+                "package.test",
+                /* packageUid= */ 2,
+                AppOpsManager.OPSTR_FINE_LOCATION,
+                /* isRunning= */ true);
+        when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+                .thenReturn(List.of(packageOps));
+
+        // WHEN we start listening
+        mController.setListening(true);
+
+        // THEN the active list has the op
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        AppOpItem first = list.get(0);
+        assertThat(first.getPackageName()).isEqualTo("package.test");
+        assertThat(first.getUid()).isEqualTo(2);
+        assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
+    }
+
+    @Test
+    public void startListening_fetchesCurrentActive_multiplePackages() {
+        AppOpsManager.PackageOps packageOps1 = createPackageOp(
+                "package.one",
+                /* packageUid= */ 1,
+                AppOpsManager.OPSTR_FINE_LOCATION,
+                /* isRunning= */ true);
+        AppOpsManager.PackageOps packageOps2 = createPackageOp(
+                "package.two",
+                /* packageUid= */ 2,
+                AppOpsManager.OPSTR_FINE_LOCATION,
+                /* isRunning= */ false);
+        AppOpsManager.PackageOps packageOps3 = createPackageOp(
+                "package.three",
+                /* packageUid= */ 3,
+                AppOpsManager.OPSTR_FINE_LOCATION,
+                /* isRunning= */ true);
+        when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+                .thenReturn(List.of(packageOps1, packageOps2, packageOps3));
+
+        // WHEN we start listening
+        mController.setListening(true);
+
+        // THEN the active list has the ops
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(2, list.size());
+
+        AppOpItem item0 = list.get(0);
+        assertThat(item0.getPackageName()).isEqualTo("package.one");
+        assertThat(item0.getUid()).isEqualTo(1);
+        assertThat(item0.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
+
+        AppOpItem item1 = list.get(1);
+        assertThat(item1.getPackageName()).isEqualTo("package.three");
+        assertThat(item1.getUid()).isEqualTo(3);
+        assertThat(item1.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
+    }
+
+    @Test
+    public void startListening_fetchesCurrentActive_multipleEntries() {
+        AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
+        when(packageOps.getUid()).thenReturn(1);
+        when(packageOps.getPackageName()).thenReturn("package.one");
+
+        // Entry 1
+        AppOpsManager.OpEntry entry1 = mock(AppOpsManager.OpEntry.class);
+        when(entry1.getOpStr()).thenReturn(AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE);
+        AppOpsManager.AttributedOpEntry attributed1 = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed1.isRunning()).thenReturn(true);
+        when(entry1.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed1));
+        // Entry 2
+        AppOpsManager.OpEntry entry2 = mock(AppOpsManager.OpEntry.class);
+        when(entry2.getOpStr()).thenReturn(AppOpsManager.OPSTR_CAMERA);
+        AppOpsManager.AttributedOpEntry attributed2 = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed2.isRunning()).thenReturn(true);
+        when(entry2.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed2));
+        // Entry 3
+        AppOpsManager.OpEntry entry3 = mock(AppOpsManager.OpEntry.class);
+        when(entry3.getOpStr()).thenReturn(AppOpsManager.OPSTR_FINE_LOCATION);
+        AppOpsManager.AttributedOpEntry attributed3 = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed3.isRunning()).thenReturn(false);
+        when(entry3.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed3));
+
+        when(packageOps.getOps()).thenReturn(List.of(entry1, entry2, entry3));
+        when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+                .thenReturn(List.of(packageOps));
+
+        // WHEN we start listening
+        mController.setListening(true);
+
+        // THEN the active list has the ops
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(2, list.size());
+
+        AppOpItem first = list.get(0);
+        assertThat(first.getPackageName()).isEqualTo("package.one");
+        assertThat(first.getUid()).isEqualTo(1);
+        assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_PHONE_CALL_MICROPHONE);
+
+        AppOpItem second = list.get(1);
+        assertThat(second.getPackageName()).isEqualTo("package.one");
+        assertThat(second.getUid()).isEqualTo(1);
+        assertThat(second.getCode()).isEqualTo(AppOpsManager.OP_CAMERA);
+    }
+
+    @Test
+    public void startListening_fetchesCurrentActive_multipleAttributes() {
+        AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
+        when(packageOps.getUid()).thenReturn(1);
+        when(packageOps.getPackageName()).thenReturn("package.one");
+        AppOpsManager.OpEntry entry = mock(AppOpsManager.OpEntry.class);
+        when(entry.getOpStr()).thenReturn(AppOpsManager.OPSTR_RECORD_AUDIO);
+
+        AppOpsManager.AttributedOpEntry attributed1 = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed1.isRunning()).thenReturn(false);
+        AppOpsManager.AttributedOpEntry attributed2 = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed2.isRunning()).thenReturn(true);
+        AppOpsManager.AttributedOpEntry attributed3 = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed3.isRunning()).thenReturn(true);
+        when(entry.getAttributedOpEntries()).thenReturn(
+                Map.of("attr1", attributed1, "attr2", attributed2, "attr3", attributed3));
+
+        when(packageOps.getOps()).thenReturn(List.of(entry));
+        when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+                .thenReturn(List.of(packageOps));
+
+        // WHEN we start listening
+        mController.setListening(true);
+
+        // THEN the active list has the ops
+        List<AppOpItem> list = mController.getActiveAppOps();
+        // Multiple attributes get merged into one entry in the active ops
+        assertEquals(1, list.size());
+
+        AppOpItem first = list.get(0);
+        assertThat(first.getPackageName()).isEqualTo("package.one");
+        assertThat(first.getUid()).isEqualTo(1);
+        assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_RECORD_AUDIO);
+    }
+
+    /** Regression test for b/294104969. */
+    @Test
+    public void addCallback_existingCallbacksNotifiedOfCurrentActive() {
+        AppOpsManager.PackageOps packageOps1 = createPackageOp(
+                "package.one",
+                /* packageUid= */ 1,
+                AppOpsManager.OPSTR_FINE_LOCATION,
+                /* isRunning= */ true);
+        AppOpsManager.PackageOps packageOps2 = createPackageOp(
+                "package.two",
+                /* packageUid= */ 2,
+                AppOpsManager.OPSTR_RECORD_AUDIO,
+                /* isRunning= */ true);
+        AppOpsManager.PackageOps packageOps3 = createPackageOp(
+                "package.three",
+                /* packageUid= */ 3,
+                AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE,
+                /* isRunning= */ true);
+        when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+                .thenReturn(List.of(packageOps1, packageOps2, packageOps3));
+
+        // WHEN we start listening
+        mController.addCallback(
+                new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION},
+                mCallback);
+        mTestableLooper.processAllMessages();
+
+        // THEN the callback is notified of the current active ops it cares about
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION,
+                /* uid= */ 1,
+                "package.one",
+                true);
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_RECORD_AUDIO,
+                /* uid= */ 2,
+                "package.two",
+                true);
+        verify(mCallback, never()).onActiveStateChanged(
+                AppOpsManager.OP_PHONE_CALL_MICROPHONE,
+                /* uid= */ 3,
+                "package.three",
+                true);
+    }
+
+    @Test
     public void addCallback_includedCode() {
         mController.addCallback(
                 new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION},
@@ -772,6 +971,22 @@
         assertFalse(list.get(1).isDisabled());
     }
 
+    private AppOpsManager.PackageOps createPackageOp(
+            String packageName, int packageUid, String opStr, boolean isRunning) {
+        AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
+        when(packageOps.getPackageName()).thenReturn(packageName);
+        when(packageOps.getUid()).thenReturn(packageUid);
+        AppOpsManager.OpEntry entry = mock(AppOpsManager.OpEntry.class);
+        when(entry.getOpStr()).thenReturn(opStr);
+        AppOpsManager.AttributedOpEntry attributed = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed.isRunning()).thenReturn(isRunning);
+
+        when(packageOps.getOps()).thenReturn(Collections.singletonList(entry));
+        when(entry.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed));
+
+        return packageOps;
+    }
+
     private class TestHandler extends AppOpsControllerImpl.H {
         TestHandler(Looper looper) {
             mController.super(looper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 88c710a..ddb482f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -16,18 +16,46 @@
 
 package com.android.systemui.back.domain.interactor
 
+import android.view.ViewRootImpl
+import android.window.BackEvent
+import android.window.BackEvent.EDGE_LEFT
+import android.window.OnBackAnimationCallback
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import android.window.WindowOnBackInvokedDispatcher
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IStatusBarService
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.QuickSettingsController
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -41,7 +69,12 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
 class BackActionInteractorTest : SysuiTestCase() {
+    private val testScope = TestScope()
+    private val featureFlags = FakeFeatureFlags()
+    private val executor = FakeExecutor(FakeSystemClock())
+
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
 
     @Mock private lateinit var statusBarStateController: StatusBarStateController
@@ -49,18 +82,42 @@
     @Mock private lateinit var shadeController: ShadeController
     @Mock private lateinit var qsController: QuickSettingsController
     @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+    @Mock private lateinit var windowRootView: WindowRootView
+    @Mock private lateinit var viewRootImpl: ViewRootImpl
+    @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
+    @Mock private lateinit var iStatusBarService: IStatusBarService
+    @Mock private lateinit var headsUpManager: HeadsUpManager
 
-    private lateinit var backActionInteractor: BackActionInteractor
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
+        WindowRootViewVisibilityInteractor(
+            testScope.backgroundScope,
+            WindowRootViewVisibilityRepository(iStatusBarService, executor),
+            keyguardRepository,
+            headsUpManager,
+        )
+    }
 
-    @Before
-    fun setup() {
-        backActionInteractor =
-            BackActionInteractor(
+    private val backActionInteractor: BackActionInteractor by lazy {
+        BackActionInteractor(
+                testScope.backgroundScope,
                 statusBarStateController,
                 statusBarKeyguardViewManager,
                 shadeController,
+                notificationShadeWindowController,
+                windowRootViewVisibilityInteractor,
+                featureFlags,
             )
-        backActionInteractor.setup(qsController, shadeViewController)
+            .apply { this.setup(qsController, shadeViewController) }
+    }
+
+    @Before
+    fun setUp() {
+        featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false)
+        whenever(notificationShadeWindowController.windowRootView).thenReturn(windowRootView)
+        whenever(windowRootView.viewRootImpl).thenReturn(viewRootImpl)
+        whenever(viewRootImpl.onBackInvokedDispatcher).thenReturn(onBackInvokedDispatcher)
     }
 
     @Test
@@ -117,4 +174,139 @@
         verify(statusBarKeyguardViewManager, never()).onBackPressed()
         verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
     }
+
+    @Test
+    fun shadeVisibleAndDeviceAwake_callbackRegistered() {
+        backActionInteractor.start()
+        windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+        setWakefulness(WakefulnessState.AWAKE)
+
+        testScope.runCurrent()
+
+        verify(onBackInvokedDispatcher)
+            .registerOnBackInvokedCallback(eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any())
+    }
+
+    @Test
+    fun noWindowRootView_noCrashAttemptingCallbackRegistration() {
+        whenever(notificationShadeWindowController.windowRootView).thenReturn(null)
+
+        backActionInteractor.start()
+        windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+        setWakefulness(WakefulnessState.AWAKE)
+
+        testScope.runCurrent()
+        // No assert necessary, just testing no crash
+    }
+
+    @Test
+    fun shadeNotVisible_callbackUnregistered() {
+        backActionInteractor.start()
+        windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+        setWakefulness(WakefulnessState.AWAKE)
+        val callback = getBackInvokedCallback()
+
+        windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false)
+        testScope.runCurrent()
+
+        verify(onBackInvokedDispatcher).unregisterOnBackInvokedCallback(callback)
+    }
+
+    @Test
+    fun deviceAsleep_callbackUnregistered() {
+        backActionInteractor.start()
+        windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+        setWakefulness(WakefulnessState.AWAKE)
+        val callback = getBackInvokedCallback()
+
+        setWakefulness(WakefulnessState.ASLEEP)
+        testScope.runCurrent()
+
+        verify(onBackInvokedDispatcher).unregisterOnBackInvokedCallback(callback)
+    }
+
+    @Test
+    fun animationFlagOff_onBackInvoked_keyguardNotified() {
+        backActionInteractor.start()
+        featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false)
+        windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+        setWakefulness(WakefulnessState.AWAKE)
+        val callback = getBackInvokedCallback()
+        whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true)
+
+        callback.onBackInvoked()
+
+        verify(statusBarKeyguardViewManager).onBackPressed()
+    }
+
+    @Test
+    fun animationFlagOn_onBackInvoked_keyguardNotified() {
+        featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
+        backActionInteractor.start()
+        windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+        setWakefulness(WakefulnessState.AWAKE)
+        val callback = getBackInvokedCallback()
+        whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true)
+
+        callback.onBackInvoked()
+
+        verify(statusBarKeyguardViewManager).onBackPressed()
+    }
+
+    @Test
+    fun animationFlagOn_callbackIsAnimationCallback() {
+        featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
+        backActionInteractor.start()
+        windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+        setWakefulness(WakefulnessState.AWAKE)
+
+        val callback = getBackInvokedCallback()
+
+        assertThat(callback).isInstanceOf(OnBackAnimationCallback::class.java)
+    }
+
+    @Test
+    fun onBackProgressed_shadeCannotBeCollapsed_shadeViewControllerNotNotified() {
+        featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
+        backActionInteractor.start()
+        windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+        setWakefulness(WakefulnessState.AWAKE)
+        val callback = getBackInvokedCallback() as OnBackAnimationCallback
+
+        whenever(shadeViewController.canBeCollapsed()).thenReturn(false)
+
+        callback.onBackProgressed(createBackEvent(0.3f))
+
+        verify(shadeViewController, never()).onBackProgressed(0.3f)
+    }
+
+    @Test
+    fun onBackProgressed_shadeCanBeCollapsed_shadeViewControllerNotified() {
+        featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
+        backActionInteractor.start()
+        windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+        setWakefulness(WakefulnessState.AWAKE)
+        val callback = getBackInvokedCallback() as OnBackAnimationCallback
+
+        whenever(shadeViewController.canBeCollapsed()).thenReturn(true)
+
+        callback.onBackProgressed(createBackEvent(0.4f))
+
+        verify(shadeViewController).onBackProgressed(0.4f)
+    }
+
+    private fun getBackInvokedCallback(): OnBackInvokedCallback {
+        testScope.runCurrent()
+        val captor = argumentCaptor<OnBackInvokedCallback>()
+        verify(onBackInvokedDispatcher).registerOnBackInvokedCallback(any(), captor.capture())
+        return captor.value!!
+    }
+
+    private fun createBackEvent(progress: Float): BackEvent =
+        BackEvent(/* touchX= */ 0f, /* touchY= */ 0f, progress, /* swipeEdge= */ EDGE_LEFT)
+
+    private fun setWakefulness(state: WakefulnessState) {
+        val model = WakefulnessModel(state, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+        keyguardRepository.setWakefulnessModel(model)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 47084c0..0ed46da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -20,6 +20,7 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
@@ -500,6 +501,81 @@
     }
 
     @Test
+    fun auto_confirm_authentication_when_finger_down() = runGenericTest {
+        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+        // No icon button when face only, can't confirm before auth
+        if (!testCase.isFaceOnly) {
+            viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
+        }
+        viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+        val authenticating by collectLastValue(viewModel.isAuthenticating)
+        val authenticated by collectLastValue(viewModel.isAuthenticated)
+        val message by collectLastValue(viewModel.message)
+        val size by collectLastValue(viewModel.size)
+        val legacyState by collectLastValue(viewModel.legacyState)
+        val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+        assertThat(authenticating).isFalse()
+        assertThat(canTryAgain).isFalse()
+        assertThat(authenticated?.isAuthenticated).isTrue()
+
+        if (testCase.isFaceOnly && expectConfirmation) {
+            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+
+            assertThat(size).isEqualTo(PromptSize.MEDIUM)
+            assertButtonsVisible(
+                cancel = true,
+                confirm = true,
+            )
+
+            viewModel.confirmAuthenticated()
+            assertThat(message).isEqualTo(PromptMessage.Empty)
+            assertButtonsVisible()
+        } else {
+            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+        }
+    }
+
+    @Test
+    fun cannot_auto_confirm_authentication_when_finger_up() = runGenericTest {
+        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+        // No icon button when face only, can't confirm before auth
+        if (!testCase.isFaceOnly) {
+            viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
+            viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_UP))
+        }
+        viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+        val authenticating by collectLastValue(viewModel.isAuthenticating)
+        val authenticated by collectLastValue(viewModel.isAuthenticated)
+        val message by collectLastValue(viewModel.message)
+        val size by collectLastValue(viewModel.size)
+        val legacyState by collectLastValue(viewModel.legacyState)
+        val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+        assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
+        if (expectConfirmation) {
+            assertThat(size).isEqualTo(PromptSize.MEDIUM)
+            assertButtonsVisible(
+                cancel = true,
+                confirm = true,
+            )
+
+            viewModel.confirmAuthenticated()
+            assertThat(message).isEqualTo(PromptMessage.Empty)
+            assertButtonsVisible()
+        }
+
+        assertThat(authenticating).isFalse()
+        assertThat(authenticated?.isAuthenticated).isTrue()
+        assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+        assertThat(canTryAgain).isFalse()
+    }
+
+    @Test
     fun cannot_confirm_unless_authenticated() = runGenericTest {
         val authenticating by collectLastValue(viewModel.isAuthenticating)
         val authenticated by collectLastValue(viewModel.isAuthenticated)
@@ -679,6 +755,10 @@
         testScope.runTest { block() }
     }
 
+    /** Obtain a MotionEvent with the specified MotionEvent action constant */
+    private fun obtainMotionEvent(action: Int): MotionEvent =
+        MotionEvent.obtain(0, 0, action, 0f, 0f, 0)
+
     companion object {
         @JvmStatic
         @Parameterized.Parameters(name = "{0}")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 0b27bc9..54f66dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import dagger.Lazy
 import java.util.Optional
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
@@ -173,10 +172,9 @@
     private fun setupComponent(enabled: Boolean): ControlsComponent {
         return ControlsComponent(
             enabled,
-            mContext,
-            Lazy { controller },
-            Lazy { uiController },
-            Lazy { listingController },
+            { controller },
+            { uiController },
+            { listingController },
             lockPatternUtils,
             keyguardStateController,
             userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 7d836a0..db7c003 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -230,7 +230,7 @@
     }
 
     private fun TestScope.latestPendingDisplayFlowValue(): FlowValue<Int?> {
-        val flowValue = collectLastValue(displayRepository.pendingDisplay)
+        val flowValue = collectLastValue(displayRepository.pendingDisplayId)
         verify(displayManager)
             .registerDisplayListener(
                 displayListener.capture(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
index fb19eca..50617a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.display.data.repository.display
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -37,6 +38,7 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito
@@ -49,10 +51,20 @@
 
     private val displayManager = mock<DisplayManager>()
     private val fakeDisplayRepository = FakeDisplayRepository()
+    private val fakeKeyguardRepository = FakeKeyguardRepository()
     private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
-        ConnectedDisplayInteractorImpl(displayManager, fakeDisplayRepository)
+        ConnectedDisplayInteractorImpl(
+            displayManager,
+            fakeKeyguardRepository,
+            fakeDisplayRepository
+        )
     private val testScope = TestScope(UnconfinedTestDispatcher())
 
+    @Before
+    fun setup() {
+        fakeKeyguardRepository.setKeyguardUnlocked(true)
+    }
+
     @Test
     fun displayState_nullDisplays_disconnected() =
         testScope.runTest {
@@ -165,6 +177,34 @@
             Mockito.verify(displayManager).disableConnectedDisplay(eq(1))
         }
 
+    @Test
+    fun onPendingDisplay_keyguardUnlocked_returnsPendingDisplay() =
+        testScope.runTest {
+            fakeKeyguardRepository.setKeyguardUnlocked(false)
+            val pendingDisplay by lastPendingDisplay()
+
+            fakeDisplayRepository.emit(1)
+            assertThat(pendingDisplay).isNull()
+
+            fakeKeyguardRepository.setKeyguardUnlocked(true)
+
+            assertThat(pendingDisplay).isNotNull()
+        }
+
+    @Test
+    fun onPendingDisplay_keyguardLocked_returnsNull() =
+        testScope.runTest {
+            fakeKeyguardRepository.setKeyguardUnlocked(true)
+            val pendingDisplay by lastPendingDisplay()
+
+            fakeDisplayRepository.emit(1)
+            assertThat(pendingDisplay).isNotNull()
+
+            fakeKeyguardRepository.setKeyguardUnlocked(false)
+
+            assertThat(pendingDisplay).isNull()
+        }
+
     private fun TestScope.lastValue(): FlowValue<State?> =
         collectLastValue(connectedDisplayStateProvider.connectedDisplayState)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
new file mode 100644
index 0000000..71a56cd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.haptics.slider
+
+import android.widget.SeekBar
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SeekableSliderEventProducerTest : SysuiTestCase() {
+
+    private val seekBar = SeekBar(mContext)
+    private val eventProducer = SeekableSliderEventProducer()
+    private val eventFlow = eventProducer.produceEvents()
+
+    @Test
+    fun onStartTrackingTouch_noProgress_trackingTouchEventProduced() = runTest {
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onStartTrackingTouch(seekBar)
+
+        assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, 0F), latest)
+    }
+
+    @Test
+    fun onStopTrackingTouch_noProgress_StoppedTrackingTouchEventProduced() = runTest {
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onStopTrackingTouch(seekBar)
+
+        assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0F), latest)
+    }
+
+    @Test
+    fun onProgressChangeByUser_changeByUserEventProduced_withNormalizedProgress() = runTest {
+        val progress = 50
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onProgressChanged(seekBar, progress, true)
+
+        assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, 0.5F), latest)
+    }
+
+    @Test
+    fun onProgressChangeByUser_zeroWidthSlider_changeByUserEventProduced_withMaxProgress() =
+        runTest {
+            // No-width slider where the min and max values are the same
+            seekBar.min = 100
+            seekBar.max = 100
+            val progress = 50
+            val latest by collectLastValue(eventFlow)
+
+            eventProducer.onProgressChanged(seekBar, progress, true)
+
+            assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, 1.0F), latest)
+        }
+
+    @Test
+    fun onProgressChangeByProgram_changeByProgramEventProduced_withNormalizedProgress() = runTest {
+        val progress = 50
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onProgressChanged(seekBar, progress, false)
+
+        assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, 0.5F), latest)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_zeroWidthSlider_changeByProgramEventProduced_withMaxProgress() =
+        runTest {
+            // No-width slider where the min and max values are the same
+            seekBar.min = 100
+            seekBar.max = 100
+            val progress = 50
+            val latest by collectLastValue(eventFlow)
+
+            eventProducer.onProgressChanged(seekBar, progress, false)
+
+            assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, 1.0F), latest)
+        }
+
+    @Test
+    fun onStartTrackingTouch_afterProgress_trackingTouchEventProduced_withNormalizedProgress() =
+        runTest {
+            val progress = 50
+            val latest by collectLastValue(eventFlow)
+
+            eventProducer.onProgressChanged(seekBar, progress, true)
+            eventProducer.onStartTrackingTouch(seekBar)
+
+            assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, 0.5F), latest)
+        }
+
+    @Test
+    fun onStopTrackingTouch_afterProgress_stopTrackingTouchEventProduced_withNormalizedProgress() =
+        runTest {
+            val progress = 50
+            val latest by collectLastValue(eventFlow)
+
+            eventProducer.onProgressChanged(seekBar, progress, true)
+            eventProducer.onStopTrackingTouch(seekBar)
+
+            assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 9886c22..f78d051 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -221,6 +221,7 @@
         mSystemClock = new FakeSystemClock();
         when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
         when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
+        when(mPowerManager.isInteractive()).thenReturn(true);
         when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
         when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
         mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
@@ -869,8 +870,6 @@
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         TestableLooper.get(this).processAllMessages();
 
-        when(mPowerManager.isInteractive()).thenReturn(true);
-
         mViewMediator.onSystemReady();
         TestableLooper.get(this).processAllMessages();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 5ead16b..2691860 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
@@ -322,20 +323,20 @@
 
             val captor = argumentCaptor<StatusBarStateController.StateListener>()
             runCurrent()
-            verify(statusBarStateController).addCallback(captor.capture())
+            verify(statusBarStateController, atLeastOnce()).addCallback(captor.capture())
 
-            captor.value.onDozeAmountChanged(0.433f, 0.4f)
+            captor.allValues.forEach { it.onDozeAmountChanged(0.433f, 0.4f) }
             runCurrent()
-            captor.value.onDozeAmountChanged(0.498f, 0.5f)
+            captor.allValues.forEach { it.onDozeAmountChanged(0.498f, 0.5f) }
             runCurrent()
-            captor.value.onDozeAmountChanged(0.661f, 0.65f)
+            captor.allValues.forEach { it.onDozeAmountChanged(0.661f, 0.65f) }
             runCurrent()
 
             assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f))
 
             job.cancel()
             runCurrent()
-            verify(statusBarStateController).removeCallback(captor.value)
+            verify(statusBarStateController).removeCallback(any())
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index ca93246..d457605 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1049,7 +1049,6 @@
     @Test
     fun occludedToAlternateBouncer() =
         testScope.runTest {
-
             // GIVEN a prior transition has run to OCCLUDED
             runTransition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED)
             keyguardRepository.setKeyguardOccluded(true)
@@ -1073,6 +1072,31 @@
         }
 
     @Test
+    fun occludedToPrimaryBouncer() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to OCCLUDED
+            runTransition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED)
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            // WHEN primary bouncer shows
+            bouncerRepository.setPrimaryShow(true) // beverlyt
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to AlternateBouncer should occur
+            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun primaryBouncerToOccluded() =
         testScope.runTest {
             // GIVEN device not sleeping
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index a9f288d..b30dc9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -23,15 +23,13 @@
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class LockscreenSceneViewModelTest : SysuiTestCase() {
@@ -47,10 +45,9 @@
     private val underTest =
         LockscreenSceneViewModel(
             authenticationInteractor = authenticationInteractor,
-            bouncerInteractor =
-                utils.bouncerInteractor(
-                    authenticationInteractor = authenticationInteractor,
-                    sceneInteractor = sceneInteractor,
+            longPress =
+                KeyguardLongPressViewModel(
+                    interactor = mock(),
                 ),
         )
 
@@ -76,30 +73,4 @@
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
         }
-
-    @Test
-    fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(false)
-            runCurrent()
-
-            underTest.onLockButtonClicked()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-        }
-
-    @Test
-    fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setUnlocked(true)
-            runCurrent()
-
-            underTest.onLockButtonClicked()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index ef51e47..3961a94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -33,7 +33,6 @@
 import com.android.keyguard.TestScopeProvider
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -105,7 +104,6 @@
     @Mock @Main private lateinit var executor: DelayableExecutor
     @Mock lateinit var mediaDataManager: MediaDataManager
     @Mock lateinit var configurationController: ConfigurationController
-    @Mock lateinit var falsingCollector: FalsingCollector
     @Mock lateinit var falsingManager: FalsingManager
     @Mock lateinit var dumpManager: DumpManager
     @Mock lateinit var logger: MediaUiEventLogger
@@ -146,7 +144,6 @@
                 executor,
                 mediaDataManager,
                 configurationController,
-                falsingCollector,
                 falsingManager,
                 dumpManager,
                 logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 338182a..b9ee19b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -36,6 +36,8 @@
 import android.os.PowerManager;
 import android.os.Temperature;
 import android.provider.Settings;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -50,20 +52,17 @@
 import com.android.systemui.power.PowerUI.WarningsUI;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.time.Duration;
-import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 
-import dagger.Lazy;
-
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
@@ -93,15 +92,12 @@
     private IThermalEventListener mSkinThermalEventListener;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private CommandQueue mCommandQueue;
-    @Mock private Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
-    @Mock private CentralSurfaces mCentralSurfaces;
+    @Mock private IVrManager mVrManager;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        when(mCentralSurfacesOptionalLazy.get()).thenReturn(Optional.of(mCentralSurfaces));
-
         createPowerUi();
         mSkinThermalEventListener = mPowerUI.new SkinThermalEventListener();
         mUsbThermalEventListener = mPowerUI.new UsbThermalEventListener();
@@ -143,6 +139,23 @@
     }
 
     @Test
+    public void testSkinWarning_throttlingEmergency_butVrMode() throws Exception {
+        mPowerUI.start();
+
+        ArgumentCaptor<IVrStateCallbacks> vrCallback =
+                ArgumentCaptor.forClass(IVrStateCallbacks.class);
+        verify(mVrManager).registerListener(vrCallback.capture());
+
+        vrCallback.getValue().onVrStateChanged(true);
+        final Temperature temp = getEmergencyStatusTemp(Temperature.TYPE_SKIN, "skin2");
+        mSkinThermalEventListener.notifyThrottling(temp);
+
+        TestableLooper.get(this).processAllMessages();
+        // don't show skin high temperature warning when in VR mode
+        verify(mMockWarnings, never()).showHighTemperatureWarning();
+    }
+
+    @Test
     public void testUsbAlarm_throttlingCritical() throws Exception {
         mPowerUI.start();
 
@@ -683,8 +696,14 @@
 
     private void createPowerUi() {
         mPowerUI = new PowerUI(
-                mContext, mBroadcastDispatcher, mCommandQueue, mCentralSurfacesOptionalLazy,
-                mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager,
+                mContext,
+                mBroadcastDispatcher,
+                mCommandQueue,
+                mVrManager,
+                mMockWarnings,
+                mEnhancedEstimates,
+                mWakefulnessLifecycle,
+                mPowerManager,
                 mUserTracker);
         mPowerUI.mThermalService = mThermalServiceMock;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 0ecbcde..6006cd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.model.SysUiState
 import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
@@ -99,7 +100,8 @@
         )
     private val sceneContainerViewModel =
         SceneContainerViewModel(
-                interactor = sceneInteractor,
+                sceneInteractor = sceneInteractor,
+                falsingInteractor = utils.falsingInteractor(),
             )
             .apply { setTransitionState(transitionState) }
 
@@ -117,7 +119,10 @@
     private val lockscreenSceneViewModel =
         LockscreenSceneViewModel(
             authenticationInteractor = authenticationInteractor,
-            bouncerInteractor = bouncerInteractor,
+            longPress =
+                KeyguardLongPressViewModel(
+                    interactor = mock(),
+                ),
         )
 
     private val shadeSceneViewModel =
@@ -151,6 +156,7 @@
                 sysUiState = sysUiState,
                 displayId = displayTracker.defaultDisplayId,
                 sceneLogger = mock(),
+                falsingCollector = utils.falsingCollector(),
             )
         startable.start()
 
@@ -165,9 +171,7 @@
     @Test
     fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
         testScope.runTest {
-            lockscreenSceneViewModel.onLockButtonClicked()
-            assertCurrentScene(SceneKey.Bouncer)
-            emulateUiSceneTransition()
+            emulateUserDrivenTransition(SceneKey.Bouncer)
 
             enterPin()
             assertCurrentScene(SceneKey.Gone)
@@ -460,10 +464,7 @@
             .that(authenticationInteractor.isUnlocked.value)
             .isFalse()
 
-        lockscreenSceneViewModel.onLockButtonClicked()
-        runCurrent()
-        emulateUiSceneTransition()
-
+        emulateUserDrivenTransition(SceneKey.Bouncer)
         enterPin()
         emulateUiSceneTransition(
             expectedVisible = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
new file mode 100644
index 0000000..2187f36
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
@@ -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.systemui.scene.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+class WindowRootViewVisibilityRepositoryTest : SysuiTestCase() {
+    private val iStatusBarService = mock<IStatusBarService>()
+    private val executor = FakeExecutor(FakeSystemClock())
+    private val underTest = WindowRootViewVisibilityRepository(iStatusBarService, executor)
+
+    @Test
+    fun isLockscreenOrShadeVisible_true() {
+        underTest.setIsLockscreenOrShadeVisible(true)
+
+        assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue()
+    }
+
+    @Test
+    fun isLockscreenOrShadeVisible_false() {
+        underTest.setIsLockscreenOrShadeVisible(false)
+
+        assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse()
+    }
+
+    @Test
+    fun onLockscreenOrShadeInteractive_statusBarServiceNotified() {
+        underTest.onLockscreenOrShadeInteractive(
+            shouldClearNotificationEffects = true,
+            notificationCount = 3,
+        )
+        executor.runAllReady()
+
+        verify(iStatusBarService).onPanelRevealed(true, 3)
+    }
+
+    @Test
+    fun onLockscreenOrShadeNotInteractive_statusBarServiceNotified() {
+        underTest.onLockscreenOrShadeNotInteractive()
+        executor.runAllReady()
+
+        verify(iStatusBarService).onPanelHidden()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
new file mode 100644
index 0000000..f304435
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -0,0 +1,346 @@
+/*
+ * 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.scene.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
+
+    private val testScope = TestScope()
+    private val iStatusBarService = mock<IStatusBarService>()
+    private val executor = FakeExecutor(FakeSystemClock())
+    private val windowRootViewVisibilityRepository =
+        WindowRootViewVisibilityRepository(iStatusBarService, executor)
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val headsUpManager = mock<HeadsUpManager>()
+    private val notificationPresenter = mock<NotificationPresenter>()
+    private val notificationsController = mock<NotificationsController>()
+
+    private val underTest =
+        WindowRootViewVisibilityInteractor(
+                testScope.backgroundScope,
+                windowRootViewVisibilityRepository,
+                keyguardRepository,
+                headsUpManager,
+            )
+            .apply { setUp(notificationPresenter, notificationsController) }
+
+    @Test
+    fun isLockscreenOrShadeVisible_true() {
+        underTest.setIsLockscreenOrShadeVisible(true)
+
+        assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue()
+    }
+
+    @Test
+    fun isLockscreenOrShadeVisible_false() {
+        underTest.setIsLockscreenOrShadeVisible(false)
+
+        assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse()
+    }
+
+    @Test
+    fun isLockscreenOrShadeVisible_matchesRepo() {
+        windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(true)
+
+        assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue()
+
+        windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(false)
+
+        assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse()
+    }
+
+    @Test
+    fun isLockscreenOrShadeVisibleAndInteractive_notVisible_false() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+            setWakefulness(WakefulnessState.AWAKE)
+
+            underTest.setIsLockscreenOrShadeVisible(false)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isLockscreenOrShadeVisibleAndInteractive_deviceAsleep_false() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+            underTest.setIsLockscreenOrShadeVisible(true)
+
+            setWakefulness(WakefulnessState.ASLEEP)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isLockscreenOrShadeVisibleAndInteractive_visibleAndAwake_true() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+
+            underTest.setIsLockscreenOrShadeVisible(true)
+            setWakefulness(WakefulnessState.AWAKE)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isLockscreenOrShadeVisibleAndInteractive_visibleAndStartingToWake_true() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+
+            underTest.setIsLockscreenOrShadeVisible(true)
+            setWakefulness(WakefulnessState.STARTING_TO_WAKE)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isLockscreenOrShadeVisibleAndInteractive_visibleAndStartingToSleep_true() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+
+            underTest.setIsLockscreenOrShadeVisible(true)
+            setWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun lockscreenShadeInteractive_statusBarServiceNotified() =
+        testScope.runTest {
+            underTest.start()
+
+            makeLockscreenShadeVisible()
+            testScope.runCurrent()
+            executor.runAllReady()
+
+            verify(iStatusBarService).onPanelRevealed(any(), any())
+        }
+
+    @Test
+    fun lockscreenShadeNotInteractive_statusBarServiceNotified() =
+        testScope.runTest {
+            underTest.start()
+
+            // First, make the shade visible
+            makeLockscreenShadeVisible()
+            testScope.runCurrent()
+            reset(iStatusBarService)
+
+            // WHEN lockscreen or shade is no longer visible
+            underTest.setIsLockscreenOrShadeVisible(false)
+            testScope.runCurrent()
+            executor.runAllReady()
+
+            // THEN status bar service is notified
+            verify(iStatusBarService).onPanelHidden()
+        }
+
+    @Test
+    fun lockscreenShadeInteractive_presenterCollapsed_notifEffectsNotCleared() =
+        testScope.runTest {
+            underTest.start()
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+
+            whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+            makeLockscreenShadeVisible()
+
+            val shouldClearNotifEffects = argumentCaptor<Boolean>()
+            verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+            assertThat(shouldClearNotifEffects.value).isFalse()
+        }
+
+    @Test
+    fun lockscreenShadeInteractive_nullPresenter_notifEffectsNotCleared() =
+        testScope.runTest {
+            underTest.start()
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+
+            underTest.setUp(presenter = null, notificationsController)
+
+            makeLockscreenShadeVisible()
+
+            val shouldClearNotifEffects = argumentCaptor<Boolean>()
+            verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+            assertThat(shouldClearNotifEffects.value).isFalse()
+        }
+
+    @Test
+    fun lockscreenShadeInteractive_stateKeyguard_notifEffectsNotCleared() =
+        testScope.runTest {
+            underTest.start()
+            whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+
+            makeLockscreenShadeVisible()
+
+            val shouldClearNotifEffects = argumentCaptor<Boolean>()
+            verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+            assertThat(shouldClearNotifEffects.value).isFalse()
+        }
+
+    @Test
+    fun lockscreenShadeInteractive_stateShade_presenterNotCollapsed_notifEffectsCleared() =
+        testScope.runTest {
+            underTest.start()
+            whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+
+            makeLockscreenShadeVisible()
+
+            val shouldClearNotifEffects = argumentCaptor<Boolean>()
+            verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+            assertThat(shouldClearNotifEffects.value).isTrue()
+        }
+
+    @Test
+    fun lockscreenShadeInteractive_stateShadeLocked_presenterNotCollapsed_notifEffectsCleared() =
+        testScope.runTest {
+            underTest.start()
+            whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+
+            makeLockscreenShadeVisible()
+
+            val shouldClearNotifEffects = argumentCaptor<Boolean>()
+            verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+            assertThat(shouldClearNotifEffects.value).isTrue()
+        }
+
+    @Test
+    fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() =
+        testScope.runTest {
+            underTest.start()
+
+            whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+            whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+            whenever(notificationsController.getActiveNotificationsCount()).thenReturn(4)
+
+            makeLockscreenShadeVisible()
+
+            val notifCount = argumentCaptor<Int>()
+            verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+            assertThat(notifCount.value).isEqualTo(1)
+        }
+
+    @Test
+    fun lockscreenShadeInteractive_hasHeadsUpAndNullPresenter_notifCountOne() =
+        testScope.runTest {
+            underTest.start()
+
+            whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+            underTest.setUp(presenter = null, notificationsController)
+
+            makeLockscreenShadeVisible()
+
+            val notifCount = argumentCaptor<Int>()
+            verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+            assertThat(notifCount.value).isEqualTo(1)
+        }
+
+    @Test
+    fun lockscreenShadeInteractive_noHeadsUp_notifCountMatchesNotifController() =
+        testScope.runTest {
+            underTest.start()
+            whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+            whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+            whenever(notificationsController.getActiveNotificationsCount()).thenReturn(9)
+
+            makeLockscreenShadeVisible()
+
+            val notifCount = argumentCaptor<Int>()
+            verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+            assertThat(notifCount.value).isEqualTo(9)
+        }
+
+    @Test
+    fun lockscreenShadeInteractive_notifPresenterNotCollapsed_notifCountMatchesNotifController() =
+        testScope.runTest {
+            underTest.start()
+            whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+
+            whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+            whenever(notificationsController.getActiveNotificationsCount()).thenReturn(8)
+
+            makeLockscreenShadeVisible()
+
+            val notifCount = argumentCaptor<Int>()
+            verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+            assertThat(notifCount.value).isEqualTo(8)
+        }
+
+    @Test
+    fun lockscreenShadeInteractive_noHeadsUp_noNotifController_notifCountZero() =
+        testScope.runTest {
+            underTest.start()
+            whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+            whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+            underTest.setUp(notificationPresenter, notificationsController = null)
+
+            makeLockscreenShadeVisible()
+
+            val notifCount = argumentCaptor<Int>()
+            verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+            assertThat(notifCount.value).isEqualTo(0)
+        }
+
+    private fun makeLockscreenShadeVisible() {
+        underTest.setIsLockscreenOrShadeVisible(true)
+        setWakefulness(WakefulnessState.AWAKE)
+        testScope.runCurrent()
+        executor.runAllReady()
+    }
+
+    private fun setWakefulness(state: WakefulnessState) {
+        val model = WakefulnessModel(state, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+        keyguardRepository.setWakefulnessModel(model)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index d60d994..771c3e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -45,6 +46,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
@@ -60,6 +62,7 @@
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = authenticationRepository,
+            sceneInteractor = sceneInteractor,
         )
     private val keyguardRepository = utils.keyguardRepository
     private val keyguardInteractor =
@@ -67,6 +70,7 @@
             repository = keyguardRepository,
         )
     private val sysUiState: SysUiState = mock()
+    private val falsingCollector: FalsingCollector = mock()
 
     private val underTest =
         SceneContainerStartable(
@@ -78,6 +82,7 @@
             sysUiState = sysUiState,
             displayId = Display.DEFAULT_DISPLAY,
             sceneLogger = mock(),
+            falsingCollector = falsingCollector,
         )
 
     @Test
@@ -243,7 +248,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -259,7 +264,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -275,7 +280,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -294,11 +299,217 @@
 
             authenticationRepository.setUnlocked(true)
             runCurrent()
-            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
 
+    @Test
+    fun collectFalsingSignals_onSuccessfulUnlock() =
+        testScope.runTest {
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
+            )
+            underTest.start()
+            runCurrent()
+            verify(falsingCollector, never()).onSuccessfulUnlock()
+
+            // Move around scenes without unlocking.
+            listOf(
+                    SceneKey.Shade,
+                    SceneKey.QuickSettings,
+                    SceneKey.Shade,
+                    SceneKey.Lockscreen,
+                    SceneKey.Bouncer,
+                )
+                .forEach { sceneKey ->
+                    sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+                    runCurrent()
+                    verify(falsingCollector, never()).onSuccessfulUnlock()
+                }
+
+            // Changing to the Gone scene should report a successful unlock.
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+            runCurrent()
+            verify(falsingCollector).onSuccessfulUnlock()
+
+            // Move around scenes without changing back to Lockscreen, shouldn't report another
+            // unlock.
+            listOf(
+                    SceneKey.Shade,
+                    SceneKey.QuickSettings,
+                    SceneKey.Shade,
+                    SceneKey.Gone,
+                )
+                .forEach { sceneKey ->
+                    sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+                    runCurrent()
+                    verify(falsingCollector, times(1)).onSuccessfulUnlock()
+                }
+
+            // Changing to the Lockscreen scene shouldn't report a successful unlock.
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
+            runCurrent()
+            verify(falsingCollector, times(1)).onSuccessfulUnlock()
+
+            // Move around scenes without unlocking.
+            listOf(
+                    SceneKey.Shade,
+                    SceneKey.QuickSettings,
+                    SceneKey.Shade,
+                    SceneKey.Lockscreen,
+                    SceneKey.Bouncer,
+                )
+                .forEach { sceneKey ->
+                    sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+                    runCurrent()
+                    verify(falsingCollector, times(1)).onSuccessfulUnlock()
+                }
+
+            // Changing to the Gone scene should report a second successful unlock.
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+            runCurrent()
+            verify(falsingCollector, times(2)).onSuccessfulUnlock()
+        }
+
+    @Test
+    fun collectFalsingSignals_setShowingAod() =
+        testScope.runTest {
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
+            )
+            underTest.start()
+            runCurrent()
+            verify(falsingCollector).setShowingAod(false)
+
+            keyguardRepository.setIsDozing(true)
+            runCurrent()
+            verify(falsingCollector).setShowingAod(true)
+
+            keyguardRepository.setIsDozing(false)
+            runCurrent()
+            verify(falsingCollector, times(2)).setShowingAod(false)
+        }
+
+    @Test
+    fun collectFalsingSignals_screenOnAndOff_aodUnavailable() =
+        testScope.runTest {
+            keyguardRepository.setAodAvailable(false)
+            runCurrent()
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
+            )
+            underTest.start()
+            runCurrent()
+            verify(falsingCollector, never()).onScreenTurningOn()
+            verify(falsingCollector, never()).onScreenOnFromTouch()
+            verify(falsingCollector, never()).onScreenOff()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+            runCurrent()
+            verify(falsingCollector, times(1)).onScreenTurningOn()
+            verify(falsingCollector, never()).onScreenOnFromTouch()
+            verify(falsingCollector, never()).onScreenOff()
+
+            keyguardRepository.setWakefulnessModel(ASLEEP)
+            runCurrent()
+            verify(falsingCollector, times(1)).onScreenTurningOn()
+            verify(falsingCollector, never()).onScreenOnFromTouch()
+            verify(falsingCollector, times(1)).onScreenOff()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
+            runCurrent()
+            verify(falsingCollector, times(1)).onScreenTurningOn()
+            verify(falsingCollector, times(1)).onScreenOnFromTouch()
+            verify(falsingCollector, times(1)).onScreenOff()
+
+            keyguardRepository.setWakefulnessModel(ASLEEP)
+            runCurrent()
+            verify(falsingCollector, times(1)).onScreenTurningOn()
+            verify(falsingCollector, times(1)).onScreenOnFromTouch()
+            verify(falsingCollector, times(2)).onScreenOff()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+            runCurrent()
+            verify(falsingCollector, times(2)).onScreenTurningOn()
+            verify(falsingCollector, times(1)).onScreenOnFromTouch()
+            verify(falsingCollector, times(2)).onScreenOff()
+        }
+
+    @Test
+    fun collectFalsingSignals_screenOnAndOff_aodAvailable() =
+        testScope.runTest {
+            keyguardRepository.setAodAvailable(true)
+            runCurrent()
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
+            )
+            underTest.start()
+            runCurrent()
+            verify(falsingCollector, never()).onScreenTurningOn()
+            verify(falsingCollector, never()).onScreenOnFromTouch()
+            verify(falsingCollector, never()).onScreenOff()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+            runCurrent()
+            verify(falsingCollector, never()).onScreenTurningOn()
+            verify(falsingCollector, never()).onScreenOnFromTouch()
+            verify(falsingCollector, never()).onScreenOff()
+
+            keyguardRepository.setWakefulnessModel(ASLEEP)
+            runCurrent()
+            verify(falsingCollector, never()).onScreenTurningOn()
+            verify(falsingCollector, never()).onScreenOnFromTouch()
+            verify(falsingCollector, never()).onScreenOff()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
+            runCurrent()
+            verify(falsingCollector, never()).onScreenTurningOn()
+            verify(falsingCollector, never()).onScreenOnFromTouch()
+            verify(falsingCollector, never()).onScreenOff()
+
+            keyguardRepository.setWakefulnessModel(ASLEEP)
+            runCurrent()
+            verify(falsingCollector, never()).onScreenTurningOn()
+            verify(falsingCollector, never()).onScreenOnFromTouch()
+            verify(falsingCollector, never()).onScreenOff()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+            runCurrent()
+            verify(falsingCollector, never()).onScreenTurningOn()
+            verify(falsingCollector, never()).onScreenOnFromTouch()
+            verify(falsingCollector, never()).onScreenOff()
+        }
+
+    @Test
+    fun collectFalsingSignals_bouncerVisibility() =
+        testScope.runTest {
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
+            )
+            underTest.start()
+            runCurrent()
+            verify(falsingCollector).onBouncerHidden()
+
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            runCurrent()
+            verify(falsingCollector).onBouncerShown()
+
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+            runCurrent()
+            verify(falsingCollector, times(2)).onBouncerHidden()
+        }
+
     private fun prepareState(
         isDeviceUnlocked: Boolean = false,
         isBypassEnabled: Boolean = false,
@@ -334,11 +545,23 @@
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
                 lastSleepReason = WakeSleepReason.POWER_BUTTON
             )
-        private val STARTING_TO_WAKE =
+        private val ASLEEP =
+            WakefulnessModel(
+                state = WakefulnessState.ASLEEP,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.POWER_BUTTON
+            )
+        private val STARTING_TO_WAKE_FROM_POWER_BUTTON =
             WakefulnessModel(
                 state = WakefulnessState.STARTING_TO_WAKE,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
                 lastSleepReason = WakeSleepReason.POWER_BUTTON
             )
+        private val STARTING_TO_WAKE_FROM_TAP =
+            WakefulnessModel(
+                state = WakefulnessState.STARTING_TO_WAKE,
+                lastWakeReason = WakeSleepReason.TAP,
+                lastSleepReason = WakeSleepReason.POWER_BUTTON
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 88abb642..0b56a59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -39,7 +39,8 @@
     private val interactor = utils.sceneInteractor()
     private val underTest =
         SceneContainerViewModel(
-            interactor = interactor,
+            sceneInteractor = interactor,
+            falsingInteractor = utils.falsingInteractor(),
         )
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 209dcc1d..c573ac63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -716,7 +716,6 @@
                 mAmbientState,
                 mRecordingController,
                 mFalsingManager,
-                new FalsingCollectorFake(),
                 mAccessibilityManager,
                 mLockscreenGestureLogger,
                 mMetricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 2aea1f9..39fe498 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.dock.DockManager
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -102,6 +103,7 @@
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
     @Mock private lateinit var shadeLogger: ShadeLogger
+    @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var ambientState: AmbientState
     @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
     @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@@ -159,48 +161,49 @@
         fakeClock = FakeSystemClock()
         underTest =
             NotificationShadeWindowViewController(
-                    lockscreenShadeTransitionController,
-                    FalsingCollectorFake(),
-                    sysuiStatusBarStateController,
-                    dockManager,
-                    notificationShadeDepthController,
-                    view,
-                    notificationPanelViewController,
-                    ShadeExpansionStateManager(),
-                    stackScrollLayoutController,
-                    statusBarKeyguardViewManager,
-                    statusBarWindowStateController,
-                    lockIconViewController,
-                    centralSurfaces,
-                    dozeServiceHost,
-                    dozeScrimController,
-                    backActionInteractor,
-                    powerInteractor,
-                    notificationShadeWindowController,
-                    unfoldTransitionProgressProvider,
-                    keyguardUnlockAnimationController,
-                    notificationInsetsController,
-                    ambientState,
-                    shadeLogger,
-                    pulsingGestureListener,
-                    mLockscreenHostedDreamGestureListener,
-                    keyguardBouncerViewModel,
-                    keyguardBouncerComponentFactory,
-                    mock(KeyguardMessageAreaController.Factory::class.java),
-                    keyguardTransitionInteractor,
-                    primaryBouncerToGoneTransitionViewModel,
-                    notificationExpansionRepository,
-                    featureFlags,
-                    fakeClock,
-                    BouncerMessageInteractor(
-                        FakeBouncerMessageRepository(),
-                        mock(BouncerMessageFactory::class.java),
-                        FakeUserRepository(),
-                        CountDownTimerUtil(),
-                        featureFlags
-                    ),
-                    BouncerLogger(logcatLogBuffer("BouncerLog")),
-                    keyEventInteractor,
+                lockscreenShadeTransitionController,
+                FalsingCollectorFake(),
+                sysuiStatusBarStateController,
+                dockManager,
+                notificationShadeDepthController,
+                view,
+                notificationPanelViewController,
+                ShadeExpansionStateManager(),
+                stackScrollLayoutController,
+                statusBarKeyguardViewManager,
+                statusBarWindowStateController,
+                lockIconViewController,
+                centralSurfaces,
+                dozeServiceHost,
+                dozeScrimController,
+                backActionInteractor,
+                powerInteractor,
+                notificationShadeWindowController,
+                unfoldTransitionProgressProvider,
+                keyguardUnlockAnimationController,
+                notificationInsetsController,
+                ambientState,
+                shadeLogger,
+                dumpManager,
+                pulsingGestureListener,
+                mLockscreenHostedDreamGestureListener,
+                keyguardBouncerViewModel,
+                keyguardBouncerComponentFactory,
+                mock(KeyguardMessageAreaController.Factory::class.java),
+                keyguardTransitionInteractor,
+                primaryBouncerToGoneTransitionViewModel,
+                notificationExpansionRepository,
+                featureFlags,
+                fakeClock,
+                BouncerMessageInteractor(
+                    FakeBouncerMessageRepository(),
+                    mock(BouncerMessageFactory::class.java),
+                    FakeUserRepository(),
+                    CountDownTimerUtil(),
+                    featureFlags
+                ),
+                BouncerLogger(logcatLogBuffer("BouncerLog")),
+                keyEventInteractor,
             )
         underTest.setupExpandedStatusBar()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 1ab6134..3031658 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.dock.DockManager
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -111,6 +112,7 @@
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
     @Mock private lateinit var ambientState: AmbientState
     @Mock private lateinit var shadeLogger: ShadeLogger
+    @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
     @Mock
     private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@@ -188,6 +190,7 @@
                 notificationInsetsController,
                 ambientState,
                 shadeLogger,
+                dumpManager,
                 pulsingGestureListener,
                 mLockscreenHostedDreamGestureListener,
                 keyguardBouncerViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index e22e571..ab0ae05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -37,7 +37,6 @@
 import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
@@ -132,7 +131,6 @@
     @Mock protected AmbientState mAmbientState;
     @Mock protected RecordingController mRecordingController;
     @Mock protected FalsingManager mFalsingManager;
-    @Mock protected FalsingCollector mFalsingCollector;
     @Mock protected AccessibilityManager mAccessibilityManager;
     @Mock protected LockscreenGestureLogger mLockscreenGestureLogger;
     @Mock protected MetricsLogger mMetricsLogger;
@@ -242,7 +240,6 @@
                 mAmbientState,
                 mRecordingController,
                 mFalsingManager,
-                mFalsingCollector,
                 mAccessibilityManager,
                 mLockscreenGestureLogger,
                 mMetricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 6a14a00..bf2d6a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -20,20 +20,28 @@
 import android.view.Display
 import android.view.WindowManager
 import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IStatusBarService
+import com.android.keyguard.TestScopeProvider
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.assist.AssistManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
 import org.junit.Before
 import org.junit.Test
@@ -47,6 +55,8 @@
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class ShadeControllerImplTest : SysuiTestCase() {
+    private val executor = FakeExecutor(FakeSystemClock())
+
     @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var statusBarStateController: StatusBarStateController
@@ -61,6 +71,17 @@
     @Mock private lateinit var nswvc: NotificationShadeWindowViewController
     @Mock private lateinit var display: Display
     @Mock private lateinit var touchLog: LogBuffer
+    @Mock private lateinit var iStatusBarService: IStatusBarService
+    @Mock private lateinit var headsUpManager: HeadsUpManager
+
+    private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
+        WindowRootViewVisibilityInteractor(
+            TestScopeProvider.getTestScope(),
+            WindowRootViewVisibilityRepository(iStatusBarService, executor),
+            FakeKeyguardRepository(),
+            headsUpManager,
+        )
+    }
 
     private lateinit var shadeController: ShadeControllerImpl
 
@@ -74,6 +95,7 @@
                 commandQueue,
                 FakeExecutor(FakeSystemClock()),
                 touchLog,
+                windowRootViewVisibilityInteractor,
                 keyguardStateController,
                 statusBarStateController,
                 statusBarKeyguardViewManager,
@@ -86,6 +108,7 @@
                 Lazy { gutsManager },
             )
         shadeController.setNotificationShadeWindowViewController(nswvc)
+        shadeController.setVisibilityListener(mock())
     }
 
     @Test
@@ -133,4 +156,24 @@
         // VERIFY that cancelCurrentTouch is NOT called
         verify(nswvc, never()).cancelCurrentTouch()
     }
+
+    @Test
+    fun visible_changesToTrue_windowInteractorUpdated() {
+        shadeController.makeExpandedVisible(true)
+
+        assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isTrue()
+    }
+
+    @Test
+    fun visible_changesToFalse_windowInteractorUpdated() {
+        // GIVEN the shade is currently expanded
+        shadeController.makeExpandedVisible(true)
+        assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isTrue()
+
+        // WHEN the shade is collapsed
+        shadeController.collapseShade()
+
+        // THEN the interactor is notified
+        assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isFalse()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
index 5fa6b3a..e7056c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
@@ -58,6 +58,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel;
 import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.kotlin.FlowProviderKt;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 import com.android.systemui.utils.os.FakeHandler;
 
@@ -72,6 +73,8 @@
 import java.util.Arrays;
 import java.util.List;
 
+import kotlinx.coroutines.flow.MutableStateFlow;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
@@ -105,7 +108,8 @@
     private ShadeCarrier mShadeCarrier3;
     private TestableLooper mTestableLooper;
     @Mock
-    private ShadeCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
+    private ShadeCarrierGroupController.OnSingleCarrierChangedListener
+            mOnSingleCarrierChangedListener;
     @Mock
     private MobileUiAdapter mMobileUiAdapter;
     @Mock
@@ -119,6 +123,9 @@
     @Mock
     private StatusBarPipelineFlags mStatusBarPipelineFlags;
 
+    private final MutableStateFlow<Boolean> mIsVisibleFlow =
+            FlowProviderKt.getMutableStateFlow(true);
+
     private FakeSlotIndexResolver mSlotIndexResolver;
     private ClickListenerTextView mNoCarrierTextView;
 
@@ -170,7 +177,7 @@
                 mMobileUiAdapter,
                 mMobileContextProvider,
                 mStatusBarPipelineFlags
-            )
+        )
                 .setShadeCarrierGroup(mShadeCarrierGroup)
                 .build();
 
@@ -181,6 +188,7 @@
         when(mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()).thenReturn(true);
         when(mMobileContextProvider.getMobileContextForSub(anyInt(), any())).thenReturn(mContext);
         when(mMobileIconsViewModel.getLogger()).thenReturn(mMobileViewLogger);
+        when(mShadeCarrierGroupMobileIconViewModel.isVisible()).thenReturn(mIsVisibleFlow);
         when(mMobileIconsViewModel.viewModelForSub(anyInt(), any()))
                 .thenReturn(mShadeCarrierGroupMobileIconViewModel);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 6be2fa5..4fcccf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -93,9 +93,6 @@
                 fakeFeatureFlags
             )
 
-        // ensure that isTooEarly() check in SystemStatusAnimationScheduler does not return true
-        systemClock.advanceTime(Process.getStartUptimeMillis() + MIN_UPTIME)
-
         // StatusBarContentInsetProvider is mocked. Ensure that it returns some mocked values.
         whenever(statusBarContentInsetProvider.getStatusBarContentInsetsForCurrentRotation())
             .thenReturn(android.util.Pair(10, 10))
@@ -156,6 +153,21 @@
         assertEquals(0f, batteryChip.view.alpha)
     }
 
+    /** Regression test for b/294104969. */
+    @Test
+    fun testPrivacyStatusEvent_beforeSystemUptime_stillDisplayed() = runTest {
+        initializeSystemStatusAnimationScheduler(testScope = this, advancePastMinUptime = false)
+
+        // WHEN the uptime hasn't quite passed the minimum required uptime...
+        systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME / 2)
+
+        // BUT the event is a privacy event
+        createAndScheduleFakePrivacyEvent()
+
+        // THEN the privacy event still happens
+        assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+    }
+
     @Test
     fun testPrivacyStatusEvent_standardAnimationLifecycle() = runTest {
         // Instantiate class under test with TestScope from runTest
@@ -568,7 +580,10 @@
         return batteryChip
     }
 
-    private fun initializeSystemStatusAnimationScheduler(testScope: TestScope) {
+    private fun initializeSystemStatusAnimationScheduler(
+        testScope: TestScope,
+        advancePastMinUptime: Boolean = true,
+    ) {
         systemStatusAnimationScheduler =
             SystemStatusAnimationSchedulerImpl(
                 systemEventCoordinator,
@@ -581,5 +596,10 @@
             )
         // add a mock listener
         systemStatusAnimationScheduler.addCallback(listener)
+
+        if (advancePastMinUptime) {
+            // ensure that isTooEarly() check in SystemStatusAnimationScheduler does not return true
+            systemClock.advanceTime(Process.getStartUptimeMillis() + MIN_UPTIME)
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 7117c23..bbf0151 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -40,8 +40,14 @@
 import com.android.internal.logging.InstanceId;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.keyguard.shared.model.WakeSleepReason;
+import com.android.systemui.keyguard.shared.model.WakefulnessModel;
+import com.android.systemui.keyguard.shared.model.WakefulnessState;
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
@@ -54,7 +60,9 @@
 import com.android.systemui.statusbar.notification.logging.nano.Notifications;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import com.google.android.collect.Lists;
@@ -71,6 +79,8 @@
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Executor;
 
+import kotlinx.coroutines.test.TestScope;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -89,7 +99,7 @@
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private NotificationListener mListener;
-    @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
+    @Mock private HeadsUpManager mHeadsUpManager;
 
     private NotificationEntry mEntry;
     private TestableNotificationLogger mLogger;
@@ -97,12 +107,23 @@
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private NotificationPanelLoggerFake mNotificationPanelLoggerFake =
             new NotificationPanelLoggerFake();
+    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
+    private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
+    private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifEntries);
 
+        mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor(
+                mTestScope.getBackgroundScope(),
+                new WindowRootViewVisibilityRepository(mBarService, mUiBgExecutor),
+                mKeyguardRepository,
+                mHeadsUpManager);
+        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+
         mEntry = new NotificationEntryBuilder()
                 .setPkg(TEST_PACKAGE_NAME)
                 .setOpPkg(TEST_PACKAGE_NAME)
@@ -120,10 +141,12 @@
                 mVisibilityProvider,
                 mNotifPipeline,
                 mock(StatusBarStateControllerImpl.class),
-                mShadeExpansionStateManager,
+                mWindowRootViewVisibilityInteractor,
+                mJavaAdapter,
                 mBarService,
                 mExpansionStateLogger
         );
+        mLogger.start();
         mLogger.setUpWithContainer(mListContainer);
         verify(mNotifPipeline).addCollectionListener(any());
     }
@@ -183,31 +206,26 @@
         Mockito.reset(mBarService);
 
         setStateAsleep();
-        mLogger.onDozingChanged(false);  // Wake to lockscreen
-        mLogger.onDozingChanged(true);  // And go back to sleep, turning off logging
+
+        setStateAwake();  // Wake to lockscreen
+
+        setStateAsleep();  // And go back to sleep, turning off logging
         mUiBgExecutor.runAllReady();
+
         // The visibility objects are recycled by NotificationLogger, so we can't use specific
         // matchers here.
         verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any());
     }
 
-    private void setStateAsleep() {
-        mLogger.onShadeExpansionFullyChanged(true);
-        mLogger.onDozingChanged(true);
-        mLogger.onStateChanged(StatusBarState.KEYGUARD);
-    }
-
-    private void setStateAwake() {
-        mLogger.onShadeExpansionFullyChanged(false);
-        mLogger.onDozingChanged(false);
-        mLogger.onStateChanged(StatusBarState.SHADE);
-    }
-
     @Test
-    public void testLogPanelShownOnWake() {
+    public void testLogPanelShownOnWakeToLockscreen() {
         when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
         setStateAsleep();
-        mLogger.onDozingChanged(false);  // Wake to lockscreen
+
+        // Wake to lockscreen
+        mLogger.onStateChanged(StatusBarState.KEYGUARD);
+        setStateAwake();
+
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
         assertTrue(mNotificationPanelLoggerFake.get(0).isLockscreen);
         assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
@@ -222,9 +240,14 @@
     @Test
     public void testLogPanelShownOnShadePull() {
         when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
+        // Start as awake, but with the panel not visible
         setStateAwake();
+        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
+
         // Now expand panel
-        mLogger.onShadeExpansionFullyChanged(true);
+        mLogger.onStateChanged(StatusBarState.SHADE);
+        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
         assertFalse(mNotificationPanelLoggerFake.get(0).isLockscreen);
         assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
@@ -251,13 +274,34 @@
 
         when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(entry));
         setStateAsleep();
-        mLogger.onDozingChanged(false);  // Wake to lockscreen
+
+        // Wake to lockscreen
+        setStateAwake();
+
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
         assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
         Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0];
         assertEquals(0, n.instanceId);
     }
 
+    private void setStateAsleep() {
+        mKeyguardRepository.setWakefulnessModel(
+                new WakefulnessModel(
+                        WakefulnessState.ASLEEP,
+                        WakeSleepReason.OTHER,
+                        WakeSleepReason.OTHER));
+        mTestScope.getTestScheduler().runCurrent();
+    }
+
+    private void setStateAwake() {
+        mKeyguardRepository.setWakefulnessModel(
+                new WakefulnessModel(
+                        WakefulnessState.AWAKE,
+                        WakeSleepReason.OTHER,
+                        WakeSleepReason.OTHER));
+        mTestScope.getTestScheduler().runCurrent();
+    }
+
     private class TestableNotificationLogger extends NotificationLogger {
 
         TestableNotificationLogger(NotificationListener notificationListener,
@@ -266,7 +310,8 @@
                 NotificationVisibilityProvider visibilityProvider,
                 NotifPipeline notifPipeline,
                 StatusBarStateControllerImpl statusBarStateController,
-                ShadeExpansionStateManager shadeExpansionStateManager,
+                WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
+                JavaAdapter javaAdapter,
                 IStatusBarService barService,
                 ExpansionStateLogger expansionStateLogger) {
             super(
@@ -276,7 +321,8 @@
                     visibilityProvider,
                     notifPipeline,
                     statusBarStateController,
-                    shadeExpansionStateManager,
+                    windowRootViewVisibilityInteractor,
+                    javaAdapter,
                     expansionStateLogger,
                     mNotificationPanelLoggerFake
             );
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 0cc0b98..7c8199e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -27,7 +27,6 @@
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.statusbar.IStatusBarService
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.plugins.FalsingManager
@@ -57,6 +56,7 @@
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.time.SystemClock
 import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
 import junit.framework.Assert
 import org.junit.After
 import org.junit.Before
@@ -68,7 +68,6 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
-import java.util.Optional
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -100,7 +99,6 @@
     private val gutsManager: NotificationGutsManager = mock()
     private val onUserInteractionCallback: OnUserInteractionCallback = mock()
     private val falsingManager: FalsingManager = mock()
-    private val falsingCollector: FalsingCollector = mock()
     private val featureFlags: FeatureFlags = mock()
     private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock()
     private val bubblesManager: BubblesManager = mock()
@@ -140,7 +138,6 @@
                 /*allowLongPress=*/ false,
                 onUserInteractionCallback,
                 falsingManager,
-                falsingCollector,
                 featureFlags,
                 peopleNotificationIdentifier,
                 Optional.of(bubblesManager),
@@ -226,20 +223,20 @@
     fun registerSettingsListener_forBubbles() {
         controller.init(mock(NotificationEntry::class.java))
         val viewStateObserver = withArgCaptor {
-            verify(view).addOnAttachStateChangeListener(capture());
+            verify(view).addOnAttachStateChangeListener(capture())
         }
-        viewStateObserver.onViewAttachedToWindow(view);
-        verify(settingsController).addCallback(any(), any());
+        viewStateObserver.onViewAttachedToWindow(view)
+        verify(settingsController).addCallback(any(), any())
     }
 
     @Test
     fun unregisterSettingsListener_forBubbles() {
         controller.init(mock(NotificationEntry::class.java))
         val viewStateObserver = withArgCaptor {
-            verify(view).addOnAttachStateChangeListener(capture());
+            verify(view).addOnAttachStateChangeListener(capture())
         }
-        viewStateObserver.onViewDetachedFromWindow(view);
-        verify(settingsController).removeCallback(any(), any());
+        viewStateObserver.onViewDetachedFromWindow(view)
+        verify(settingsController).removeCallback(any(), any())
     }
 
     @Test
@@ -263,11 +260,17 @@
         whenever(view.privateLayout).thenReturn(childView)
 
         controller.mSettingsListener.onSettingChanged(
-                BUBBLES_SETTING_URI, view.entry.sbn.userId, "1")
+            BUBBLES_SETTING_URI,
+            view.entry.sbn.userId,
+            "1"
+        )
         verify(childView).setBubblesEnabledForUser(true)
 
         controller.mSettingsListener.onSettingChanged(
-                BUBBLES_SETTING_URI, view.entry.sbn.userId, "9")
+            BUBBLES_SETTING_URI,
+            view.entry.sbn.userId,
+            "9"
+        )
         verify(childView).setBubblesEnabledForUser(false)
     }
 
@@ -277,13 +280,12 @@
         whenever(view.privateLayout).thenReturn(childView)
 
         val notification = Notification.Builder(mContext).build()
-        val sbn = SbnBuilder().setNotification(notification)
-                .setUser(UserHandle.of(USER_ALL))
-                .build()
-        whenever(view.entry).thenReturn(NotificationEntryBuilder()
-                .setSbn(sbn)
-                .setUser(UserHandle.of(USER_ALL))
-                .build())
+        val sbn =
+            SbnBuilder().setNotification(notification).setUser(UserHandle.of(USER_ALL)).build()
+        whenever(view.entry)
+            .thenReturn(
+                NotificationEntryBuilder().setSbn(sbn).setUser(UserHandle.of(USER_ALL)).build()
+            )
 
         controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 9, "1")
         verify(childView).setBubblesEnabledForUser(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 705d52b..9e0f83c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -66,11 +67,16 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -84,6 +90,9 @@
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.wmshell.BubblesManager;
 
 import org.junit.Before;
@@ -97,6 +106,8 @@
 
 import java.util.Optional;
 
+import kotlinx.coroutines.test.TestScope;
+
 /**
  * Tests for {@link NotificationGutsManager}.
  */
@@ -108,6 +119,10 @@
 
     private NotificationChannel mTestNotificationChannel = new NotificationChannel(
             TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+
+    private TestScope mTestScope = TestScopeProvider.getTestScope();
+    private JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
     private TestableLooper mTestableLooper;
     private Handler mHandler;
     private NotificationTestHelper mHelper;
@@ -124,6 +139,7 @@
     @Mock private AccessibilityManager mAccessibilityManager;
     @Mock private HighPriorityProvider mHighPriorityProvider;
     @Mock private INotificationManager mINotificationManager;
+    @Mock private IStatusBarService mBarService;
     @Mock private LauncherApps mLauncherApps;
     @Mock private ShortcutManager mShortcutManager;
     @Mock private ChannelEditorDialogController mChannelEditorDialogController;
@@ -140,6 +156,8 @@
 
     @Mock private UserManager mUserManager;
 
+    private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
+
     @Before
     public void setUp() {
         mTestableLooper = TestableLooper.get(this);
@@ -148,21 +166,42 @@
         mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
 
-        mGutsManager = new NotificationGutsManager(mContext, mHandler, mHandler,
+        mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor(
+                mTestScope.getBackgroundScope(),
+                new WindowRootViewVisibilityRepository(mBarService, mExecutor),
+                new FakeKeyguardRepository(),
+                mHeadsUpManagerPhone);
+
+        mGutsManager = new NotificationGutsManager(
+                mContext,
+                mHandler,
+                mHandler,
+                mJavaAdapter,
                 mAccessibilityManager,
-                mHighPriorityProvider, mINotificationManager, mUserManager,
-                mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
-                mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
-                Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
+                mHighPriorityProvider,
+                mINotificationManager,
+                mUserManager,
+                mPeopleSpaceWidgetManager,
+                mLauncherApps,
+                mShortcutManager,
+                mChannelEditorDialogController,
+                mContextTracker,
+                mAssistantFeedbackController,
+                Optional.of(mBubblesManager),
+                new UiEventLoggerFake(),
+                mOnUserInteractionCallback,
                 mShadeController,
+                mWindowRootViewVisibilityInteractor,
                 mNotificationLockscreenUserManager,
                 mStatusBarStateController,
                 mDeviceProvisionedController,
                 mMetricsLogger,
-                mHeadsUpManagerPhone, mActivityStarter);
+                mHeadsUpManagerPhone,
+                mActivityStarter);
         mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
                 mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
+        mGutsManager.start();
     }
 
     ////////////////////////////////////////////////////////////////////////////////////////////////
@@ -210,6 +249,62 @@
     }
 
     @Test
+    public void testLockscreenShadeVisible_visible_gutsNotClosed() {
+        // First, start out lockscreen or shade as not visible
+        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
+        mTestScope.getTestScheduler().runCurrent();
+
+        NotificationGuts guts = mock(NotificationGuts.class);
+        mGutsManager.setExposedGuts(guts);
+
+        // WHEN the lockscreen or shade becomes visible
+        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+        mTestScope.getTestScheduler().runCurrent();
+
+        // THEN the guts are not closed
+        verify(guts, never()).removeCallbacks(any());
+        verify(guts, never()).closeControls(
+                anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void testLockscreenShadeVisible_notVisible_gutsClosed() {
+        // First, start out lockscreen or shade as visible
+        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+        mTestScope.getTestScheduler().runCurrent();
+
+        NotificationGuts guts = mock(NotificationGuts.class);
+        mGutsManager.setExposedGuts(guts);
+
+        // WHEN the lockscreen or shade is no longer visible
+        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
+        mTestScope.getTestScheduler().runCurrent();
+
+        // THEN the guts are closed
+        verify(guts).removeCallbacks(any());
+        verify(guts).closeControls(
+                /* leavebehinds= */ eq(true),
+                /* controls= */ eq(true),
+                /* x= */ anyInt(),
+                /* y= */ anyInt(),
+                /* force= */ eq(true));
+    }
+
+    @Test
+    public void testLockscreenShadeVisible_notVisible_listContainerReset() {
+        // First, start out lockscreen or shade as visible
+        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+        mTestScope.getTestScheduler().runCurrent();
+
+        // WHEN the lockscreen or shade is no longer visible
+        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
+        mTestScope.getTestScheduler().runCurrent();
+
+        // THEN the list container is reset
+        verify(mNotificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean());
+    }
+
+    @Test
     public void testChangeDensityOrFontScale() {
         NotificationGuts guts = spy(new NotificationGuts(mContext));
         when(guts.post(any())).thenAnswer(invocation -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 0425830..9dfcb3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -54,7 +54,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.TestableDependency;
-import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
@@ -600,7 +599,6 @@
                 mock(OnExpandClickListener.class),
                 mock(ExpandableNotificationRow.CoordinateOnClickListener.class),
                 new FalsingManagerFake(),
-                new FalsingCollectorFake(),
                 mStatusBarStateController,
                 mPeopleNotificationIdentifier,
                 mOnUserInteractionCallback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index f47efe3..4e3690f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -24,7 +24,6 @@
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
-import static junit.framework.TestCase.fail;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -62,7 +61,6 @@
 import android.os.IThermalService;
 import android.os.Looper;
 import android.os.PowerManager;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.dreams.IDreamManager;
 import android.support.test.metricshelper.MetricsAsserts;
@@ -73,13 +71,7 @@
 import android.util.SparseArray;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
-import android.view.ViewRootImpl;
 import android.view.WindowManager;
-import android.window.BackEvent;
-import android.window.OnBackAnimationCallback;
-import android.window.OnBackInvokedCallback;
-import android.window.OnBackInvokedDispatcher;
-import android.window.WindowOnBackInvokedDispatcher;
 
 import androidx.test.filters.SmallTest;
 
@@ -125,6 +117,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shade.CameraLauncher;
@@ -158,7 +151,6 @@
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -167,13 +159,10 @@
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
+import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -199,7 +188,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -207,6 +195,8 @@
 import java.io.PrintWriter;
 import java.util.Optional;
 
+import javax.inject.Provider;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
@@ -227,7 +217,6 @@
     @Mock private KeyguardIndicationController mKeyguardIndicationController;
     @Mock private NotificationStackScrollLayout mStackScroller;
     @Mock private NotificationStackScrollLayoutController mStackScrollerController;
-    @Mock private NotificationListContainer mNotificationListContainer;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationPanelViewController mNotificationPanelViewController;
     @Mock private ShadeLogger mShadeLogger;
@@ -254,7 +243,6 @@
     @Mock private StatusBarNotificationPresenter mNotificationPresenter;
     @Mock private NotificationActivityStarter mNotificationActivityStarter;
     @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
-    @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private StatusBarSignalPolicy mStatusBarSignalPolicy;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
@@ -273,6 +261,7 @@
     @Mock private DynamicPrivacyController mDynamicPrivacyController;
     @Mock private AutoHideController mAutoHideController;
     @Mock private StatusBarWindowController mStatusBarWindowController;
+    @Mock private Provider<CollapsedStatusBarFragment> mCollapsedStatusBarFragmentProvider;
     @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
     @Mock private UserSwitcherController mUserSwitcherController;
     @Mock private Bubbles mBubbles;
@@ -291,8 +280,6 @@
     @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
     @Mock private VolumeComponent mVolumeComponent;
     @Mock private CommandQueue mCommandQueue;
-    @Mock private CentralSurfacesComponent.Factory mStatusBarComponentFactory;
-    @Mock private CentralSurfacesComponent mCentralSurfacesComponent;
     @Mock private CentralSurfacesCommandQueueCallbacks mCentralSurfacesCommandQueueCallbacks;
     @Mock private PluginManager mPluginManager;
     @Mock private ViewMediatorCallback mViewMediatorCallback;
@@ -325,18 +312,11 @@
     @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
     @Mock private CameraLauncher mCameraLauncher;
     @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
-    /**
-     * The process of registering/unregistering a predictive back callback requires a
-     * ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test.
-     * To prevent an NPE during test execution, we explicitly craft and provide a fake ViewRootImpl.
-     */
-    @Mock private ViewRootImpl mViewRootImpl;
-    @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
     @Mock private UserTracker mUserTracker;
     @Mock private FingerprintManager mFingerprintManager;
-    @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
     @Mock IPowerManager mPowerManagerService;
     @Mock ActivityStarter mActivityStarter;
+    @Mock private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
 
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -388,18 +368,6 @@
         mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
 
         mMetricsLogger = new FakeMetricsLogger();
-        NotificationLogger notificationLogger = new NotificationLogger(
-                mNotificationListener,
-                mUiBgExecutor,
-                mNotifLiveDataStore,
-                mVisibilityProvider,
-                mock(NotifPipeline.class),
-                mStatusBarStateController,
-                mShadeExpansionStateManager,
-                mExpansionStateLogger,
-                new NotificationPanelLoggerFake()
-        );
-        notificationLogger.setVisibilityReporter(mock(Runnable.class));
 
         when(mCommandQueue.asBinder()).thenReturn(new Binder());
 
@@ -438,7 +406,6 @@
         when(mNotificationShadeWindowViewControllerLazy.get())
                 .thenReturn(mNotificationShadeWindowViewController);
 
-        when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent);
         doAnswer(invocation -> {
             ((Runnable) invocation.getArgument(0)).run();
             return null;
@@ -448,6 +415,7 @@
                 mCommandQueue,
                 mMainExecutor,
                 mock(LogBuffer.class),
+                mock(WindowRootViewVisibilityInteractor.class),
                 mKeyguardStateController,
                 mStatusBarStateController,
                 mStatusBarKeyguardViewManager,
@@ -475,7 +443,10 @@
                 mock(FragmentService.class),
                 mLightBarController,
                 mAutoHideController,
-                new StatusBarInitializer(mStatusBarWindowController, emptySet()),
+                new StatusBarInitializer(
+                        mStatusBarWindowController,
+                        mCollapsedStatusBarFragmentProvider,
+                        emptySet()),
                 mStatusBarWindowController,
                 mStatusBarWindowStateController,
                 mKeyguardUpdateMonitor,
@@ -490,7 +461,6 @@
                 new FalsingCollectorFake(),
                 mBroadcastDispatcher,
                 mNotificationGutsManager,
-                notificationLogger,
                 mNotificationInterruptStateProvider,
                 new ShadeExpansionStateManager(),
                 mKeyguardViewMediator,
@@ -537,10 +507,10 @@
                 mDozeScrimController,
                 mVolumeComponent,
                 mCommandQueue,
-                mStatusBarComponentFactory,
                 () -> mCentralSurfacesCommandQueueCallbacks,
                 mPluginManager,
                 mShadeController,
+                mWindowRootViewVisibilityInteractor,
                 mStatusBarKeyguardViewManager,
                 mViewMediatorCallback,
                 mInitController,
@@ -578,16 +548,9 @@
                 mUserTracker,
                 () -> mFingerprintManager,
                 mActivityStarter
-        ) {
-            @Override
-            protected ViewRootImpl getViewRootImpl() {
-                return mViewRootImpl;
-            }
-        };
+        );
         mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
         mCentralSurfaces.initShadeVisibilityListener();
-        when(mViewRootImpl.getOnBackInvokedDispatcher())
-                .thenReturn(mOnBackInvokedDispatcher);
         when(mKeyguardViewMediator.registerCentralSurfaces(
                 any(CentralSurfacesImpl.class),
                 any(NotificationPanelViewController.class),
@@ -609,7 +572,6 @@
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "sysui:GestureWakeLock");
         mCentralSurfaces.startKeyguard();
         mInitController.executePostInitTasks();
-        notificationLogger.setUpWithContainer(mNotificationListContainer);
         mCentralSurfaces.registerCallbacks();
     }
 
@@ -799,151 +761,6 @@
     }
 
     @Test
-    public void testLogHidden() {
-        try {
-            mCentralSurfaces.handleVisibleToUserChanged(false);
-            mUiBgExecutor.runAllReady();
-            verify(mBarService, times(1)).onPanelHidden();
-            verify(mBarService, never()).onPanelRevealed(anyBoolean(), anyInt());
-        } catch (RemoteException e) {
-            fail();
-        }
-    }
-
-    /**
-     * Do the following:
-     * 1. verify that a predictive back callback is registered when CSurf becomes visible
-     * 2. verify that the same callback is unregistered when CSurf becomes invisible
-     */
-    @Test
-    public void testPredictiveBackCallback_registration() {
-        mCentralSurfaces.handleVisibleToUserChanged(true);
-        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
-                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
-                mOnBackInvokedCallback.capture());
-
-        mCentralSurfaces.handleVisibleToUserChanged(false);
-        verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
-                eq(mOnBackInvokedCallback.getValue()));
-    }
-
-    /**
-     * Do the following:
-     * 1. capture the predictive back callback during registration
-     * 2. call the callback directly
-     * 3. verify that the ShadeController's panel collapse animation is invoked
-     */
-    @Test
-    public void testPredictiveBackCallback_invocationCollapsesPanel() {
-        mCentralSurfaces.handleVisibleToUserChanged(true);
-        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
-                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
-                mOnBackInvokedCallback.capture());
-
-        when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
-        mOnBackInvokedCallback.getValue().onBackInvoked();
-        verify(mBackActionInteractor).onBackRequested();
-    }
-
-    /**
-     * When back progress is at 100%, the onBackProgressed animation driver inside
-     * NotificationPanelViewController should be invoked appropriately (with 1.0f passed in).
-     */
-    @Test
-    public void testPredictiveBackAnimation_progressMaxScalesPanel() {
-        mCentralSurfaces.handleVisibleToUserChanged(true);
-        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
-                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
-                mOnBackInvokedCallback.capture());
-
-        OnBackAnimationCallback onBackAnimationCallback =
-                (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
-        when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
-        when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
-
-        BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 100.0f, 1.0f, BackEvent.EDGE_LEFT);
-        onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
-        verify(mNotificationPanelViewController).onBackProgressed(eq(1.0f));
-    }
-
-    /**
-     * When back progress is at 0%, the onBackProgressed animation driver inside
-     * NotificationPanelViewController should be invoked appropriately (with 0.0f passed in).
-     */
-    @Test
-    public void testPredictiveBackAnimation_progressMinScalesPanel() {
-        mCentralSurfaces.handleVisibleToUserChanged(true);
-        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
-                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
-                mOnBackInvokedCallback.capture());
-
-        OnBackAnimationCallback onBackAnimationCallback =
-                (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
-        when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
-        when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
-
-        BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 10.0f, 0.0f, BackEvent.EDGE_LEFT);
-        onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
-        verify(mNotificationPanelViewController).onBackProgressed(eq(0.0f));
-    }
-
-    @Test
-    public void testPanelOpenForHeadsUp() {
-        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
-        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
-        when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5);
-        when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(true);
-        mCentralSurfaces.setBarStateForTest(SHADE);
-
-        try {
-            mCentralSurfaces.handleVisibleToUserChanged(true);
-            mUiBgExecutor.runAllReady();
-            verify(mBarService, never()).onPanelHidden();
-            verify(mBarService, times(1)).onPanelRevealed(false, 1);
-        } catch (RemoteException e) {
-            fail();
-        }
-        mMainExecutor.runAllReady();
-    }
-
-    @Test
-    public void testPanelOpenAndClear() {
-        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
-        when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5);
-
-        when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false);
-        mCentralSurfaces.setBarStateForTest(SHADE);
-
-        try {
-            mCentralSurfaces.handleVisibleToUserChanged(true);
-            mUiBgExecutor.runAllReady();
-            verify(mBarService, never()).onPanelHidden();
-            verify(mBarService, times(1)).onPanelRevealed(true, 5);
-        } catch (RemoteException e) {
-            fail();
-        }
-        mMainExecutor.runAllReady();
-    }
-
-    @Test
-    public void testPanelOpenAndNoClear() {
-        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
-        when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5);
-        when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false);
-        mCentralSurfaces.setBarStateForTest(StatusBarState.KEYGUARD);
-
-        try {
-            mCentralSurfaces.handleVisibleToUserChanged(true);
-            mUiBgExecutor.runAllReady();
-            verify(mBarService, never()).onPanelHidden();
-            verify(mBarService, times(1)).onPanelRevealed(false, 5);
-        } catch (RemoteException e) {
-            fail();
-        }
-        mMainExecutor.runAllReady();
-    }
-
-    @Test
     public void testDump_DoesNotCrash() {
         mCentralSurfaces.dump(new PrintWriter(new ByteArrayOutputStream()), null);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
index 08e89fb..6f04f36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
@@ -220,30 +220,6 @@
 
     /** Regression test for b/255428281. */
     @Test
-    fun internalAndExternalIconWithSameName_internalRemoved_viaRemoveAll_externalStays() {
-        val slotName = "mute"
-
-        // Internal
-        underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
-
-        // External
-        underTest.setIconFromTile(slotName, createExternalIcon())
-
-        // WHEN the internal icon is removed via #removeAllIconsForSlot
-        underTest.removeAllIconsForSlot(slotName)
-
-        // THEN the internal icon is removed but the external icon remains
-        assertThat(iconList.slots).hasSize(2)
-        assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
-        assertThat(iconList.slots[1].name).isEqualTo(slotName)
-        assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
-        assertThat(iconList.slots[1].hasIconsInSlot()).isFalse() // Indicates removal
-
-        verify(iconGroup).onRemoveIcon(1)
-    }
-
-    /** Regression test for b/255428281. */
-    @Test
     fun internalAndExternalIconWithSameName_internalUpdatedIndependently() {
         val slotName = "mute"
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 085ec27..0ff6f20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -120,34 +120,6 @@
         verify(manager, never()).onRemoveIcon(anyInt());
     }
 
-    @Test
-    public void testRemoveAllIconsForSlot_ignoredForNewPipeline() {
-        IconManager manager = mock(IconManager.class);
-
-        // GIVEN the new pipeline is on
-        StatusBarPipelineFlags flags = mock(StatusBarPipelineFlags.class);
-        when(flags.isIconControlledByFlags("test_icon")).thenReturn(true);
-
-        StatusBarIconController iconController = new StatusBarIconControllerImpl(
-                mContext,
-                mock(CommandQueue.class),
-                mock(DemoModeController.class),
-                mock(ConfigurationController.class),
-                mock(TunerService.class),
-                mock(DumpManager.class),
-                mock(StatusBarIconList.class),
-                flags
-        );
-
-        iconController.addIconGroup(manager);
-
-        // WHEN a request to remove a new icon is sent
-        iconController.removeAllIconsForSlot("test_icon");
-
-        // THEN it is not removed for those icons
-        verify(manager, never()).onRemoveIcon(anyInt());
-    }
-
     private <T extends IconManager & TestableIconManager> void testCallOnAdd_forManager(T manager) {
         StatusBarIconHolder holder = holderForType(TYPE_ICON);
         manager.onIconAdded(0, "test_slot", false, holder);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
index 8150a31..6624ec2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.statusbar.connectivity.WifiIcons
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
@@ -41,7 +42,6 @@
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.util.CarrierConfigTracker
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -144,17 +144,112 @@
             wifiRepository.setIsWifiDefault(true)
             wifiRepository.setWifiNetwork(networkModel)
 
-            // Type is [Visible] since that is the only model that stores a resId
-            val expectedIcon: WifiIcon.Visible =
-                WifiIcon.fromModel(networkModel, context) as WifiIcon.Visible
-
             assertThat(latest?.secondaryTitle).isEqualTo("test ssid")
             assertThat(latest?.secondaryLabel).isNull()
-            assertThat(latest?.icon).isEqualTo(ResourceIcon.get(expectedIcon.icon.res))
+            assertThat(latest?.icon)
+                .isEqualTo(ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4]))
             assertThat(latest?.iconId).isNull()
         }
 
     @Test
+    fun wifiDefaultAndActive_hotspotNone() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.tileModel)
+
+            val networkModel =
+                WifiNetworkModel.Active(
+                    networkId = 1,
+                    level = 4,
+                    ssid = "test ssid",
+                    hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
+                )
+
+            connectivityRepository.setWifiConnected()
+            wifiRepository.setIsWifiDefault(true)
+            wifiRepository.setWifiNetwork(networkModel)
+
+            assertThat(latest?.icon)
+                .isEqualTo(ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4]))
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotTablet() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.tileModel)
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.TABLET)
+
+            assertThat(latest?.icon)
+                .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_tablet))
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotLaptop() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.tileModel)
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.LAPTOP)
+
+            assertThat(latest?.icon)
+                .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_laptop))
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotWatch() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.tileModel)
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.WATCH)
+
+            assertThat(latest?.icon)
+                .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_watch))
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotAuto() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.tileModel)
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.AUTO)
+
+            assertThat(latest?.icon)
+                .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_auto))
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotPhone() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.tileModel)
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.PHONE)
+
+            assertThat(latest?.icon)
+                .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotUnknown() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.tileModel)
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
+
+            assertThat(latest?.icon)
+                .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+        }
+
+    @Test
+    fun wifiDefaultAndActive_hotspotInvalid() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.tileModel)
+
+            setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.INVALID)
+
+            assertThat(latest?.icon)
+                .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+        }
+
+    @Test
     fun wifiDefaultAndNotActive_noNetworksAvailable() =
         testScope.runTest {
             val latest by collectLastValue(underTest.tileModel)
@@ -237,6 +332,20 @@
             assertThat(latest?.icon).isNull()
         }
 
+    private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
+        val networkModel =
+            WifiNetworkModel.Active(
+                networkId = 1,
+                level = 4,
+                ssid = "test ssid",
+                hotspotDeviceType = hotspot,
+            )
+
+        connectivityRepository.setWifiConnected()
+        wifiRepository.setIsWifiDefault(true)
+        wifiRepository.setWifiNetwork(networkModel)
+    }
+
     companion object {
         const val SUB_1_ID = 1
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 5aacc66..a520f6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.connectivity.WifiIcons
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -117,6 +118,27 @@
         }
 
     @Test
+    fun wifiIcon_validHotspot_hotspotIconNotShown() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiIcon)
+
+            // Even WHEN the network has a valid hotspot type
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.Active(
+                    NETWORK_ID,
+                    isValidated = true,
+                    level = 1,
+                    hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP,
+                )
+            )
+
+            // THEN the hotspot icon is not used for the status bar icon, and the typical wifi icon
+            // is used instead
+            assertThat(latest).isInstanceOf(WifiIcon.Visible::class.java)
+            assertThat((latest as WifiIcon.Visible).res).isEqualTo(WifiIcons.WIFI_FULL_ICONS[1])
+        }
+
+    @Test
     fun activity_showActivityConfigFalse_outputsFalse() =
         testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt
new file mode 100644
index 0000000..274acc9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.util.kotlin
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Wrapper for flow constructors that can be retrieved from java tests */
+fun <T> getMutableStateFlow(value: T): MutableStateFlow<T> = MutableStateFlow(value)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index ac9cfb3..1b623a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -420,6 +420,7 @@
                 syncExecutor,
                 mock(Handler.class),
                 mTaskViewTransitions,
+                mTransitions,
                 mock(SyncTransactionQueue.class),
                 mock(IWindowManager.class),
                 mBubbleProperties);
@@ -511,6 +512,11 @@
     }
 
     @Test
+    public void instantiateController_registerTransitionObserver() {
+        verify(mTransitions).registerObserver(any());
+    }
+
+    @Test
     public void testAddBubble() {
         mBubbleController.updateBubble(mBubbleEntry);
         assertTrue(mBubbleController.hasBubbles());
@@ -1470,6 +1476,34 @@
     }
 
     @Test
+    public void testBroadcastReceiverCloseDialogs_reasonHomeKey() {
+        spyOn(mContext);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleData.setExpanded(true);
+
+        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+        Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        i.putExtra("reason", "homekey");
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+        assertStackCollapsed();
+    }
+
+    @Test
+    public void testBroadcastReceiverCloseDialogs_reasonRecentsKey() {
+        spyOn(mContext);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleData.setExpanded(true);
+
+        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+        Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        i.putExtra("reason", "recentapps");
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+        assertStackCollapsed();
+    }
+
+    @Test
     public void testBroadcastReceiver_screenOff() {
         spyOn(mContext);
         mBubbleController.updateBubble(mBubbleEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 5855347..9ad234e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -43,6 +43,7 @@
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
 
 import java.util.Optional;
 
@@ -74,6 +75,7 @@
             ShellExecutor shellMainExecutor,
             Handler shellMainHandler,
             TaskViewTransitions taskViewTransitions,
+            Transitions transitions,
             SyncTransactionQueue syncQueue,
             IWindowManager wmService,
             BubbleProperties bubbleProperties) {
@@ -82,7 +84,8 @@
                 windowManagerShellWrapper, userManager, launcherApps, bubbleLogger,
                 taskStackListener, shellTaskOrganizer, positioner, displayController,
                 oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
-                new SyncExecutor(), taskViewTransitions, syncQueue, wmService, bubbleProperties);
+                new SyncExecutor(), taskViewTransitions, transitions,
+                syncQueue, wmService, bubbleProperties);
         setInflateSynchronously(true);
         onInit();
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index d54ef73..6f51d1b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -38,12 +38,12 @@
     /** Emits [value] as [displays] flow value. */
     suspend fun emit(value: Set<Display>) = flow.emit(value)
 
-    /** Emits [value] as [pendingDisplay] flow value. */
+    /** Emits [value] as [pendingDisplayId] flow value. */
     suspend fun emit(value: Int?) = pendingDisplayFlow.emit(value)
 
     override val displays: Flow<Set<Display>>
         get() = flow
 
-    override val pendingDisplay: Flow<Int?>
+    override val pendingDisplayId: Flow<Int?>
         get() = pendingDisplayFlow
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index faebcaa..cc0c943 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -81,7 +81,7 @@
     override val linearDozeAmount: Flow<Float> = _dozeAmount
 
     private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
-    override val statusBarState: Flow<StatusBarState> = _statusBarState
+    override val statusBarState: StateFlow<StatusBarState> = _statusBarState
 
     private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel())
     override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 4f33a97..f7db44e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -27,6 +27,9 @@
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -98,6 +101,9 @@
 
     private val context = test.context
 
+    private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() }
+    private var falsingInteractor: FalsingInteractor? = null
+
     fun fakeSceneContainerRepository(
         containerConfig: SceneContainerConfig = fakeSceneContainerConfig(),
     ): SceneContainerRepository {
@@ -175,6 +181,7 @@
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
             featureFlags = featureFlags,
+            falsingInteractor = falsingInteractor(),
         )
     }
 
@@ -191,6 +198,14 @@
         )
     }
 
+    fun falsingInteractor(collector: FalsingCollector = falsingCollector()): FalsingInteractor {
+        return falsingInteractor ?: FalsingInteractor(collector).also { falsingInteractor = it }
+    }
+
+    fun falsingCollector(): FalsingCollector {
+        return falsingCollectorFake
+    }
+
     private fun applicationScope(): CoroutineScope {
         return testScope.backgroundScope
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 03e3423..3774d1d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -84,10 +84,6 @@
     }
 
     @Override
-    public void removeAllIconsForSlot(String slot) {
-    }
-
-    @Override
     public void setIconAccessibilityLiveRegion(String slot, int mode) {
     }
 
diff --git a/packages/services/VirtualCamera/OWNERS b/packages/services/VirtualCamera/OWNERS
new file mode 100644
index 0000000..c66443f
--- /dev/null
+++ b/packages/services/VirtualCamera/OWNERS
@@ -0,0 +1,3 @@
+include /services/companion/java/com/android/server/companion/virtual/OWNERS
+caen@google.com
+jsebechlebsky@google.com
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 4688658..423b85f 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -56,7 +56,7 @@
     private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
     private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
 
-    private final FillServiceCallbacks mCallbacks;
+    private FillServiceCallbacks mCallbacks;
     private final Object mLock = new Object();
     private CompletableFuture<FillResponse> mPendingFillRequest;
     private int mPendingFillRequestId = INVALID_REQUEST_ID;
@@ -128,9 +128,12 @@
      */
     public int cancelCurrentRequest() {
         synchronized (mLock) {
-            return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
+            int canceledRequestId = mPendingFillRequest != null && mPendingFillRequest.cancel(false)
                     ? mPendingFillRequestId
                     : INVALID_REQUEST_ID;
+            mPendingFillRequest = null;
+            mPendingFillRequestId = INVALID_REQUEST_ID;
+            return canceledRequestId;
         }
     }
 
@@ -184,6 +187,10 @@
                 mPendingFillRequest = null;
                 mPendingFillRequestId = INVALID_REQUEST_ID;
             }
+            if (mCallbacks == null) {
+                Slog.w(TAG, "Error calling RemoteFillService - service already unbound");
+                return;
+            }
             if (err == null) {
                 mCallbacks.onFillRequestSuccess(request.getId(), res,
                         mComponentName.getPackageName(), request.getFlags());
@@ -220,6 +227,10 @@
             return save;
         }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS)
                 .whenComplete((res, err) -> Handler.getMain().post(() -> {
+                    if (mCallbacks == null) {
+                        Slog.w(TAG, "Error calling RemoteFillService - service already unbound");
+                        return;
+                    }
                     if (err == null) {
                         mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), res);
                     } else {
@@ -234,6 +245,8 @@
     }
 
     public void destroy() {
+        cancelCurrentRequest();
         unbind();
+        mCallbacks = null;
     }
 }
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index b573800..3b02be5 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -1750,12 +1750,8 @@
 
         synchronized (mClearDataLock) {
             mClearingData = true;
-            try {
-                mActivityManager.clearApplicationUserData(packageName, keepSystemState, observer,
-                        mUserId);
-            } catch (RemoteException e) {
-                // can't happen because the activity manager is in this process
-            }
+            mActivityManagerInternal.clearApplicationUserData(packageName, keepSystemState,
+                    /*isRestore=*/ true, observer, mUserId);
 
             // Only wait 30 seconds for the clear data to happen.
             long timeoutMark = System.currentTimeMillis() + CLEAR_DATA_TIMEOUT_INTERVAL;
diff --git a/services/companion/java/com/android/server/companion/virtual/Android.bp b/services/companion/java/com/android/server/companion/virtual/Android.bp
index 6526c78..7a7d376 100644
--- a/services/companion/java/com/android/server/companion/virtual/Android.bp
+++ b/services/companion/java/com/android/server/companion/virtual/Android.bp
@@ -1,6 +1,9 @@
 java_aconfig_library {
     name: "virtualdevice_flags_lib",
     aconfig_declarations: "virtualdevice_flags",
+    static_libs: [
+        "android.companion.virtual.flags-aconfig-java",
+    ],
 }
 
 aconfig_declarations {
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 34033e2..cf6092e 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -17,6 +17,7 @@
 package com.android.server.companion.virtual;
 
 import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
@@ -46,7 +47,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.BlockedAppStreamingActivity;
 
-import java.util.List;
 import java.util.Set;
 
 
@@ -219,28 +219,37 @@
     }
 
     @Override
-    public boolean canContainActivities(@NonNull List<ActivityInfo> activities,
-            @WindowConfiguration.WindowingMode int windowingMode) {
-        if (!isWindowingModeSupported(windowingMode)) {
+    public boolean canActivityBeLaunched(@NonNull ActivityInfo activityInfo,
+            @Nullable Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
+            int launchingFromDisplayId, boolean isNewTask) {
+        if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId, isNewTask)) {
+            mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
             return false;
         }
-        // Can't display all the activities if any of them don't want to be displayed.
-        final int activityCount = activities.size();
-        for (int i = 0; i < activityCount; i++) {
-            final ActivityInfo aInfo = activities.get(i);
-            if (!canContainActivity(aInfo, /* windowFlags= */ 0, /* systemWindowFlags= */ 0)) {
-                mActivityBlockedCallback.onActivityBlocked(mDisplayId, aInfo);
-                return false;
-            }
+        if (mIntentListenerCallback != null && intent != null
+                && mIntentListenerCallback.shouldInterceptIntent(intent)) {
+            Slog.d(TAG, "Virtual device intercepting intent");
+            return false;
         }
         return true;
     }
 
     @Override
-    public boolean canActivityBeLaunched(ActivityInfo activityInfo,
-            Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
-            int launchingFromDisplayId, boolean isNewTask) {
+    public boolean canContainActivity(@NonNull ActivityInfo activityInfo,
+            @WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
+            boolean isNewTask) {
         if (!isWindowingModeSupported(windowingMode)) {
+            Slog.d(TAG, "Virtual device doesn't support windowing mode " + windowingMode);
+            return false;
+        }
+        if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+            Slog.d(TAG, "Virtual device requires android:canDisplayOnRemoteDevices=true");
+            return false;
+        }
+        final UserHandle activityUser =
+                UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid);
+        if (!mAllowedUsers.contains(activityUser)) {
+            Slog.d(TAG, "Virtual device launch disallowed from user " + activityUser);
             return false;
         }
 
@@ -249,40 +258,36 @@
             // The error dialog alerting users that streaming is blocked is always allowed.
             return true;
         }
-
-        if (!canContainActivity(activityInfo, /* windowFlags= */  0, /* systemWindowFlags= */ 0)) {
-            mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
+        if (!activityMatchesDisplayCategory(activityInfo)) {
+            Slog.d(TAG, "The activity's required display category '"
+                    + activityInfo.requiredDisplayCategory
+                    + "' not found on virtual display with the following categories: "
+                    + mDisplayCategories);
             return false;
         }
-
-        if (launchingFromDisplayId == Display.DEFAULT_DISPLAY) {
-            return true;
-        }
-        if (isNewTask && !mBlockedCrossTaskNavigations.isEmpty()
-                && mBlockedCrossTaskNavigations.contains(activityComponent)) {
-            Slog.d(TAG, "Virtual device blocking cross task navigation of " + activityComponent);
-            mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
+        if ((mDefaultActivityPolicy == VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED
+                && mBlockedActivities.contains(activityComponent))
+                || (mDefaultActivityPolicy == VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_BLOCKED
+                && !mAllowedActivities.contains(activityComponent))) {
+            Slog.d(TAG, "Virtual device launch disallowed by policy: " + activityComponent);
             return false;
         }
-        if (isNewTask && !mAllowedCrossTaskNavigations.isEmpty()
-                && !mAllowedCrossTaskNavigations.contains(activityComponent)) {
-            Slog.d(TAG, "Virtual device not allowing cross task navigation of "
-                    + activityComponent);
-            mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
-            return false;
-        }
-
-        if (mIntentListenerCallback != null && intent != null
-                && mIntentListenerCallback.shouldInterceptIntent(intent)) {
-            Slog.d(TAG, "Virtual device has intercepted intent");
-            return false;
+        if (isNewTask && launchingFromDisplayId != DEFAULT_DISPLAY) {
+            if ((!mBlockedCrossTaskNavigations.isEmpty()
+                    && mBlockedCrossTaskNavigations.contains(activityComponent))
+                    || ((!mAllowedCrossTaskNavigations.isEmpty()
+                    && !mAllowedCrossTaskNavigations.contains(activityComponent)))) {
+                Slog.d(TAG, "Virtual device cross task navigation disallowed by policy: "
+                        + activityComponent);
+                return false;
+            }
         }
 
         return true;
     }
 
-
     @Override
+    @SuppressWarnings("AndroidFrameworkRequiresPermission")
     public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
             int systemWindowFlags) {
         // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
@@ -293,10 +298,17 @@
                     activityInfo.applicationInfo.uid));
         }
 
-        if (!canContainActivity(activityInfo, windowFlags, systemWindowFlags)) {
-            mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
-            return false;
+        if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
+                activityInfo.packageName,
+                UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid))) {
+            // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
+            if ((windowFlags & FLAG_SECURE) != 0
+                    || (systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
+                mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
+                return false;
+            }
         }
+
         return true;
     }
 
@@ -368,53 +380,6 @@
 
     }
 
-    private boolean canContainActivity(ActivityInfo activityInfo, int windowFlags,
-            int systemWindowFlags) {
-        if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
-            return false;
-        }
-        ComponentName activityComponent = activityInfo.getComponentName();
-        if (BLOCKED_APP_STREAMING_COMPONENT.equals(activityComponent)) {
-            // The error dialog alerting users that streaming is blocked is always allowed. Need to
-            // run before the clauses below to ensure error dialog always shows up.
-            return true;
-        }
-        if (!activityMatchesDisplayCategory(activityInfo)) {
-            Slog.d(TAG, String.format(
-                    "The activity's required display category: %s is not found on virtual display"
-                            + " with the following categories: %s",
-                    activityInfo.requiredDisplayCategory, mDisplayCategories.toString()));
-            return false;
-        }
-        final UserHandle activityUser =
-                UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid);
-        if (!mAllowedUsers.contains(activityUser)) {
-            Slog.d(TAG, "Virtual device activity not allowed from user " + activityUser);
-            return false;
-        }
-        if (mDefaultActivityPolicy == VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED
-                && mBlockedActivities.contains(activityComponent)) {
-            Slog.d(TAG, "Virtual device blocking launch of " + activityComponent);
-            return false;
-        }
-        if (mDefaultActivityPolicy == VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_BLOCKED
-                && !mAllowedActivities.contains(activityComponent)) {
-            Slog.d(TAG, activityComponent + " is not in the allowed list.");
-            return false;
-        }
-        if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
-                activityInfo.packageName, activityUser)) {
-            // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
-            if ((windowFlags & FLAG_SECURE) != 0) {
-                return false;
-            }
-            if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     @VisibleForTesting
     int getRunningAppsChangedListenersSizeForTesting() {
         synchronized (mGenericWindowPolicyControllerLock) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 7429fbe..a135878 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -34,6 +34,7 @@
 import android.companion.virtual.VirtualDevice;
 import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.content.AttributionSource;
 import android.content.Context;
@@ -323,6 +324,15 @@
                 @NonNull IVirtualDeviceSoundEffectListener soundEffectListener) {
             createVirtualDevice_enforcePermission();
             attributionSource.enforceCallingUid();
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (Flags.moreLogs()) {
+                    Slog.i(TAG, "Creating VirtualDevice");
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+
             final int callingUid = getCallingUid();
             final String packageName = attributionSource.getPackageName();
             if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) {
diff --git a/services/companion/java/com/android/server/companion/virtual/flags.aconfig b/services/companion/java/com/android/server/companion/virtual/flags.aconfig
index 4fe4c87..6297e91 100644
--- a/services/companion/java/com/android/server/companion/virtual/flags.aconfig
+++ b/services/companion/java/com/android/server/companion/virtual/flags.aconfig
@@ -1,3 +1,5 @@
+# OLD PACKAGE, DO NOT USE: Prefer `flags.aconfig` in core/java/android/companion/virtual
+# (or other custom files) to define your flags
 package: "com.android.server.companion.virtual"
 
 flag {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c6b64dec..a787644 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -32,6 +32,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.IInstalld.IFsveritySetupAuthToken;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED;
 import static android.os.storage.OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT;
@@ -678,6 +679,7 @@
     private static final int H_VOLUME_STATE_CHANGED = 15;
     private static final int H_CLOUD_MEDIA_PROVIDER_CHANGED = 16;
     private static final int H_SECURE_KEYGUARD_STATE_CHANGED = 17;
+    private static final int H_REMOUNT_VOLUMES_ON_MOVE = 18;
 
     class StorageManagerServiceHandler extends Handler {
         public StorageManagerServiceHandler(Looper looper) {
@@ -833,6 +835,10 @@
                     }
                     break;
                 }
+                case H_REMOUNT_VOLUMES_ON_MOVE: {
+                    remountVolumesForRunningUsersOnMove();
+                    break;
+                }
             }
         }
     }
@@ -1286,6 +1292,44 @@
         }
     }
 
+    /**
+     * This method informs vold and storaged that the user has stopped and started whenever move
+     * storage is performed. This ensures that the correct emulated volumes are mounted for the
+     * users other than the current user. This solves an edge case wherein the correct emulated
+     * volumes are not mounted, this will cause the media data to be still stored on internal
+     * storage whereas the data should be stored in the adopted primary storage. This method stops
+     * the users at vold first which will remove the old volumes which and starts the users at vold
+     * which will reattach the correct volumes. This does not performs a full reset as full reset
+     * clears every state from vold and SMS {@link #resetIfRebootedAndConnected} which is expensive
+     * and causes instability.
+     */
+    private void remountVolumesForRunningUsersOnMove() {
+        // Do not want to hold the lock for long
+        final List<Integer> unlockedUsers = new ArrayList<>();
+        synchronized (mLock) {
+            for (int userId : mSystemUnlockedUsers) {
+                if (userId == mCurrentUserId) continue;
+                unlockedUsers.add(userId);
+            }
+        }
+        for (Integer userId : unlockedUsers) {
+            try {
+                mVold.onUserStopped(userId);
+                mStoraged.onUserStopped(userId);
+            } catch (Exception e) {
+                Slog.wtf(TAG, e);
+            }
+        }
+        for (Integer userId : unlockedUsers) {
+            try {
+                mVold.onUserStarted(userId);
+                mStoraged.onUserStarted(userId);
+            } catch (Exception e) {
+                Slog.wtf(TAG, e);
+            }
+        }
+    }
+
     private boolean supportsBlockCheckpoint() throws RemoteException {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
         return mVold.supportsBlockCheckpoint();
@@ -1820,6 +1864,7 @@
 
             mPrimaryStorageUuid = mMoveTargetUuid;
             writeSettingsLocked();
+            mHandler.obtainMessage(H_REMOUNT_VOLUMES_ON_MOVE).sendToTarget();
         }
 
         if (PackageManager.isMoveStatusFinished(status)) {
@@ -4950,5 +4995,24 @@
             }
         }
 
+        @Override
+        public IFsveritySetupAuthToken createFsveritySetupAuthToken(ParcelFileDescriptor authFd,
+                int appUid, @UserIdInt int userId) throws IOException {
+            try {
+                return mInstaller.createFsveritySetupAuthToken(authFd, appUid, userId);
+            } catch (Installer.InstallerException e) {
+                throw new IOException(e);
+            }
+        }
+
+        @Override
+        public int enableFsverity(IFsveritySetupAuthToken authToken, String filePath,
+                String packageName) throws IOException {
+            try {
+                return mInstaller.enableFsverity(authToken, filePath, packageName);
+            } catch (Installer.InstallerException e) {
+                throw new IOException(e);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 03022b0..cd0a9d2 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -74,6 +74,15 @@
         {
             "name": "CtsVcnTestCases",
             "file_patterns": ["VcnManagementService\\.java"]
+        },
+        {
+            "name": "FrameworksNetTests",
+            "options": [
+                {
+                    "exclude-annotation": "com.android.testutils.SkipPresubmit"
+                }
+            ],
+            "file_patterns": ["VpnManagerService\\.java"]
         }
     ],
     "presubmit-large": [
@@ -91,6 +100,21 @@
                 }
             ],
             "file_patterns": ["ClipboardService\\.java"]
+        },
+        {
+            "name": "CtsHostsideNetworkTests",
+            "options": [
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                },
+                {
+                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                },
+                {
+                    "exclude-annotation": "com.android.testutils.SkipPresubmit"
+                }
+            ],
+            "file_patterns": ["VpnManagerService\\.java"]
         }
     ]
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5773e20..0d265a0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3531,6 +3531,12 @@
     @Override
     public boolean clearApplicationUserData(final String packageName, boolean keepState,
             final IPackageDataObserver observer, int userId) {
+        return clearApplicationUserData(packageName, keepState, /*isRestore=*/ false, observer,
+                userId);
+    }
+
+    private boolean clearApplicationUserData(final String packageName, boolean keepState,
+            boolean isRestore, final IPackageDataObserver observer, int userId) {
         enforceNotIsolatedCaller("clearApplicationUserData");
         int uid = Binder.getCallingUid();
         int pid = Binder.getCallingPid();
@@ -3625,6 +3631,9 @@
                         intent.putExtra(Intent.EXTRA_UID,
                                 (appInfo != null) ? appInfo.uid : INVALID_UID);
                         intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId);
+                        if (isRestore) {
+                            intent.putExtra(Intent.EXTRA_IS_RESTORE, true);
+                        }
                         if (isInstantApp) {
                             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
                         }
@@ -3843,15 +3852,16 @@
 
     @Override
     public void forceStopPackage(final String packageName, int userId) {
-        forceStopPackage(packageName, userId, /*flags=*/ 0);
+        forceStopPackage(packageName, userId, /*flags=*/ 0, null);
     }
 
     @Override
     public void forceStopPackageEvenWhenStopping(final String packageName, int userId) {
-        forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED);
+        forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED, null);
     }
 
-    private void forceStopPackage(final String packageName, int userId, int userRunningFlags) {
+    private void forceStopPackage(final String packageName, int userId, int userRunningFlags,
+            String reason) {
         if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED) {
             String msg = "Permission Denial: forceStopPackage() from pid="
@@ -3896,7 +3906,8 @@
                                 + packageName + ": " + e);
                     }
                     if (mUserController.isUserRunning(user, userRunningFlags)) {
-                        forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
+                        forceStopPackageLocked(packageName, pkgUid,
+                                reason == null ? ("from pid " + callingPid) : reason);
                         finishForceStopPackageLocked(packageName, pkgUid);
                     }
                 }
@@ -5435,6 +5446,19 @@
                 intent = new Intent(Intent.ACTION_MAIN);
             }
             try {
+                if (allowlistToken != null) {
+                    final int callingUid = Binder.getCallingUid();
+                    final String packageName;
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        packageName = AppGlobals.getPackageManager().getNameForUid(callingUid);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                    Slog.wtf(TAG, "Send a non-null allowlistToken to a non-PI target."
+                            + " Calling package: " + packageName + "; intent: " + intent
+                            + "; options: " + options);
+                }
                 target.send(code, intent, resolvedType, allowlistToken, null,
                         requiredPermission, options);
             } catch (RemoteException e) {
@@ -14889,8 +14913,8 @@
                                     Intent.EXTRA_QUARANTINED, false);
                             if (suspended && quarantined && packageNames != null) {
                                 for (int i = 0; i < packageNames.length; i++) {
-                                    forceStopPackageLocked(packageNames[i], -1, false, true, true,
-                                            false, false, userId, "suspended");
+                                    forceStopPackage(packageNames[i], userId,
+                                            ActivityManager.FLAG_OR_STOPPED, "quarantined");
                                 }
                             }
 
@@ -19014,6 +19038,13 @@
             return mAppProfiler.mCachedAppsWatermarkData.getCachedAppsHighWatermarkStats(
                     atomTag, resetAfterPull);
         }
+
+        @Override
+        public boolean clearApplicationUserData(final String packageName, boolean keepState,
+                boolean isRestore, final IPackageDataObserver observer, int userId) {
+            return ActivityManagerService.this.clearApplicationUserData(packageName, keepState,
+                    isRestore, observer, userId);
+        }
     }
 
     long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 1a58556..5fa0ffa 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1514,7 +1514,7 @@
                     mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
                         pid,
                         (int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
-                        new Pair<String, Integer>(app.processName, reason)));
+                        new Pair<ProcessRecord, Integer>(app, reason)));
         }
     }
 
@@ -2159,11 +2159,12 @@
                 case REPORT_UNFREEZE_MSG: {
                     int pid = msg.arg1;
                     int frozenDuration = msg.arg2;
-                    Pair<String, Integer> obj = (Pair<String, Integer>) msg.obj;
-                    String processName = obj.first;
+                    Pair<ProcessRecord, Integer> obj = (Pair<ProcessRecord, Integer>) msg.obj;
+                    ProcessRecord app = obj.first;
+                    String processName = app.processName;
                     int reason = obj.second;
 
-                    reportUnfreeze(pid, frozenDuration, processName, reason);
+                    reportUnfreeze(app, pid, frozenDuration, processName, reason);
                 } break;
                 case UID_FROZEN_STATE_CHANGED_MSG: {
                     final boolean frozen = (msg.arg1 == 1);
@@ -2358,10 +2359,11 @@
             }
         }
 
-        private void reportUnfreeze(int pid, int frozenDuration, String processName,
-                @UnfreezeReason int reason) {
+        private void reportUnfreeze(ProcessRecord app, int pid, int frozenDuration,
+                String processName, @UnfreezeReason int reason) {
 
             EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName, reason);
+            app.onProcessUnfrozen();
 
             // See above for why we're not taking mPhenotypeFlagLock here
             if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 5ad49a4..db74f1a 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -225,6 +225,32 @@
         mBaseProcessTracker = baseProcessTracker;
     }
 
+    void onProcessFrozen() {
+        synchronized (mService.mProcessStats.mLock) {
+            final ProcessState tracker = mBaseProcessTracker;
+            if (tracker != null) {
+                final PackageList pkgList = mApp.getPkgList();
+                final long now = SystemClock.uptimeMillis();
+                synchronized (pkgList) {
+                    tracker.onProcessFrozen(now, pkgList.getPackageListLocked());
+                }
+            }
+        }
+    }
+
+    void onProcessUnfrozen() {
+        synchronized (mService.mProcessStats.mLock) {
+            final ProcessState tracker = mBaseProcessTracker;
+            if (tracker != null) {
+                final PackageList pkgList = mApp.getPkgList();
+                final long now = SystemClock.uptimeMillis();
+                synchronized (pkgList) {
+                    tracker.onProcessUnfrozen(now, pkgList.getPackageListLocked());
+                }
+            }
+        }
+    }
+
     void onProcessActive(IApplicationThread thread, ProcessStatsService tracker) {
         if (mThread == null) {
             synchronized (mProfilerLock) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e2edd8a..cfbb5a5 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1359,6 +1359,15 @@
         return false;
     }
 
+    void onProcessFrozen() {
+        mProfile.onProcessFrozen();
+    }
+
+    void onProcessUnfrozen() {
+        mProfile.onProcessUnfrozen();
+    }
+
+
     /*
      *  Delete all packages from list except the package indicated in info
      */
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 6d42da6..c05a1f6 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -772,7 +772,6 @@
         final int mVolume;
         final boolean mIsLeOutput;
         final @NonNull String mEventSource;
-        final @AudioSystem.AudioFormatNativeEnumForBtCodec int mCodec;
         final int mAudioSystemDevice;
         final int mMusicDevice;
 
@@ -787,7 +786,6 @@
             mEventSource = d.mEventSource;
             mAudioSystemDevice = audioDevice;
             mMusicDevice = AudioSystem.DEVICE_NONE;
-            mCodec = codec;
         }
 
         // constructor used by AudioDeviceBroker to search similar message
@@ -796,7 +794,6 @@
             mProfile = profile;
             mEventSource = "";
             mMusicDevice = AudioSystem.DEVICE_NONE;
-            mCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
             mAudioSystemDevice = 0;
             mState = 0;
             mSupprNoisy = false;
@@ -811,7 +808,6 @@
             mProfile = profile;
             mEventSource = "";
             mMusicDevice = musicDevice;
-            mCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
             mAudioSystemDevice = audioSystemDevice;
             mState = state;
             mSupprNoisy = false;
@@ -829,7 +825,6 @@
             mEventSource = src.mEventSource;
             mAudioSystemDevice = src.mAudioSystemDevice;
             mMusicDevice = src.mMusicDevice;
-            mCodec = src.mCodec;
         }
 
         // redefine equality op so we can match messages intended for this device
@@ -847,6 +842,19 @@
             }
             return false;
         }
+
+        @Override
+        public String toString() {
+            return "BtDeviceInfo: device=" + mDevice.toString()
+                            + " state=" + mState
+                            + " prof=" + mProfile
+                            + " supprNoisy=" + mSupprNoisy
+                            + " volume=" + mVolume
+                            + " isLeOutput=" + mIsLeOutput
+                            + " eventSource=" + mEventSource
+                            + " audioSystemDevice=" + mAudioSystemDevice
+                            + " musicDevice=" + mMusicDevice;
+        }
     }
 
     BtDeviceInfo createBtDeviceInfo(@NonNull BtDeviceChangedData d, @NonNull BluetoothDevice device,
@@ -859,9 +867,6 @@
                 break;
             case BluetoothProfile.A2DP:
                 audioDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
-                synchronized (mDeviceStateLock) {
-                    codec = mBtHelper.getA2dpCodec(device);
-                }
                 break;
             case BluetoothProfile.HEARING_AID:
                 audioDevice = AudioSystem.DEVICE_OUT_HEARING_AID;
@@ -1696,8 +1701,11 @@
                 case MSG_L_SET_BT_ACTIVE_DEVICE:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
-                            BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
-                            mDeviceInventory.onSetBtActiveDevice(btInfo,
+                            final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
+                            @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+                                    mBtHelper.getA2dpCodecWithFallbackToSBC(
+                                            btInfo.mDevice, "MSG_L_SET_BT_ACTIVE_DEVICE");
+                            mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
                                     (btInfo.mProfile
                                             != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
                                             ? mAudioService.getBluetoothContextualVolumeStream()
@@ -1730,12 +1738,16 @@
                                 (String) msg.obj, msg.arg1);
                     }
                     break;
-                case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
+                case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: {
+                    final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
                     synchronized (mDeviceStateLock) {
+                        @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+                                mBtHelper.getA2dpCodecWithFallbackToSBC(
+                                        btInfo.mDevice, "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
                         mDeviceInventory.onBluetoothDeviceConfigChange(
-                                (BtDeviceInfo) msg.obj, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
+                                btInfo, codec, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
                     }
-                    break;
+                } break;
                 case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
                     onSendBecomingNoisyIntent();
                     break;
@@ -1831,20 +1843,12 @@
                     }
                     break;
                 case MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT: {
-                    final BtDeviceInfo info = (BtDeviceInfo) msg.obj;
-                    if (info.mDevice == null) break;
+                    final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
+                    if (btInfo.mDevice == null) break;
                     AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                            "msg: onBluetoothActiveDeviceChange "
-                                    + " state=" + info.mState
-                                    // only querying address as this is the only readily available
-                                    // field on the device
-                                    + " addr=" + info.mDevice.getAddress()
-                                    + " prof=" + info.mProfile
-                                    + " supprNoisy=" + info.mSupprNoisy
-                                    + " src=" + info.mEventSource
-                                    )).printLog(TAG));
+                            "msg: onBluetoothActiveDeviceChange " + btInfo)).printLog(TAG));
                     synchronized (mDeviceStateLock) {
-                        mDeviceInventory.setBluetoothActiveDevice(info);
+                        mDeviceInventory.setBluetoothActiveDevice(btInfo);
                     }
                 } break;
                 case MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY: {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index da89502..60af280 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -503,9 +503,11 @@
         }
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-    void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int streamType) {
+    // @GuardedBy("mDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
+    void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
+                             @AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
+                             int streamType) {
         if (AudioService.DEBUG_DEVICES) {
             Log.d(TAG, "onSetBtActiveDevice"
                     + " btDevice=" + btInfo.mDevice
@@ -518,10 +520,7 @@
         }
 
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent("BT connected:"
-                        + " addr=" + address
-                        + " profile=" + btInfo.mProfile
-                        + " state=" + btInfo.mState
-                        + " codec=" + AudioSystem.audioFormatToString(btInfo.mCodec)));
+                        + btInfo + " codec=" + AudioSystem.audioFormatToString(codec)));
 
         new MediaMetrics.Item(mMetricsId + "onSetBtActiveDevice")
                 .set(MediaMetrics.Property.STATUS, btInfo.mProfile)
@@ -529,7 +528,7 @@
                         AudioSystem.getDeviceName(btInfo.mAudioSystemDevice))
                 .set(MediaMetrics.Property.ADDRESS, address)
                 .set(MediaMetrics.Property.ENCODING,
-                        AudioSystem.audioFormatToString(btInfo.mCodec))
+                        AudioSystem.audioFormatToString(codec))
                 .set(MediaMetrics.Property.EVENT, "onSetBtActiveDevice")
                 .set(MediaMetrics.Property.STREAM_TYPE,
                         AudioSystem.streamToString(streamType))
@@ -568,7 +567,7 @@
                                     btInfo.mVolume * 10, btInfo.mAudioSystemDevice,
                                     "onSetBtActiveDevice");
                         }
-                        makeA2dpDeviceAvailable(btInfo, "onSetBtActiveDevice");
+                        makeA2dpDeviceAvailable(btInfo, codec, "onSetBtActiveDevice");
                     }
                     break;
                 case BluetoothProfile.HEARING_AID:
@@ -594,9 +593,10 @@
     }
 
 
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     /*package*/ void onBluetoothDeviceConfigChange(
-            @NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int event) {
+            @NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
+            @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, int event) {
         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
                 + "onBluetoothDeviceConfigChange")
                 .set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
@@ -610,7 +610,6 @@
             Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
         }
         int volume = btInfo.mVolume;
-        @AudioSystem.AudioFormatNativeEnumForBtCodec final int audioCodec = btInfo.mCodec;
 
         String address = btDevice.getAddress();
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
@@ -639,8 +638,7 @@
             }
 
             mmi.set(MediaMetrics.Property.ADDRESS, address)
-                    .set(MediaMetrics.Property.ENCODING,
-                            AudioSystem.audioFormatToString(audioCodec))
+                    .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(codec))
                     .set(MediaMetrics.Property.INDEX, volume)
                     .set(MediaMetrics.Property.NAME, di.mDeviceName);
 
@@ -648,20 +646,19 @@
             if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
                 boolean a2dpCodecChange = false;
                 if (btInfo.mProfile == BluetoothProfile.A2DP) {
-                    if (di.mDeviceCodecFormat != audioCodec) {
-                        di.mDeviceCodecFormat = audioCodec;
+                    if (di.mDeviceCodecFormat != codec) {
+                        di.mDeviceCodecFormat = codec;
                         mConnectedDevices.replace(key, di);
                         a2dpCodecChange = true;
                     }
                     final int res = mAudioSystem.handleDeviceConfigChange(
-                            btInfo.mAudioSystemDevice, address,
-                            BtHelper.getName(btDevice), audioCodec);
+                            btInfo.mAudioSystemDevice, address, BtHelper.getName(btDevice), codec);
 
                     if (res != AudioSystem.AUDIO_STATUS_OK) {
                         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                                 "APM handleDeviceConfigChange failed for A2DP device addr="
                                         + address + " codec="
-                                        + AudioSystem.audioFormatToString(audioCodec))
+                                        + AudioSystem.audioFormatToString(codec))
                                 .printLog(TAG));
 
                         // force A2DP device disconnection in case of error so that AudioService
@@ -672,7 +669,7 @@
                         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                                 "APM handleDeviceConfigChange success for A2DP device addr="
                                         + address
-                                        + " codec=" + AudioSystem.audioFormatToString(audioCodec))
+                                        + " codec=" + AudioSystem.audioFormatToString(codec))
                                 .printLog(TAG));
 
                     }
@@ -1338,7 +1335,7 @@
      * @param device the device whose connection state is queried
      * @return true if connected
      */
-    // called with AudioDeviceBroker.mDeviceStateLock lock held
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     public boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) {
         final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
                 device.getAddress());
@@ -1489,7 +1486,7 @@
         }
     }
 
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     /*package*/ void onBtProfileDisconnected(int profile) {
         switch (profile) {
             case BluetoothProfile.HEADSET:
@@ -1554,7 +1551,7 @@
         disconnectLeAudio(AudioSystem.DEVICE_OUT_BLE_BROADCAST);
     }
 
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     private void disconnectHeadset() {
         boolean disconnect = false;
         synchronized (mDevicesLock) {
@@ -1594,9 +1591,10 @@
         return mCurAudioRoutes;
     }
 
-    // only public for mocking/spying
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-    @VisibleForTesting
+    /**
+     * Set a Bluetooth device to active.
+     */
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     public int setBluetoothActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo info) {
         int delay;
         synchronized (mDevicesLock) {
@@ -1617,12 +1615,7 @@
             }
 
             if (AudioService.DEBUG_DEVICES) {
-                Log.i(TAG, "setBluetoothActiveDevice device: " + info.mDevice
-                        + " profile: " + BluetoothProfile.getProfileName(info.mProfile)
-                        + " state: " + BluetoothProfile.getConnectionStateName(info.mState)
-                        + " delay(ms): " + delay
-                        + " codec:" + Integer.toHexString(info.mCodec)
-                        + " suppressNoisyIntent: " + info.mSupprNoisy);
+                Log.i(TAG, "setBluetoothActiveDevice " + info.toString() + " delay(ms): " + delay);
             }
             mDeviceBroker.postBluetoothActiveDevice(info, delay);
             if (info.mProfile == BluetoothProfile.HEARING_AID
@@ -1658,10 +1651,10 @@
 
     @GuardedBy("mDevicesLock")
     private void makeA2dpDeviceAvailable(AudioDeviceBroker.BtDeviceInfo btInfo,
+                                         @AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
                                          String eventSource) {
         final String address = btInfo.mDevice.getAddress();
         final String name = BtHelper.getName(btInfo.mDevice);
-        final int a2dpCodec = btInfo.mCodec;
 
         // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
         // audio policy manager
@@ -1671,7 +1664,7 @@
         AudioDeviceAttributes ada = new AudioDeviceAttributes(
                 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name);
         final int res = mAudioSystem.setDeviceConnectionState(ada,
-                AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec);
+                AudioSystem.DEVICE_STATE_AVAILABLE, codec);
 
         // TODO: log in MediaMetrics once distinction between connection failure and
         // double connection is made.
@@ -1694,7 +1687,7 @@
         //   time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
         UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
         final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
-                address, a2dpCodec, sensorUuid);
+                address, codec, sensorUuid);
         final String diKey = di.getKey();
         mConnectedDevices.put(diKey, di);
         // on a connection always overwrite the device seen by AudioPolicy, see comment above when
@@ -1910,9 +1903,9 @@
     }
 
     @GuardedBy("mDevicesLock")
-    private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
+    private void makeA2dpDeviceUnavailableNow(String address, int codec) {
         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address)
-                .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(a2dpCodec))
+                .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(codec))
                 .set(MediaMetrics.Property.EVENT, "makeA2dpDeviceUnavailableNow");
 
         if (address == null) {
@@ -1939,7 +1932,7 @@
         AudioDeviceAttributes ada = new AudioDeviceAttributes(
                 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
         final int res = mAudioSystem.setDeviceConnectionState(ada,
-                AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec);
+                AudioSystem.DEVICE_STATE_UNAVAILABLE, codec);
 
         if (res != AudioSystem.AUDIO_STATUS_OK) {
             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 1462b3c..6af409e 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -173,8 +173,8 @@
     //----------------------------------------------------------------------
     // Interface for AudioDeviceBroker
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    // @GuardedBy("mDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     /*package*/ synchronized void onSystemReady() {
         mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
         resetBluetoothSco();
@@ -263,8 +263,20 @@
         return AudioSystem.bluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec
+            int getA2dpCodecWithFallbackToSBC(
+                    @NonNull BluetoothDevice device, @NonNull String source) {
+        @AudioSystem.AudioFormatNativeEnumForBtCodec int codec = getA2dpCodec(device);
+        if (codec == AudioSystem.AUDIO_FORMAT_DEFAULT) {
+            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                    "getA2dpCodec DEFAULT from " + source + " fallback to SBC"));
+            return AudioSystem.AUDIO_FORMAT_SBC;
+        }
+        return codec;
+    }
+
+    // @GuardedBy("mDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     /*package*/ synchronized void onReceiveBtEvent(Intent intent) {
         final String action = intent.getAction();
 
@@ -283,8 +295,8 @@
      * Exclusively called from AudioDeviceBroker when handling MSG_L_RECEIVED_BT_EVENT
      * as part of the serialization of the communication route selection
      */
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    // @GuardedBy("mDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     private void onScoAudioStateChanged(int state) {
         boolean broadcast = false;
         int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
@@ -355,16 +367,16 @@
                 == BluetoothHeadset.STATE_AUDIO_CONNECTED;
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    // @GuardedBy("mDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode,
                 @NonNull String eventSource) {
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
         return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    // @GuardedBy("mDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     /*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) {
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
         return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
@@ -435,8 +447,8 @@
         mScoConnectionState = state;
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    // @GuardedBy("mDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     /*package*/ synchronized void resetBluetoothSco() {
         mScoAudioState = SCO_STATE_INACTIVE;
         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
@@ -445,7 +457,8 @@
         mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
     }
 
-    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    // @GuardedBy("mDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     /*package*/ synchronized void onBtProfileDisconnected(int profile) {
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                 "BT profile " + BluetoothProfile.getProfileName(profile) + " disconnected"));
@@ -474,7 +487,8 @@
         }
     }
 
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    // @GuardedBy("mDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                 "BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy "
@@ -522,8 +536,8 @@
         }
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    // @GuardedBy("mDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     private void onHeadsetProfileConnected(BluetoothHeadset headset) {
         // Discard timeout message
         mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
@@ -642,8 +656,8 @@
         return btDevice == null ? "(null)" : btDevice.getAnonymizedAddress();
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    // @GuardedBy("mDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     /*package */ synchronized void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
         Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice)
                 + " -> " + getAnonymizedAddress(btDevice));
@@ -712,8 +726,8 @@
 
     //----------------------------------------------------------------------
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    // @GuardedBy("mDeviceBroker.mSetModeLock")
+    // @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     @GuardedBy("BtHelper.this")
     private boolean requestScoState(int state, int scoAudioMode) {
         checkScoAudioState();
diff --git a/services/core/java/com/android/server/audio/UuidUtils.java b/services/core/java/com/android/server/audio/UuidUtils.java
index 2003619..035bea3 100644
--- a/services/core/java/com/android/server/audio/UuidUtils.java
+++ b/services/core/java/com/android/server/audio/UuidUtils.java
@@ -45,26 +45,24 @@
      *  Generate a headtracking UUID from AudioDeviceAttributes
      */
     public static UUID uuidFromAudioDeviceAttributes(AudioDeviceAttributes device) {
-        switch (device.getInternalType()) {
-            case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP:
-                String address = device.getAddress().replace(":", "");
-                if (address.length() != 12) {
-                    return null;
-                }
-                address = "0x" + address;
-                long lsb = LSB_PREFIX_BT;
-                try {
-                    lsb |= Long.decode(address).longValue();
-                } catch (NumberFormatException e) {
-                    return null;
-                }
-                if (AudioService.DEBUG_DEVICES) {
-                    Slog.i(TAG, "uuidFromAudioDeviceAttributes lsb: " + Long.toHexString(lsb));
-                }
-                return new UUID(0, lsb);
-            default:
-                // Handle other device types here
-                return null;
+        if (!AudioSystem.isBluetoothA2dpOutDevice(device.getInternalType())
+                && !AudioSystem.isBluetoothLeOutDevice(device.getInternalType())) {
+            return null;
         }
+        String address = device.getAddress().replace(":", "");
+        if (address.length() != 12) {
+            return null;
+        }
+        address = "0x" + address;
+        long lsb = LSB_PREFIX_BT;
+        try {
+            lsb |= Long.decode(address).longValue();
+        } catch (NumberFormatException e) {
+            return null;
+        }
+        if (AudioService.DEBUG_DEVICES) {
+            Slog.i(TAG, "uuidFromAudioDeviceAttributes lsb: " + Long.toHexString(lsb));
+        }
+        return new UUID(0, lsb);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStats.java b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
index e109cc8..707240b 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStats.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
@@ -90,6 +90,11 @@
         mRejectedAttempts = 0;
     }
 
+    /** Update enrollment notification counter after sending a notification. */
+    public void updateNotificationCounter() {
+        mEnrollmentNotifications++;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (this == obj) {
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index 3d1b162..e8a20de 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -68,8 +68,10 @@
         @Override
         public void onReceive(@NonNull Context context, @NonNull Intent intent) {
             final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
             if (userId != UserHandle.USER_NULL
                     && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
+                Slog.d(TAG, "Removing data for user: " + userId);
                 onUserRemoved(userId);
             }
         }
@@ -84,7 +86,9 @@
         mModality = modality;
         mBiometricNotification = biometricNotification;
 
-        context.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED));
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+        context.registerReceiver(mBroadcastReceiver, intentFilter);
     }
 
     private void initializeUserAuthenticationStatsMap() {
@@ -121,10 +125,11 @@
 
         authenticationStats.authenticate(authenticated);
 
+        sendNotificationIfNeeded(userId);
+
         if (mPersisterInitialized) {
             persistDataIfNeeded(userId);
         }
-        sendNotificationIfNeeded(userId);
     }
 
     /** Check if a notification should be sent after a calculation cycle. */
@@ -164,8 +169,10 @@
         }
         if (hasEnrolledFace && !hasEnrolledFingerprint) {
             mBiometricNotification.sendFpEnrollNotification(mContext);
+            authenticationStats.updateNotificationCounter();
         } else if (!hasEnrolledFace && hasEnrolledFingerprint) {
             mBiometricNotification.sendFaceEnrollNotification(mContext);
+            authenticationStats.updateNotificationCounter();
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
index 74e1410..8122b1d 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
@@ -59,7 +59,7 @@
         // The package info in the context isn't initialized in the way it is for normal apps,
         // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
         // build the path manually below using the same policy that appears in ContextImpl.
-        final File prefsFile = new File(Environment.getDataSystemDeDirectory(), FILE_NAME);
+        final File prefsFile = new File(Environment.getDataSystemDirectory(), FILE_NAME);
         mSharedPreferences = context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
     }
 
@@ -137,16 +137,19 @@
                     iterator.remove();
                     break;
                 }
+                // Reset frrStatJson when user doesn't exist.
+                frrStatJson = null;
             }
 
-            // If there's existing frr stats in the file, we want to update the stats for the given
-            // modality and keep the stats for other modalities.
+            // Checks if this is a new user and there's no JSON for this user in the storage.
             if (frrStatJson == null) {
                 frrStatJson = new JSONObject().put(USER_ID, userId);
             }
             frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts,
                     enrollmentNotifications, modality));
 
+            Slog.d(TAG, "frrStatsSet to persist: " + frrStatsSet);
+
             mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply();
 
         } catch (JSONException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index 2ff695d..f1c74f0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -78,7 +78,8 @@
                 null /* options */, UserHandle.CURRENT);
 
         showNotificationHelper(context, name, title, content, pendingIntent, FACE_RE_ENROLL_CHANNEL,
-                FACE_RE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET);
+                Notification.CATEGORY_SYSTEM, FACE_RE_ENROLL_NOTIFICATION_TAG,
+                Notification.VISIBILITY_SECRET);
     }
 
     /**
@@ -95,15 +96,14 @@
 
         final Intent intent = new Intent(FACE_ENROLL_ACTION);
         intent.setPackage(SETTINGS_PACKAGE);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        context.startActivity(intent);
 
         final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
                 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
                 null /* options */, UserHandle.CURRENT);
 
         showNotificationHelper(context, name, title, content, pendingIntent, FACE_ENROLL_CHANNEL,
-                FACE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC);
+                Notification.CATEGORY_RECOMMENDATION, FACE_ENROLL_NOTIFICATION_TAG,
+                Notification.VISIBILITY_PUBLIC);
     }
 
     /**
@@ -120,16 +120,14 @@
 
         final Intent intent = new Intent(FINGERPRINT_ENROLL_ACTION);
         intent.setPackage(SETTINGS_PACKAGE);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        context.startActivity(intent);
 
         final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
                 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
                 null /* options */, UserHandle.CURRENT);
 
         showNotificationHelper(context, name, title, content, pendingIntent,
-                FINGERPRINT_ENROLL_CHANNEL, FINGERPRINT_ENROLL_NOTIFICATION_TAG,
-                Notification.VISIBILITY_PUBLIC);
+                Notification.CATEGORY_RECOMMENDATION, FINGERPRINT_ENROLL_CHANNEL,
+                FINGERPRINT_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC);
     }
 
     /**
@@ -163,13 +161,13 @@
                 null /* options */, UserHandle.CURRENT);
 
         showNotificationHelper(context, name, title, content, pendingIntent,
-                FINGERPRINT_BAD_CALIBRATION_CHANNEL, BAD_CALIBRATION_NOTIFICATION_TAG,
-                Notification.VISIBILITY_SECRET);
+                Notification.CATEGORY_SYSTEM, FINGERPRINT_BAD_CALIBRATION_CHANNEL,
+                BAD_CALIBRATION_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET);
     }
 
     private static void showNotificationHelper(Context context, String name, String title,
-                String content, PendingIntent pendingIntent, String channelName,
-                String notificationTag, int visibility) {
+                String content, PendingIntent pendingIntent, String category,
+                String channelName, String notificationTag, int visibility) {
         final NotificationManager notificationManager =
                 context.getSystemService(NotificationManager.class);
         final NotificationChannel channel = new NotificationChannel(channelName, name,
@@ -182,7 +180,7 @@
                 .setOnlyAlertOnce(true)
                 .setLocalOnly(true)
                 .setAutoCancel(true)
-                .setCategory(Notification.CATEGORY_SYSTEM)
+                .setCategory(category)
                 .setContentIntent(pendingIntent)
                 .setVisibility(visibility)
                 .build();
diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING
new file mode 100644
index 0000000..687d4b0
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING
@@ -0,0 +1,30 @@
+{
+  "presubmit": [
+      {
+          "name": "FrameworksNetTests",
+          "options": [
+              {
+                  "exclude-annotation": "com.android.testutils.SkipPresubmit"
+              }
+          ],
+          "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+      }
+  ],
+  "presubmit-large": [
+      {
+          "name": "CtsHostsideNetworkTests",
+          "options": [
+              {
+                "exclude-annotation": "androidx.test.filters.FlakyTest"
+              },
+              {
+                "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+              },
+              {
+                "exclude-annotation": "com.android.testutils.SkipPresubmit"
+              }
+          ],
+        "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+      }
+  ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index cba5039..bfccd58 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3066,7 +3066,8 @@
          * <p>This variable controls the retry delay, and is reset when the VPN pass network
          * validation.
          */
-        private int mValidationFailRetryCount = 0;
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+        int mValidationFailRetryCount = 0;
 
         /**
          * The number of attempts since the last successful connection.
@@ -3296,13 +3297,6 @@
                         }
                         agentConnect(this::onValidationStatus);
                         return; // Link properties are already sent.
-                    } else {
-                        // Underlying networks also set in agentConnect()
-                        doSetUnderlyingNetworks(networkAgent, Collections.singletonList(network));
-                        mNetworkCapabilities =
-                                new NetworkCapabilities.Builder(mNetworkCapabilities)
-                                        .setUnderlyingNetworks(Collections.singletonList(network))
-                                        .build();
                     }
 
                     lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
@@ -3384,8 +3378,6 @@
 
                     final LinkProperties oldLp = makeLinkProperties();
 
-                    final boolean underlyingNetworkHasChanged =
-                            !Arrays.equals(mConfig.underlyingNetworks, new Network[]{network});
                     mConfig.underlyingNetworks = new Network[] {network};
                     mConfig.mtu = calculateVpnMtu();
 
@@ -3417,18 +3409,9 @@
                                     removed.getAddress(), removed.getPrefixLength());
                         }
                     } else {
-                        // Put below 3 updates into else block is because agentConnect() will do
-                        // those things, so there is no need to do the redundant work.
+                        // Put below update into else block is because agentConnect() will do
+                        // the same things, so there is no need to do the redundant work.
                         if (!newLp.equals(oldLp)) doSendLinkProperties(mNetworkAgent, newLp);
-                        if (underlyingNetworkHasChanged) {
-                            mNetworkCapabilities =
-                                    new NetworkCapabilities.Builder(mNetworkCapabilities)
-                                            .setUnderlyingNetworks(
-                                                    Collections.singletonList(network))
-                                            .build();
-                            doSetUnderlyingNetworks(mNetworkAgent,
-                                    Collections.singletonList(network));
-                        }
                     }
                 }
 
@@ -3554,10 +3537,28 @@
          */
         private void startOrMigrateIkeSession(@Nullable Network underlyingNetwork) {
             if (underlyingNetwork == null) {
+                // For null underlyingNetwork case, there will not be a NetworkAgent available so
+                // no underlying network update is necessary here. Note that updating
+                // mNetworkCapabilities here would also be reasonable, but it will be updated next
+                // time the VPN connects anyway.
                 Log.d(TAG, "There is no active network for starting an IKE session");
                 return;
             }
 
+            final List<Network> networks = Collections.singletonList(underlyingNetwork);
+            // Update network capabilities if underlying network is changed.
+            if (!networks.equals(mNetworkCapabilities.getUnderlyingNetworks())) {
+                mNetworkCapabilities =
+                        new NetworkCapabilities.Builder(mNetworkCapabilities)
+                                .setUnderlyingNetworks(networks)
+                                .build();
+                // No NetworkAgent case happens when Vpn tries to start a new VPN. The underlying
+                // network update will be done later with NetworkAgent connected event.
+                if (mNetworkAgent != null) {
+                    doSetUnderlyingNetworks(mNetworkAgent, networks);
+                }
+            }
+
             if (maybeMigrateIkeSessionAndUpdateVpnTransportInfo(underlyingNetwork)) return;
 
             startIkeSession(underlyingNetwork);
@@ -3897,6 +3898,18 @@
                 // Skip other invalid status if the scheduled recovery exists.
                 if (mScheduledHandleDataStallFuture != null) return;
 
+                // Trigger network validation on the underlying network to possibly cause system
+                // switch default network or try recover if the current default network is broken.
+                //
+                // For the same underlying network, the first validation result should clarify if
+                // it's caused by broken underlying network. So only perform underlying network
+                // re-evaluation after first validation failure to prevent extra network resource
+                // costs on sending probes.
+                if (mValidationFailRetryCount == 0) {
+                    mConnectivityManager.reportNetworkConnectivity(
+                            mActiveNetwork, false /* hasConnectivity */);
+                }
+
                 if (mValidationFailRetryCount < MAX_MOBIKE_RECOVERY_ATTEMPT) {
                     Log.d(TAG, "Validation failed");
 
diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java
index f6d06aa..955b8d9 100644
--- a/services/core/java/com/android/server/display/WifiDisplayController.java
+++ b/services/core/java/com/android/server/display/WifiDisplayController.java
@@ -1062,8 +1062,10 @@
     }
 
     private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
+        WifiP2pWfdInfo wfdInfo = device.getWfdInfo();
+        boolean isSessionAvailable = wfdInfo != null && wfdInfo.isSessionAvailable();
         return new WifiDisplay(device.deviceAddress, device.deviceName, null,
-                true, device.getWfdInfo().isSessionAvailable(), false);
+                true, isSessionAvailable, false);
     }
 
     private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 39b8bfd..36adea7 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -73,6 +73,7 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.HexDump;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.NetdUtils.ModifyOperation;
 import com.android.net.module.util.PermissionUtils;
@@ -782,7 +783,10 @@
     @Override
     public boolean getIpForwardingEnabled() throws IllegalStateException{
         PermissionUtils.enforceNetworkStackPermission(mContext);
-
+        if (SdkLevel.isAtLeastV()) {
+            throw new UnsupportedOperationException(
+                    "NMS#getIpForwardingEnabled not supported in V+");
+        }
         try {
             return mNetdService.ipfwdEnabled();
         } catch (RemoteException | ServiceSpecificException e) {
@@ -793,7 +797,10 @@
     @Override
     public void setIpForwardingEnabled(boolean enable) {
         PermissionUtils.enforceNetworkStackPermission(mContext);
-        try {
+        if (SdkLevel.isAtLeastV()) {
+            throw new UnsupportedOperationException(
+                    "NMS#setIpForwardingEnabled not supported in V+");
+        }        try {
             if (enable) {
                 mNetdService.ipfwdEnableForwarding("tethering");
             } else {
@@ -807,6 +814,9 @@
     @Override
     public void startTethering(String[] dhcpRange) {
         PermissionUtils.enforceNetworkStackPermission(mContext);
+        if (SdkLevel.isAtLeastV()) {
+            throw new UnsupportedOperationException("NMS#startTethering not supported in V+");
+        }
         try {
             NetdUtils.tetherStart(mNetdService, true /* usingLegacyDnsProxy */, dhcpRange);
         } catch (RemoteException | ServiceSpecificException e) {
@@ -817,6 +827,9 @@
     @Override
     public void stopTethering() {
         PermissionUtils.enforceNetworkStackPermission(mContext);
+        if (SdkLevel.isAtLeastV()) {
+            throw new UnsupportedOperationException("NMS#stopTethering not supported in V+");
+        }
         try {
             mNetdService.tetherStop();
         } catch (RemoteException | ServiceSpecificException e) {
@@ -827,6 +840,9 @@
     @Override
     public boolean isTetheringStarted() {
         PermissionUtils.enforceNetworkStackPermission(mContext);
+        if (SdkLevel.isAtLeastV()) {
+            throw new UnsupportedOperationException("NMS#isTetheringStarted not supported in V+");
+        }
         try {
             return mNetdService.tetherIsEnabled();
         } catch (RemoteException | ServiceSpecificException e) {
@@ -837,6 +853,9 @@
     @Override
     public void tetherInterface(String iface) {
         PermissionUtils.enforceNetworkStackPermission(mContext);
+        if (SdkLevel.isAtLeastV()) {
+            throw new UnsupportedOperationException("NMS#tetherInterface not supported in V+");
+        }
         try {
             final LinkAddress addr = getInterfaceConfig(iface).getLinkAddress();
             final IpPrefix dest = new IpPrefix(addr.getAddress(), addr.getPrefixLength());
@@ -849,6 +868,9 @@
     @Override
     public void untetherInterface(String iface) {
         PermissionUtils.enforceNetworkStackPermission(mContext);
+        if (SdkLevel.isAtLeastV()) {
+            throw new UnsupportedOperationException("NMS#untetherInterface not supported in V+");
+        }
         try {
             NetdUtils.untetherInterface(mNetdService, iface);
         } catch (RemoteException | ServiceSpecificException e) {
@@ -859,6 +881,10 @@
     @Override
     public String[] listTetheredInterfaces() {
         PermissionUtils.enforceNetworkStackPermission(mContext);
+        if (SdkLevel.isAtLeastV()) {
+            throw new UnsupportedOperationException(
+                    "NMS#listTetheredInterfaces not supported in V+");
+        }
         try {
             return mNetdService.tetherInterfaceList();
         } catch (RemoteException | ServiceSpecificException e) {
@@ -869,6 +895,9 @@
     @Override
     public void enableNat(String internalInterface, String externalInterface) {
         PermissionUtils.enforceNetworkStackPermission(mContext);
+        if (SdkLevel.isAtLeastV()) {
+            throw new UnsupportedOperationException("NMS#enableNat not supported in V+");
+        }
         try {
             mNetdService.tetherAddForward(internalInterface, externalInterface);
         } catch (RemoteException | ServiceSpecificException e) {
@@ -879,6 +908,9 @@
     @Override
     public void disableNat(String internalInterface, String externalInterface) {
         PermissionUtils.enforceNetworkStackPermission(mContext);
+        if (SdkLevel.isAtLeastV()) {
+            throw new UnsupportedOperationException("NMS#disableNat not supported in V+");
+        }
         try {
             mNetdService.tetherRemoveForward(internalInterface, externalInterface);
         } catch (RemoteException | ServiceSpecificException e) {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index e490745..a700d32 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1101,8 +1101,11 @@
                     .allowAlarms(true)
                     .allowMedia(true)
                     .build());
-        } else {
+        } else if (rule.zenPolicy != null) {
             policy.apply(rule.zenPolicy);
+        } else {
+            // active rule with no specified policy inherits the default settings
+            policy.apply(mConfig.toZenPolicy());
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index a988821..b8feb4d 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
 import static android.content.pm.PackageManager.DELETE_SUCCEEDED;
 import static android.content.pm.PackageManager.MATCH_KNOWN_PACKAGES;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -63,6 +64,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageUserState;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -443,7 +445,7 @@
             // semantics than normal for uninstalling system apps.
             final boolean clearPackageStateAndReturn;
             synchronized (mPm.mLock) {
-                markPackageUninstalledForUserLPw(ps, user);
+                markPackageUninstalledForUserLPw(ps, user, flags);
                 if (!systemApp) {
                     // Do not uninstall the APK if an app should be cached
                     boolean keepUninstalledPackage =
@@ -547,7 +549,7 @@
     }
 
     @GuardedBy("mPm.mLock")
-    private void markPackageUninstalledForUserLPw(PackageSetting ps, UserHandle user) {
+    private void markPackageUninstalledForUserLPw(PackageSetting ps, UserHandle user, int flags) {
         final int[] userIds = (user == null || user.getIdentifier() == UserHandle.USER_ALL)
                 ? mUserManagerInternal.getUserIds()
                 : new int[] {user.getIdentifier()};
@@ -556,6 +558,12 @@
                 Slog.d(TAG, "Marking package:" + ps.getPackageName()
                         + " uninstalled for user:" + nextUserId);
             }
+            // Preserve ArchiveState if this is not a full uninstall
+            ArchiveState archiveState =
+                    (flags & DELETE_KEEP_DATA) == 0
+                            ? null
+                            : ps.getUserStateOrDefault(nextUserId).getArchiveState();
+
             ps.setUserState(nextUserId,
                     ps.getCeDataInode(nextUserId),
                     COMPONENT_ENABLED_STATE_DEFAULT,
@@ -576,7 +584,7 @@
                     null /*splashScreenTheme*/,
                     0 /*firstInstallTime*/,
                     PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
-                    null /*archiveState*/);
+                    archiveState);
         }
         mPm.mSettings.writeKernelMappingLPr(ps);
     }
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index f3ea42e..2a00a44 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -619,7 +619,7 @@
         pw.println("    --checkin: dump for a checkin");
         pw.println("    -f: print details of intent filters");
         pw.println("    -h: print this help");
-        pw.println("    ---proto: dump data to proto");
+        pw.println("    --proto: dump data to proto");
         pw.println("    --all-components: include all component names in package dump");
         pw.println("    --include-apex: includes the apex packages in package dump");
         pw.println("  cmd may be one of:");
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 3e18387..dd04340 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1144,8 +1144,16 @@
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
         final ParsedPackage parsedPackage;
         try (PackageParser2 pp = mPm.mInjector.getPreparingPackageParser()) {
-            parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
-            AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
+            if (request.getPackageLite() == null || !PackageInstallerSession.isArchivedInstallation(
+                    request.getInstallFlags())) {
+                // TODO: pass packageLite from install request instead of reparsing the package
+                parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
+                AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
+            } else {
+                // Archived install mode, no APK.
+                parsedPackage = pp.parsePackageFromPackageLite(request.getPackageLite(),
+                        parseFlags);
+            }
         } catch (PackageManagerException e) {
             throw new PrepareFailure("Failed parse during installPackageLI", e);
         } finally {
@@ -1547,6 +1555,7 @@
                     // TODO: Are these system flags actually set properly at this stage?
                     boolean isUpdatedSystemAppInferred =
                             pkgSetting != null && pkgSetting.isSystem();
+                    // derivePackageAbi works OK for archived packages despite logging some errors.
                     final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
                             derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage,
                             systemApp, (isUpdatedSystemAppFromExistingSetting
@@ -1582,7 +1591,8 @@
 
         final PackageFreezer freezer =
                 freezePackageForInstall(pkgName, UserHandle.USER_ALL, installFlags,
-                        "installPackageLI", ApplicationExitInfo.REASON_PACKAGE_UPDATED);
+                        "installPackageLI", ApplicationExitInfo.REASON_PACKAGE_UPDATED, request);
+
         boolean shouldCloseFreezerBeforeReturn = true;
         try {
             final PackageState oldPackageState;
@@ -2032,11 +2042,11 @@
     }
 
     private PackageFreezer freezePackageForInstall(String packageName, int userId, int installFlags,
-            String killReason, int exitInfoReason) {
+            String killReason, int exitInfoReason, InstallRequest request) {
         if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
-            return new PackageFreezer(mPm);
+            return new PackageFreezer(mPm, request);
         } else {
-            return mPm.freezePackage(packageName, userId, killReason, exitInfoReason);
+            return mPm.freezePackage(packageName, userId, killReason, exitInfoReason, request);
         }
     }
 
@@ -3207,7 +3217,7 @@
             try (PackageFreezer freezer =
                          mPm.freezePackage(stubPkg.getPackageName(), UserHandle.USER_ALL,
                                  "setEnabledSetting",
-                                 ApplicationExitInfo.REASON_PACKAGE_UPDATED)) {
+                                 ApplicationExitInfo.REASON_PACKAGE_UPDATED, null /* request */)) {
                 pkg = installStubPackageLI(stubPkg, parseFlags, 0 /*scanFlags*/);
                 mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
                 synchronized (mPm.mLock) {
@@ -3231,7 +3241,8 @@
                 try (PackageFreezer freezer =
                              mPm.freezePackage(stubPkg.getPackageName(), UserHandle.USER_ALL,
                                      "setEnabledSetting",
-                                     ApplicationExitInfo.REASON_PACKAGE_UPDATED)) {
+                                     ApplicationExitInfo.REASON_PACKAGE_UPDATED,
+                                     null /* request */)) {
                     synchronized (mPm.mLock) {
                         // NOTE: Ensure the system package is enabled; even for a compressed stub.
                         // If we don't, installing the system package fails during scan
@@ -4291,7 +4302,8 @@
                                 + " name: " + pkgSetting.getPackageName());
                 try (@SuppressWarnings("unused") PackageFreezer freezer = mPm.freezePackage(
                         parsedPackage.getPackageName(), UserHandle.USER_ALL,
-                        "scanPackageInternalLI", ApplicationExitInfo.REASON_OTHER)) {
+                        "scanPackageInternalLI", ApplicationExitInfo.REASON_OTHER,
+                        null /* request */)) {
                     DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
                     deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true,
                             mPm.mUserManager.getUserIds(), 0, null, false);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 6fc14e8..fe7c086 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -35,6 +35,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
+import android.content.pm.parsing.PackageLite;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Process;
@@ -93,6 +94,8 @@
     private int[] mNewUsers;
     @Nullable
     private AndroidPackage mPkg;
+    @Nullable
+    private PackageLite mPackageLite;
     private int mReturnCode;
     private int mInternalErrorCode;
     @Nullable
@@ -142,6 +145,7 @@
                 params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
                 params.mDataLoaderType, params.mPackageSource,
                 params.mApplicationEnabledSettingPersistent);
+        mPackageLite = params.mPackageLite;
         mPackageMetrics = new PackageMetrics(this);
         mIsInstallInherit = params.mIsInherit;
         mSessionId = params.mSessionId;
@@ -306,6 +310,11 @@
     }
 
     @Nullable
+    public PackageLite getPackageLite() {
+        return mPackageLite;
+    }
+
+    @Nullable
     public String getTraceMethod() {
         return mInstallArgs == null ? null : mInstallArgs.mTraceMethod;
     }
@@ -831,4 +840,16 @@
             }
         }
     }
+
+    public void onFreezeStarted() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepStarted(PackageMetrics.STEP_FREEZE_INSTALL);
+        }
+    }
+
+    public void onFreezeCompleted() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepFinished(PackageMetrics.STEP_FREEZE_INSTALL);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 9ac983d..6233c9b 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -29,6 +29,7 @@
 import android.os.CreateAppDataResult;
 import android.os.IBinder;
 import android.os.IInstalld;
+import android.os.ParcelFileDescriptor;
 import android.os.ReconcileSdkDataArgs;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -1161,6 +1162,55 @@
         }
     }
 
+    /**
+     * Returns an auth token for the provided writable FD.
+     *
+     * @param authFd a file descriptor to proof that the caller can write to the file.
+     * @param appUid uid of the calling app.
+     * @param userId id of the user whose app file to enable fs-verity.
+     *
+     * @return authToken, or null if a remote call shouldn't be continued. See {@link
+     * #checkBeforeRemote}.
+     *
+     * @throws InstallerException if the remote call failed.
+     */
+    public IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken(
+            ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId)
+            throws InstallerException {
+        if (!checkBeforeRemote()) {
+            return null;
+        }
+        try {
+            return mInstalld.createFsveritySetupAuthToken(authFd, appUid, userId);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
+    /**
+     * Enables fs-verity to the given app file.
+     *
+     * @param authToken a token previously returned from {@link #createFsveritySetupAuthToken}.
+     * @param filePath file path of the package to enable fs-verity.
+     * @param packageName name of the package.
+     *
+     * @return 0 if the operation was successful, otherwise {@code errno}.
+     *
+     * @throws InstallerException if the remote call failed (e.g. see {@link #checkBeforeRemote}).
+     */
+    public int enableFsverity(IInstalld.IFsveritySetupAuthToken authToken, String filePath,
+            String packageName) throws InstallerException {
+        if (!checkBeforeRemote()) {
+            throw new InstallerException("fs-verity wasn't enabled with an isolated installer");
+        }
+        BlockGuard.getVmPolicy().onPathAccess(filePath);
+        try {
+            return mInstalld.enableFsverity(authToken, filePath, packageName);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
     public static class InstallerException extends Exception {
         public InstallerException(String detailMessage) {
             super(detailMessage);
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 30a23bf..fe6a8a1 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -543,7 +543,6 @@
             mInstallPackageHelper.installPackagesTraced(installRequests);
 
             for (InstallRequest request : installRequests) {
-                request.onInstallCompleted();
                 doPostInstall(request);
             }
         }
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 01c2734..148e0df 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -147,7 +147,8 @@
         final PackageFreezer freezer;
         synchronized (mPm.mLock) {
             freezer = mPm.freezePackage(packageName, UserHandle.USER_ALL,
-                    "movePackageInternal", ApplicationExitInfo.REASON_USER_REQUESTED);
+                    "movePackageInternal", ApplicationExitInfo.REASON_USER_REQUESTED,
+                    null /* request */);
         }
 
         final Bundle extras = new Bundle();
diff --git a/services/core/java/com/android/server/pm/PackageFreezer.java b/services/core/java/com/android/server/pm/PackageFreezer.java
index 841b66e..7c56157 100644
--- a/services/core/java/com/android/server/pm/PackageFreezer.java
+++ b/services/core/java/com/android/server/pm/PackageFreezer.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.pm.PackageManager;
 
 import dalvik.system.CloseGuard;
@@ -29,6 +30,8 @@
  * app code/data to prevent the app from running while you're working.
  */
 final class PackageFreezer implements AutoCloseable {
+    @Nullable private InstallRequest mInstallRequest;
+
     private final String mPackageName;
 
     private final AtomicBoolean mClosed = new AtomicBoolean();
@@ -43,18 +46,29 @@
      * {@link PackageManager#INSTALL_DONT_KILL_APP} or
      * {@link PackageManager#DELETE_DONT_KILL_APP}.
      */
-    PackageFreezer(PackageManagerService pm) {
+
+    PackageFreezer(PackageManagerService pm, @Nullable InstallRequest request) {
         mPm = pm;
         mPackageName = null;
         mClosed.set(true);
         mCloseGuard.open("close");
+        mInstallRequest = request;
+        // We only focus on the install Freeze metrics now
+        if (mInstallRequest != null) {
+            mInstallRequest.onFreezeStarted();
+        }
     }
 
     PackageFreezer(String packageName, int userId, String killReason,
-            PackageManagerService pm, int exitInfoReason) {
+            PackageManagerService pm, int exitInfoReason, @Nullable InstallRequest request) {
         mPm = pm;
         mPackageName = packageName;
+        mInstallRequest = request;
         final PackageSetting ps;
+        // We only focus on the install Freeze metrics now
+        if (mInstallRequest != null) {
+            mInstallRequest.onFreezeStarted();
+        }
         synchronized (mPm.mLock) {
             final int refCounts = mPm.mFrozenPackages
                     .getOrDefault(mPackageName, 0 /* defaultValue */) + 1;
@@ -92,5 +106,10 @@
                 }
             }
         }
+        // We only focus on the install Freeze metrics now
+        if (mInstallRequest != null) {
+            mInstallRequest.onFreezeCompleted();
+            mInstallRequest = null;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 83d2f6a..b4ca477 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -93,6 +93,7 @@
                 mPm.mRunningInstalls.delete(msg.arg1);
 
                 request.closeFreezer();
+                request.onInstallCompleted();
                 request.runPostInstallRunnable();
                 if (!request.isInstallExistingForUser()) {
                     mInstallPackageHelper.handlePackagePostInstall(request, didRestore);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 923bbab..1554072 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -31,6 +31,7 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
 import static android.content.pm.PackageManager.INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_STAGED;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -52,6 +53,7 @@
 import static com.android.server.pm.PackageInstallerService.prepareStageDir;
 import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
 import static com.android.server.pm.PackageManagerServiceUtils.isInstalledByAdb;
+import static com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
 
 import android.Manifest;
 import android.annotation.AnyThread;
@@ -838,6 +840,10 @@
                 params.dataLoaderParams.getComponentName().getPackageName());
     }
 
+    static boolean isArchivedInstallation(int installFlags) {
+        return (installFlags & PackageManager.INSTALL_ARCHIVED) != 0;
+    }
+
     private final Handler.Callback mHandlerCallback = new Handler.Callback() {
         @Override
         public boolean handleMessage(Message msg) {
@@ -896,6 +902,10 @@
         return isSystemDataLoaderInstallation(this.params);
     }
 
+    private boolean isArchivedInstallation() {
+        return isArchivedInstallation(this.params.installFlags);
+    }
+
     /**
      * @return {@code true} iff the installing is app an device owner or affiliated profile owner.
      */
@@ -1146,6 +1156,17 @@
         if (isIncrementalInstallation() && !IncrementalManager.isAllowed()) {
             throw new IllegalArgumentException("Incremental installation not allowed.");
         }
+
+        if (isArchivedInstallation()) {
+            if (params.mode != SessionParams.MODE_FULL_INSTALL) {
+                throw new IllegalArgumentException(
+                        "Archived installation can only be full install.");
+            }
+            if (!isStreamingInstallation() || !isSystemDataLoaderInstallation()) {
+                throw new IllegalArgumentException(
+                        "Archived installation can only use Streaming System DataLoader.");
+            }
+        }
     }
 
     /**
@@ -1462,6 +1483,58 @@
     }
 
     @GuardedBy("mLock")
+    private List<ApkLite> getAddedApkLitesLocked() throws PackageManagerException {
+        if (!isArchivedInstallation()) {
+            List<File> files = getAddedApksLocked();
+            final List<ApkLite> result = new ArrayList<>(files.size());
+
+            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+            for (int i = 0, size = files.size(); i < size; ++i) {
+                final ParseResult<ApkLite> parseResult = ApkLiteParseUtils.parseApkLite(
+                        input.reset(), files.get(i),
+                        ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
+                if (parseResult.isError()) {
+                    throw new PackageManagerException(parseResult.getErrorCode(),
+                            parseResult.getErrorMessage(), parseResult.getException());
+                }
+                result.add(parseResult.getResult());
+            }
+
+            return result;
+        }
+
+        InstallationFile[] files = getInstallationFilesLocked();
+        final List<ApkLite> result = new ArrayList<>(files.length);
+
+        for (int i = 0, size = files.length; i < size; ++i) {
+            File file = new File(stageDir, files[i].getName());
+            if (!sAddedApkFilter.accept(file)) {
+                continue;
+            }
+
+            final Metadata metadata;
+            try {
+                metadata = Metadata.fromByteArray(files[i].getMetadata());
+            } catch (IOException e) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Failed to ", e);
+            }
+            if (metadata.getMode() != Metadata.ARCHIVED) {
+                throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
+                        "File metadata is not for ARCHIVED package: " + file);
+            }
+
+            var archPkg = metadata.getArchivedPackage();
+            if (archPkg.packageName == null || archPkg.signingDetails == null) {
+                throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
+                        "ArchivedPackage does not contain required info: " + file);
+            }
+            result.add(new ApkLite(file.getAbsolutePath(), archPkg));
+        }
+        return result;
+    }
+
+    @GuardedBy("mLock")
     private List<File> getRemovedFilesLocked() {
         String[] names = getNamesLocked();
         return filterFiles(stageDir, names, sRemovedFilter);
@@ -2732,9 +2805,18 @@
                 throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                         "Session not sealed");
             }
-            Objects.requireNonNull(mPackageName);
-            Objects.requireNonNull(mSigningDetails);
-            Objects.requireNonNull(mResolvedBaseFile);
+            if (mPackageName == null) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Session no package name");
+            }
+            if (mSigningDetails == null) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Session no signing data");
+            }
+            if (mResolvedBaseFile == null) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Session no resolved base file");
+            }
             final PackageLite result;
             if (!isApexSession()) {
                 // For mode inherit existing, it would link/copy existing files to stage dir in
@@ -3176,7 +3258,7 @@
             }
         }
 
-        final List<File> addedFiles = getAddedApksLocked();
+        final List<ApkLite> addedFiles = getAddedApkLitesLocked();
         if (addedFiles.isEmpty()
                 && (removeSplitList.size() == 0 || getStagedAppMetadataFile() != null)) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
@@ -3190,15 +3272,7 @@
         final ArraySet<String> requiredSplitTypes = new ArraySet<>();
         final ArrayMap<String, ApkLite> splitApks = new ArrayMap<>();
         final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-        for (File addedFile : addedFiles) {
-            final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(),
-                    addedFile, ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
-            if (result.isError()) {
-                throw new PackageManagerException(result.getErrorCode(),
-                        result.getErrorMessage(), result.getException());
-            }
-
-            final ApkLite apk = result.getResult();
+        for (ApkLite apk : addedFiles) {
             if (!stagedSplits.add(apk.getSplitName())) {
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                         "Split " + apk.getSplitName() + " was defined multiple times");
@@ -3214,7 +3288,7 @@
             }
             mHasDeviceAdminReceiver = apk.isHasDeviceAdminReceiver();
 
-            assertApkConsistentLocked(String.valueOf(addedFile), apk);
+            assertApkConsistentLocked(String.valueOf(apk), apk);
 
             // Take this opportunity to enforce uniform naming
             final String targetName = ApkLiteParseUtils.splitNameToFileName(apk);
@@ -3235,7 +3309,10 @@
             }
 
             final File targetFile = new File(stageDir, targetName);
-            resolveAndStageFileLocked(addedFile, targetFile, apk.getSplitName());
+            if (!isArchivedInstallation()) {
+                final File sourceFile = new File(apk.getPath());
+                resolveAndStageFileLocked(sourceFile, targetFile, apk.getSplitName());
+            }
 
             // Base is coming from session
             if (apk.getSplitName() == null) {
@@ -4005,6 +4082,11 @@
             NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
         }
 
+        // Skip native libraries processing for archival installation.
+        if (isArchivedInstallation()) {
+            return;
+        }
+
         NativeLibraryHelper.Handle handle = null;
         try {
             handle = NativeLibraryHelper.Handle.create(packageLite);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ac78429..8faadba 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -77,6 +77,7 @@
 import android.content.IntentSender.SendIntentException;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.AuxiliaryResolveInfo;
 import android.content.pm.ChangedPackages;
 import android.content.pm.Checksum;
@@ -115,7 +116,11 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
 import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.ApkLite;
+import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
@@ -4294,16 +4299,17 @@
     }
 
     public PackageFreezer freezePackage(String packageName, int userId, String killReason,
-            int exitInfoReason) {
-        return new PackageFreezer(packageName, userId, killReason, this, exitInfoReason);
+            int exitInfoReason, InstallRequest request) {
+        return new PackageFreezer(packageName, userId, killReason, this, exitInfoReason, request);
     }
 
     public PackageFreezer freezePackageForDelete(String packageName, int userId, int deleteFlags,
             String killReason, int exitInfoReason) {
         if ((deleteFlags & PackageManager.DELETE_DONT_KILL_APP) != 0) {
-            return new PackageFreezer(this);
+            return new PackageFreezer(this, null /* request */);
         } else {
-            return freezePackage(packageName, userId, killReason, exitInfoReason);
+            return freezePackage(packageName, userId, killReason, exitInfoReason,
+                    null /* request */);
         }
     }
 
@@ -4632,7 +4638,7 @@
             try (PackageFreezer ignored =
                             freezePackage(packageName, UserHandle.USER_ALL,
                                     "clearApplicationProfileData",
-                                    ApplicationExitInfo.REASON_OTHER)) {
+                                    ApplicationExitInfo.REASON_OTHER, null /* request */)) {
                 synchronized (mInstallLock) {
                     mAppDataHelper.clearAppProfilesLIF(pkg);
                 }
@@ -4675,7 +4681,7 @@
                     final boolean succeeded;
                     try (PackageFreezer freezer = freezePackage(packageName, UserHandle.USER_ALL,
                             "clearApplicationUserData",
-                            ApplicationExitInfo.REASON_USER_REQUESTED)) {
+                            ApplicationExitInfo.REASON_USER_REQUESTED, null /* request */)) {
                         synchronized (mInstallLock) {
                             succeeded = clearApplicationUserDataLIF(snapshotComputer(), packageName,
                                     userId);
@@ -6297,6 +6303,38 @@
             }
         }
 
+        @Override
+        public ArchivedPackageParcel getArchivedPackage(String apkPath) {
+            ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+            ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(),
+                    new File(apkPath), ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
+            if (result.isError()) {
+                throw new IllegalArgumentException(result.getErrorMessage(), result.getException());
+            }
+            final ApkLite apk = result.getResult();
+
+            ArchivedPackageParcel archPkg = new ArchivedPackageParcel();
+            archPkg.packageName = apk.getPackageName();
+            archPkg.signingDetails = apk.getSigningDetails();
+
+            archPkg.versionCodeMajor = apk.getVersionCodeMajor();
+            archPkg.versionCode = apk.getVersionCode();
+
+            archPkg.targetSdkVersion = apk.getTargetSdkVersion();
+
+            // These get translated in flags important for user data management.
+            archPkg.clearUserDataAllowed = apk.isClearUserDataAllowed();
+            archPkg.backupAllowed = apk.isBackupAllowed();
+            archPkg.defaultToDeviceProtectedStorage =
+                    apk.isDefaultToDeviceProtectedStorage();
+            archPkg.requestLegacyExternalStorage = apk.isRequestLegacyExternalStorage();
+            archPkg.userDataFragile = apk.isUserDataFragile();
+            archPkg.clearUserDataOnFailedRestoreAllowed =
+                    apk.isClearUserDataOnFailedRestoreAllowed();
+
+            return archPkg;
+        }
+
         /**
          * Wait for the handler to finish handling all pending messages.
          * @param timeoutMillis Maximum time in milliseconds to wait.
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index db997d8..2028231 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -919,16 +919,22 @@
 
         final File packageFile = new File(packagePath);
         final long sizeBytes;
-        try {
-            sizeBytes = InstallLocationUtils.calculateInstalledSize(pkg, abiOverride);
-        } catch (IOException e) {
-            if (!packageFile.exists()) {
-                ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI;
-            } else {
-                ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
-            }
+        if (!PackageInstallerSession.isArchivedInstallation(flags)) {
+            try {
+                sizeBytes = InstallLocationUtils.calculateInstalledSize(pkg, abiOverride);
+            } catch (IOException e) {
+                if (!packageFile.exists()) {
+                    ret.recommendedInstallLocation =
+                            InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI;
+                } else {
+                    ret.recommendedInstallLocation =
+                            InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
+                }
 
-            return ret;
+                return ret;
+            }
+        } else {
+            sizeBytes = 0;
         }
 
         final PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 8bdbe04..d9f1df5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -43,6 +43,7 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageInstaller;
@@ -82,6 +83,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.IBinder;
 import android.os.IUserManager;
 import android.os.ParcelFileDescriptor;
@@ -105,6 +107,7 @@
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.IntArray;
+import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -246,6 +249,8 @@
                     return runStreamingInstall();
                 case "install-incremental":
                     return runIncrementalInstall();
+                case "install-archived":
+                    return runArchivedInstall();
                 case "install-abandon":
                 case "install-destroy":
                     return runInstallAbandon();
@@ -1549,6 +1554,16 @@
         return doRunInstall(params);
     }
 
+    private int runArchivedInstall() throws RemoteException {
+        final InstallParams params = makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS);
+        params.sessionParams.installFlags |= PackageManager.INSTALL_ARCHIVED;
+        if (params.sessionParams.dataLoaderParams == null) {
+            params.sessionParams.setDataLoaderParams(
+                    PackageManagerShellCommandDataLoader.getStreamingDataLoaderParams(this));
+        }
+        return doRunInstall(params);
+    }
+
     private int runIncrementalInstall() throws RemoteException {
         final InstallParams params = makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS);
         if (params.sessionParams.dataLoaderParams == null) {
@@ -1565,9 +1580,22 @@
     private int doRunInstall(final InstallParams params) throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
 
+        int requestUserId = params.userId;
+        if (requestUserId != UserHandle.USER_ALL && requestUserId != UserHandle.USER_CURRENT) {
+            UserManagerInternal umi =
+                    LocalServices.getService(UserManagerInternal.class);
+            UserInfo userInfo = umi.getUserInfo(requestUserId);
+            if (userInfo == null) {
+                pw.println("Failure [user " + requestUserId + " doesn't exist]");
+                return 1;
+            }
+        }
+
         final boolean isStreaming = params.sessionParams.dataLoaderParams != null;
         final boolean isApex =
                 (params.sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0;
+        final boolean installArchived =
+                (params.sessionParams.installFlags & PackageManager.INSTALL_ARCHIVED) != 0;
 
         ArrayList<String> args = getRemainingArgs();
 
@@ -1584,6 +1612,13 @@
             return 1;
         }
 
+        if (installArchived) {
+            if (hasSplits) {
+                pw.println("Error: can't have SPLIT(s) for Archival install");
+                return 1;
+            }
+        }
+
         if (!isStreaming) {
             if (fromStdIn && hasSplits) {
                 pw.println("Error: can't specify SPLIT(s) along with STDIN");
@@ -1602,8 +1637,8 @@
         boolean abandonSession = true;
         try {
             if (isStreaming) {
-                if (doAddFiles(sessionId, args, params.sessionParams.sizeBytes, isApex)
-                        != PackageInstaller.STATUS_SUCCESS) {
+                if (doAddFiles(sessionId, args, params.sessionParams.sizeBytes, isApex,
+                        installArchived) != PackageInstaller.STATUS_SUCCESS) {
                     return 1;
                 }
             } else {
@@ -2319,6 +2354,15 @@
                     break;
                 case "--user":
                     userId = UserHandle.parseUserArg(getNextArgRequired());
+                    if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+                        UserManagerInternal umi =
+                                LocalServices.getService(UserManagerInternal.class);
+                        UserInfo userInfo = umi.getUserInfo(userId);
+                        if (userInfo == null) {
+                            pw.println("Failure [user " + userId + " doesn't exist]");
+                            return 1;
+                        }
+                    }
                     break;
                 case "--versionCode":
                     versionCode = Long.parseLong(getNextArgRequired());
@@ -3836,7 +3880,7 @@
     }
 
     private int doAddFiles(int sessionId, ArrayList<String> args, long sessionSizeBytes,
-            boolean isApex) throws RemoteException {
+            boolean isApex, boolean installArchived) throws RemoteException {
         PackageInstaller.Session session = null;
         try {
             session = new PackageInstaller.Session(
@@ -3845,9 +3889,17 @@
             // 1. Single file from stdin.
             if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) {
                 final String name = "base" + RANDOM.nextInt() + "." + (isApex ? "apex" : "apk");
-                final Metadata metadata = Metadata.forStdIn(name);
-                session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes,
-                        metadata.toByteArray(), null);
+                final long size;
+                final Metadata metadata;
+                if (!installArchived) {
+                    metadata = Metadata.forStdIn(name);
+                    size = sessionSizeBytes;
+                } else {
+                    metadata = Metadata.forArchived(
+                            getArchivedPackage(STDIN_PATH, sessionSizeBytes));
+                    size = -1;
+                }
+                session.addFile(LOCATION_DATA_APP, name, size, metadata.toByteArray(), null);
                 return 0;
             }
 
@@ -3856,16 +3908,21 @@
 
                 if (delimLocation != -1) {
                     // 2. File with specified size read from stdin.
+                    if (installArchived) {
+                        getOutPrintWriter().println(
+                                "Error: can't install with size from STDIN for Archival install");
+                        return 1;
+                    }
                     if (processArgForStdin(arg, session) != 0) {
                         return 1;
                     }
                 } else {
                     // 3. Local file.
-                    processArgForLocalFile(arg, session);
+                    processArgForLocalFile(arg, session, installArchived);
                 }
             }
             return 0;
-        } catch (IllegalArgumentException e) {
+        } catch (IOException | IllegalArgumentException e) {
             getErrPrintWriter().println("Failed to add file(s), reason: " + e);
             getOutPrintWriter().println("Failure [failed to add file(s)]");
             return 1;
@@ -3954,26 +4011,67 @@
         }
     }
 
-    private void processArgForLocalFile(String arg, PackageInstaller.Session session) {
+    private ArchivedPackageParcel getArchivedPackage(String inPath, long sizeBytes)
+            throws RemoteException, IOException {
+        final var fdWithSize = openInFile(inPath, sizeBytes);
+        if (fdWithSize.first == null) {
+            throw new IllegalArgumentException("Error: Can't open file: " + inPath);
+        }
+
+        File tmpFile = null;
+        final ParcelFileDescriptor fd = fdWithSize.first;
+        try (InputStream inStream = new AutoCloseInputStream(fd)) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                File tmpStagingDir = Environment.getDataAppDirectory(null);
+                tmpFile = new File(tmpStagingDir, "tmdl" + RANDOM.nextInt() + ".tmp");
+
+                try (OutputStream outStream = new FileOutputStream(tmpFile)) {
+                    Streams.copy(inStream, outStream);
+                }
+
+                return mInterface.getArchivedPackage(tmpFile.getAbsolutePath());
+            } finally {
+                if (tmpFile != null) {
+                    tmpFile.delete();
+                }
+                Binder.restoreCallingIdentity(identity);
+            }
+        } catch (IOException e) {
+            throw new IllegalArgumentException("Error: Can't stage file: " + inPath, e);
+        }
+    }
+
+    private void processArgForLocalFile(String arg, PackageInstaller.Session session,
+            boolean installArchived) throws IOException, RemoteException {
         final String inPath = arg;
 
         final File file = new File(inPath);
         final String name = file.getName();
-        final long size = getFileStatSize(file);
-        final Metadata metadata = Metadata.forLocalFile(inPath);
+        final long size;
+        final Metadata metadata;
+        if (installArchived) {
+            metadata = Metadata.forArchived(getArchivedPackage(inPath, -1));
+            size = 0;
+        } else {
+            metadata = Metadata.forLocalFile(inPath);
+            size = getFileStatSize(file);
+        }
 
         byte[] v4signatureBytes = null;
-        // Try to load the v4 signature file for the APK; it might not exist.
-        final String v4SignaturePath = inPath + V4Signature.EXT;
-        final ParcelFileDescriptor pfd = openFileForSystem(v4SignaturePath, "r");
-        if (pfd != null) {
-            try {
-                final V4Signature v4signature = V4Signature.readFrom(pfd);
-                v4signatureBytes = v4signature.toByteArray();
-            } catch (IOException ex) {
-                Slog.e(TAG, "V4 signature file exists but failed to be parsed.", ex);
-            } finally {
-                IoUtils.closeQuietly(pfd);
+        if (!installArchived) {
+            // Try to load the v4 signature file for the APK; it might not exist.
+            final String v4SignaturePath = inPath + V4Signature.EXT;
+            final ParcelFileDescriptor pfd = openFileForSystem(v4SignaturePath, "r");
+            if (pfd != null) {
+                try {
+                    final V4Signature v4signature = V4Signature.readFrom(pfd);
+                    v4signatureBytes = v4signature.toByteArray();
+                } catch (IOException ex) {
+                    Slog.e(TAG, "V4 signature file exists but failed to be parsed.", ex);
+                } finally {
+                    IoUtils.closeQuietly(pfd);
+                }
             }
         }
 
@@ -3995,6 +4093,32 @@
         return 0;
     }
 
+    private Pair<ParcelFileDescriptor, Long> openInFile(String inPath, long sizeBytes)
+            throws IOException {
+        final ParcelFileDescriptor fd;
+        if (STDIN_PATH.equals(inPath)) {
+            fd = ParcelFileDescriptor.dup(getInFileDescriptor());
+        } else if (inPath != null) {
+            fd = openFileForSystem(inPath, "r");
+            if (fd == null) {
+                return Pair.create(null, -1L);
+            }
+            sizeBytes = fd.getStatSize();
+            if (sizeBytes < 0) {
+                fd.close();
+                getErrPrintWriter().println("Unable to get size of: " + inPath);
+                return Pair.create(null, -1L);
+            }
+        } else {
+            fd = ParcelFileDescriptor.dup(getInFileDescriptor());
+        }
+        if (sizeBytes <= 0) {
+            getErrPrintWriter().println("Error: must specify an APK size");
+            return Pair.create(null, 1L);
+        }
+        return Pair.create(fd, sizeBytes);
+    }
+
     private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
             boolean logSuccess) throws RemoteException {
         PackageInstaller.Session session = null;
@@ -4004,26 +4128,13 @@
 
             final PrintWriter pw = getOutPrintWriter();
 
-            final ParcelFileDescriptor fd;
-            if (STDIN_PATH.equals(inPath)) {
-                fd = ParcelFileDescriptor.dup(getInFileDescriptor());
-            } else if (inPath != null) {
-                fd = openFileForSystem(inPath, "r");
-                if (fd == null) {
-                    return -1;
-                }
-                sizeBytes = fd.getStatSize();
-                if (sizeBytes < 0) {
-                    getErrPrintWriter().println("Unable to get size of: " + inPath);
-                    return -1;
-                }
-            } else {
-                fd = ParcelFileDescriptor.dup(getInFileDescriptor());
+            final var fdWithSize = openInFile(inPath, sizeBytes);
+            if (fdWithSize.first == null) {
+                long resultCode = fdWithSize.second;
+                return (int) resultCode;
             }
-            if (sizeBytes <= 0) {
-                getErrPrintWriter().println("Error: must specify an APK size");
-                return 1;
-            }
+            final ParcelFileDescriptor fd = fdWithSize.first;
+            sizeBytes = fdWithSize.second;
 
             session.write(splitName, 0, sizeBytes, fd);
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index a1e5153..fbe5a51 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -18,9 +18,11 @@
 
 import android.annotation.NonNull;
 import android.content.ComponentName;
+import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.InstallationFile;
 import android.content.pm.PackageInstaller;
+import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.ShellCommand;
 import android.service.dataloader.DataLoaderService;
@@ -37,6 +39,7 @@
 import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
 import java.security.SecureRandom;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.concurrent.atomic.AtomicLong;
 
@@ -136,9 +139,13 @@
          * Everything streamed.
          */
         static final byte STREAMING = 3;
+        /**
+         * Archived install.
+         */
+        static final byte ARCHIVED = 4;
 
         private final byte mMode;
-        private final String mData;
+        private final byte[] mData;
         private final String mSalt;
 
         private static AtomicLong sGlobalSalt = new AtomicLong((new SecureRandom()).nextLong());
@@ -156,6 +163,21 @@
             return new Metadata(LOCAL_FILE, filePath, nextGlobalSalt().toString());
         }
 
+        /** @hide */
+        @VisibleForTesting
+        public static Metadata forArchived(ArchivedPackageParcel archivedPackage) {
+            Parcel parcel = Parcel.obtain();
+            byte[] bytes;
+            try {
+                parcel.writeParcelable(archivedPackage, 0);
+                bytes = parcel.marshall();
+            } finally {
+                parcel.recycle();
+            }
+
+            return new Metadata(ARCHIVED, bytes, null);
+        }
+
         static Metadata forDataOnlyStreaming(String fileId) {
             return new Metadata(DATA_ONLY_STREAMING, fileId);
         }
@@ -169,8 +191,12 @@
         }
 
         private Metadata(byte mode, String data, String salt) {
+            this(mode, (data != null ? data : "").getBytes(StandardCharsets.UTF_8), salt);
+        }
+
+        private Metadata(byte mode, byte[] data, String salt) {
             this.mMode = mode;
-            this.mData = (data == null) ? "" : data;
+            this.mData = data;
             this.mSalt = salt;
         }
 
@@ -181,22 +207,21 @@
             int offset = 0;
             final byte mode = bytes[offset];
             offset += 1;
-            final String data;
+            final byte[] data;
             final String salt;
             switch (mode) {
                 case LOCAL_FILE: {
                     int dataSize = ByteBuffer.wrap(bytes, offset, 4).order(
                             ByteOrder.LITTLE_ENDIAN).getInt();
                     offset += 4;
-                    data = new String(bytes, offset, dataSize, StandardCharsets.UTF_8);
+                    data = Arrays.copyOfRange(bytes, offset, offset + dataSize);
                     offset += dataSize;
                     salt = new String(bytes, offset, bytes.length - offset,
                             StandardCharsets.UTF_8);
                     break;
                 }
                 default:
-                    data = new String(bytes, offset, bytes.length - offset,
-                            StandardCharsets.UTF_8);
+                    data = Arrays.copyOfRange(bytes, offset, bytes.length);
                     salt = null;
                     break;
             }
@@ -207,7 +232,7 @@
         @VisibleForTesting
         public byte[] toByteArray() {
             final byte[] result;
-            final byte[] dataBytes = this.mData.getBytes(StandardCharsets.UTF_8);
+            final byte[] dataBytes = this.mData;
             switch (this.mMode) {
                 case LOCAL_FILE: {
                     int dataSize = dataBytes.length;
@@ -237,9 +262,26 @@
             return this.mMode;
         }
 
-        String getData() {
+        byte[] getData() {
             return this.mData;
         }
+
+        ArchivedPackageParcel getArchivedPackage() {
+            if (getMode() != ARCHIVED) {
+                throw new IllegalStateException("Not an archived package metadata.");
+            }
+
+            Parcel parcel = Parcel.obtain();
+            ArchivedPackageParcel result;
+            try {
+                parcel.unmarshall(this.mData, 0, this.mData.length);
+                parcel.setDataPosition(0);
+                result = parcel.readParcelable(ArchivedPackageParcel.class.getClassLoader());
+            } finally {
+                parcel.recycle();
+            }
+            return result;
+        }
     }
 
     private static class DataLoader implements DataLoaderService.DataLoader {
@@ -278,7 +320,9 @@
                         case Metadata.LOCAL_FILE: {
                             ParcelFileDescriptor incomingFd = null;
                             try {
-                                incomingFd = getLocalFilePFD(shellCommand, metadata.getData());
+                                final String filePath = new String(metadata.getData(),
+                                        StandardCharsets.UTF_8);
+                                incomingFd = getLocalFilePFD(shellCommand, filePath);
                                 mConnector.writeData(file.getName(), 0, incomingFd.getStatSize(),
                                         incomingFd);
                             } finally {
@@ -286,6 +330,10 @@
                             }
                             break;
                         }
+                        case Metadata.ARCHIVED: {
+                            // Do nothing, metadata already contains everything needed for install.
+                            break;
+                        }
                         default:
                             Slog.e(TAG, "Unsupported metadata mode: " + metadata.getMode());
                             return false;
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 2d58fe5..c1580c4b 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -51,13 +51,15 @@
     public static final int STEP_RECONCILE = 3;
     public static final int STEP_COMMIT = 4;
     public static final int STEP_DEXOPT = 5;
+    public static final int STEP_FREEZE_INSTALL = 6;
 
     @IntDef(prefix = {"STEP_"}, value = {
             STEP_PREPARE,
             STEP_SCAN,
             STEP_RECONCILE,
             STEP_COMMIT,
-            STEP_DEXOPT
+            STEP_DEXOPT,
+            STEP_FREEZE_INSTALL
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface StepInt {
@@ -109,10 +111,17 @@
 
         long versionCode = 0, apksSize = 0;
         if (success) {
-            final PackageSetting ps = mInstallRequest.getScannedPackageSetting();
-            if (ps != null) {
-                versionCode = ps.getVersionCode();
-                apksSize = getApksSize(ps.getPath());
+            // TODO: Remove temp try-catch to avoid IllegalStateException. The reason is because
+            //  the scan result is null for installExistingPackageAsUser(). Because it's installing
+            //  a package that's already existing, there's no scanning or parsing involved
+            try {
+                final PackageSetting ps = mInstallRequest.getScannedPackageSetting();
+                if (ps != null) {
+                    versionCode = ps.getVersionCode();
+                    apksSize = getApksSize(ps.getPath());
+                }
+            } catch (IllegalStateException e) {
+                // no-op
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/PrepareFailure.java b/services/core/java/com/android/server/pm/PrepareFailure.java
index 3180bac..09cb6b9 100644
--- a/services/core/java/com/android/server/pm/PrepareFailure.java
+++ b/services/core/java/com/android/server/pm/PrepareFailure.java
@@ -42,7 +42,8 @@
     }
 
     PrepareFailure(String message, Exception e) {
-        super(((PackageManagerException) e).error,
+        super(e instanceof PackageManagerException ? ((PackageManagerException) e).error
+                        : PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
                 ExceptionUtils.getCompleteMessage(message, e));
     }
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 307867c..f740cf4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2916,11 +2916,7 @@
 
             StringBuilder sb = new StringBuilder();
             for (final PackageSetting ps : mPackages.values()) {
-                // TODO(b/135203078): This doesn't handle multiple users
-                final String dataPath = PackageInfoUtils.getDataDir(ps, UserHandle.USER_SYSTEM)
-                        .getAbsolutePath();
-
-                if (ps.getPkg() == null || dataPath == null) {
+                if (ps.getPkg() == null) {
                     if (!"android".equals(ps.getPackageName())) {
                         Slog.w(TAG, "Skipping " + ps + " due to missing metadata");
                     }
@@ -2932,6 +2928,10 @@
                     continue;
                 }
 
+                // TODO(b/135203078): This doesn't handle multiple users
+                final File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.USER_SYSTEM);
+                final String dataPath = dataDir == null ? "null" : dataDir.getAbsolutePath();
+
                 final boolean isDebug = ps.getPkg().isDebuggable();
                 final IntArray gids = new IntArray();
                 for (final int userId : userIds) {
@@ -2973,7 +2973,7 @@
                 sb.append(ps.getSeInfo());
                 sb.append(" ");
                 final int gidsSize = gids.size();
-                if (gids != null && gids.size() > 0) {
+                if (gids.size() > 0) {
                     sb.append(gids.get(0));
                     for (int i = 1; i < gidsSize; i++) {
                         sb.append(",");
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index d32eb22..e9c6aab 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -363,14 +363,14 @@
 
     private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi,
             boolean forBackup) throws IOException, XmlPullParserException {
-        spi.waitForBitmapSaves();
         if (forBackup) {
             if (spi.getPackageUserId() != spi.getOwnerUserId()) {
                 return; // Don't save cross-user information.
             }
+            spi.waitForBitmapSaves();
             spi.saveToXml(out, forBackup);
         } else {
-            spi.saveShortcutPackageItem();
+            spi.scheduleSave();
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 6f0fe63..db5b9b1 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -155,7 +155,8 @@
 
         for (PackageStateInternal ps : packages) {
             freezers.add(mPm.freezePackage(ps.getPackageName(), UserHandle.USER_ALL,
-                    "loadPrivatePackagesInner", ApplicationExitInfo.REASON_OTHER));
+                    "loadPrivatePackagesInner", ApplicationExitInfo.REASON_OTHER,
+                    null /* request */));
             synchronized (mPm.mInstallLock) {
                 final AndroidPackage pkg;
                 try {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f2797eb..259b207 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4787,11 +4787,14 @@
         // default check is for DISALLOW_ADD_USER
         // If new user is of type CLONE, check if creation of clone profile is allowed
         // If new user is of type MANAGED, check if creation of managed profile is allowed
+        // If new user is of type PRIVATE, check if creation of private profile is allowed
         String restriction = UserManager.DISALLOW_ADD_USER;
         if (UserManager.isUserTypeCloneProfile(userType)) {
             restriction = UserManager.DISALLOW_ADD_CLONE_PROFILE;
         } else if (UserManager.isUserTypeManagedProfile(userType)) {
             restriction = UserManager.DISALLOW_ADD_MANAGED_PROFILE;
+        } else if (UserManager.isUserTypePrivateProfile(userType)) {
+            restriction = UserManager.DISALLOW_ADD_PRIVATE_PROFILE;
         }
 
         enforceUserRestriction(restriction, UserHandle.getCallingUserId(),
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 4e2ceab..35861d7 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -103,6 +103,7 @@
             UserManager.DISALLOW_ADD_USER,
             UserManager.DISALLOW_ADD_MANAGED_PROFILE,
             UserManager.DISALLOW_ADD_CLONE_PROFILE,
+            UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
             UserManager.ENSURE_VERIFY_APPS,
             UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
             UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
@@ -212,7 +213,8 @@
     private static final Set<String> IMMUTABLE_BY_OWNERS = Sets.newArraySet(
             UserManager.DISALLOW_RECORD_AUDIO,
             UserManager.DISALLOW_WALLPAPER,
-            UserManager.DISALLOW_OEM_UNLOCK
+            UserManager.DISALLOW_OEM_UNLOCK,
+            UserManager.DISALLOW_ADD_PRIVATE_PROFILE
     );
 
     /**
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index b7f9aaf..85b60a0 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -51,6 +51,7 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
 
@@ -284,9 +285,9 @@
                 .setBadgeLabels(
                         com.android.internal.R.string.private_profile_label_badge)
                 .setBadgeColors(
-                        com.android.internal.R.color.system_accent1_900)
+                        R.color.black)
                 .setDarkThemeBadgeColors(
-                        com.android.internal.R.color.system_accent1_900)
+                        R.color.white)
                 .setDefaultRestrictions(getDefaultProfileRestrictions())
                 .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
                 .setDefaultUserProperties(new UserProperties.Builder()
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 3a1fd7c..8c73ce8 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -191,7 +191,7 @@
         // Perform package verification and enable rollback (unless we are simply moving the
         // package).
         if (!mOriginInfo.mExisting) {
-            if (!isApex()) {
+            if (!isApex() && !isArchivedInstallation()) {
                 // TODO(b/182426975): treat APEX as APK when APK verification is concerned
                 sendApkVerificationRequest(pkgLite);
             }
@@ -896,6 +896,9 @@
     public boolean isApex() {
         return (mInstallFlags & PackageManager.INSTALL_APEX) != 0;
     }
+    public boolean isArchivedInstallation() {
+        return (mInstallFlags & PackageManager.INSTALL_ARCHIVED) != 0;
+    }
     public boolean isStaged() {
         return mIsStaged;
     }
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index f5ba3f6..d82a500 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -22,6 +22,7 @@
 import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
@@ -183,6 +184,23 @@
     }
 
     /**
+     * Creates a ParsedPackage from PackageLite without any additional parsing or processing.
+     * Most fields will get reasonable default values, corresponding to "deleted-keep-data".
+     */
+    @AnyThread
+    public ParsedPackage parsePackageFromPackageLite(PackageLite packageLite, int flags)
+            throws PackageManagerException {
+        ParseInput input = mSharedResult.get().reset();
+        ParseResult<ParsingPackage> result = parsingUtils.parsePackageFromPackageLite(input,
+                packageLite, flags);
+        if (result.isError()) {
+            throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
+                    result.getException());
+        }
+        return result.getResult().hideAsParsed();
+    }
+
+    /**
      * Removes the cached value for the thread the parser was created on. It is assumed that
      * any threads created for parallel parsing will be created and released, so they don't
      * need an explicit close call.
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index f1f0fa3..699ccbd 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -375,6 +375,10 @@
 
     ParsingPackage setBaseRevisionCode(int baseRevisionCode);
 
+    ParsingPackage setVersionCode(int vesionCode);
+
+    ParsingPackage setVersionCodeMajor(int vesionCodeMajor);
+
     ParsingPackage setVersionName(String versionName);
 
     ParsingPackage setCompileSdkVersion(int compileSdkVersion);
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index dc022f7..2d55b9f 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -474,15 +474,121 @@
         }
     }
 
-    private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
-            String codePath, SplitAssetLoader assetLoader, int flags) {
-        final String apkPath = apkFile.getAbsolutePath();
+    /**
+     * Creates ParsingPackage using only PackageLite.
+     * Missing fields will contain reasonable defaults.
+     * Used for packageless (aka archived) package installation.
+     */
+    public ParseResult<ParsingPackage> parsePackageFromPackageLite(ParseInput input,
+            PackageLite lite, int flags) {
+        final String volumeUuid = getVolumeUuid(lite.getPath());
+        final String pkgName = lite.getPackageName();
 
+        final TypedArray manifestArray = null;
+        final ParsingPackage pkg = mCallback.startParsingPackage(pkgName,
+                lite.getBaseApkPath(), lite.getPath(), manifestArray, lite.isCoreApp());
+
+        final int targetSdk = lite.getTargetSdk();
+        final String versionName = null;
+        final int compileSdkVersion = 0;
+        final String compileSdkVersionCodeName = null;
+        final boolean isolatedSplitLoading = false;
+
+        // Normally set from manifestArray.
+        pkg.setVersionCode(lite.getVersionCode());
+        pkg.setVersionCodeMajor(lite.getVersionCodeMajor());
+        pkg.setBaseRevisionCode(lite.getBaseRevisionCode());
+        pkg.setVersionName(versionName);
+        pkg.setCompileSdkVersion(compileSdkVersion);
+        pkg.setCompileSdkVersionCodeName(compileSdkVersionCodeName);
+        pkg.setIsolatedSplitLoading(isolatedSplitLoading);
+        pkg.setTargetSdkVersion(targetSdk);
+
+        // parseBaseApkTags
+        pkg.setInstallLocation(lite.getInstallLocation())
+                .setTargetSandboxVersion(PARSE_DEFAULT_TARGET_SANDBOX)
+                /* Set the global "on SD card" flag */
+                .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
+
+        // parseBaseAppBasicFlags
+        pkg
+                // Default true
+                .setBackupAllowed(lite.isBackupAllowed())
+                .setClearUserDataAllowed(lite.isClearUserDataAllowed())
+                .setClearUserDataOnFailedRestoreAllowed(
+                        lite.isClearUserDataOnFailedRestoreAllowed())
+                .setAllowNativeHeapPointerTagging(true)
+                .setEnabled(true)
+                .setExtractNativeLibrariesRequested(true)
+                // targetSdkVersion gated
+                .setAllowAudioPlaybackCapture(targetSdk >= Build.VERSION_CODES.Q)
+                .setHardwareAccelerated(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+                .setRequestLegacyExternalStorage(lite.isRequestLegacyExternalStorage())
+                .setCleartextTrafficAllowed(targetSdk < Build.VERSION_CODES.P)
+                // Default false
+                .setDefaultToDeviceProtectedStorage(lite.isDefaultToDeviceProtectedStorage())
+                .setUserDataFragile(lite.isUserDataFragile())
+                // Ints
+                .setCategory(ApplicationInfo.CATEGORY_UNDEFINED)
+                // Floats Default 0f
+                .setMaxAspectRatio(0f)
+                .setMinAspectRatio(0f);
+
+        // No APK - no code.
+        pkg.setDeclaredHavingCode(false);
+
+        final String taskAffinity = null;
+        ParseResult<String> taskAffinityResult = ComponentParseUtils.buildTaskAffinityName(
+                pkgName, pkgName, taskAffinity, input);
+        if (taskAffinityResult.isError()) {
+            return input.error(taskAffinityResult);
+        }
+        pkg.setTaskAffinity(taskAffinityResult.getResult());
+
+        final CharSequence pname = null;
+        ParseResult<String> processNameResult = ComponentParseUtils.buildProcessName(
+                pkgName, null /*defProc*/, pname, flags, mSeparateProcesses, input);
+        if (processNameResult.isError()) {
+            return input.error(processNameResult);
+        }
+        pkg.setProcessName(processNameResult.getResult());
+
+        pkg.setGwpAsanMode(-1);
+        pkg.setMemtagMode(-1);
+
+        afterParseBaseApplication(pkg);
+
+        final ParseResult<ParsingPackage> result = validateBaseApkTags(input, pkg);
+        if (result.isError()) {
+            return result;
+        }
+
+        pkg.setVolumeUuid(volumeUuid);
+
+        if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
+            pkg.setSigningDetails(lite.getSigningDetails());
+        } else {
+            pkg.setSigningDetails(SigningDetails.UNKNOWN);
+        }
+
+        return input.success(pkg
+                .set32BitAbiPreferred(lite.isUse32bitAbi()));
+    }
+
+    private static String getVolumeUuid(final String apkPath) {
         String volumeUuid = null;
         if (apkPath.startsWith(MNT_EXPAND)) {
             final int end = apkPath.indexOf('/', MNT_EXPAND.length());
             volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
         }
+        return volumeUuid;
+    }
+
+    private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
+            String codePath, SplitAssetLoader assetLoader, int flags) {
+        final String apkPath = apkFile.getAbsolutePath();
+
+        final String volumeUuid = getVolumeUuid(apkPath);
 
         if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
 
@@ -882,7 +988,7 @@
         }
 
         pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
-                R.styleable.AndroidManifest_installLocation, sa))
+                        R.styleable.AndroidManifest_installLocation, sa))
                 .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,
                         R.styleable.AndroidManifest_targetSandboxVersion, sa))
                 /* Set the global "on SD card" flag */
@@ -932,6 +1038,10 @@
             }
         }
 
+        return validateBaseApkTags(input, pkg);
+    }
+
+    private ParseResult<ParsingPackage> validateBaseApkTags(ParseInput input, ParsingPackage pkg) {
         if (!ParsedAttributionUtils.isCombinationValid(pkg.getAttributions())) {
             return input.error(
                     INSTALL_PARSE_FAILED_BAD_MANIFEST,
@@ -2199,15 +2309,19 @@
             pkg.sortServices();
         }
 
-        // Must be run after the entire {@link ApplicationInfo} has been fully processed and after
-        // every activity info has had a chance to set it from its attributes.
+        afterParseBaseApplication(pkg);
+
+        return input.success(pkg);
+    }
+
+    // Must be run after the entire {@link ApplicationInfo} has been fully processed and after
+    // every activity info has had a chance to set it from its attributes.
+    private void afterParseBaseApplication(ParsingPackage pkg) {
         setMaxAspectRatio(pkg);
         setMinAspectRatio(pkg);
         setSupportsSizeChanges(pkg);
 
         pkg.setHasDomainUrls(hasDomainURLs(pkg));
-
-        return input.success(pkg);
     }
 
     /**
diff --git a/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java b/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java
index 65325c2..7c4d787 100644
--- a/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java
+++ b/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java
@@ -22,6 +22,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import java.util.NoSuchElementException;
+
 public class BootControlHIDL implements IBootControl {
     private static final String TAG = "BootControlHIDL";
 
@@ -32,7 +34,7 @@
     public static boolean isServicePresent() {
         try {
             android.hardware.boot.V1_0.IBootControl.getService(true);
-        } catch (RemoteException e) {
+        } catch (RemoteException | NoSuchElementException e) {
             return false;
         }
         return true;
@@ -41,7 +43,7 @@
     public static boolean isV1_2ServicePresent() {
         try {
             android.hardware.boot.V1_2.IBootControl.getService(true);
-        } catch (RemoteException e) {
+        } catch (RemoteException | NoSuchElementException e) {
             return false;
         }
         return true;
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 9529621..3aed6e3 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -27,10 +27,12 @@
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.UserHandle;
+import android.os.storage.StorageManagerInternal;
 import android.security.IFileIntegrityService;
 import android.util.Slog;
 
@@ -54,6 +56,7 @@
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Objects;
 
 /**
  * A {@link SystemService} that provides file integrity related operations.
@@ -112,7 +115,7 @@
                     .exec(this, in, out, err, args, callback, resultReceiver);
         }
 
-        private void checkCallerPermission(String packageName) {
+        private void checkCallerPackageName(String packageName) {
             final int callingUid = Binder.getCallingUid();
             final int callingUserId = UserHandle.getUserId(callingUid);
             final PackageManagerInternal packageManager =
@@ -123,7 +126,10 @@
                 throw new SecurityException(
                         "Calling uid " + callingUid + " does not own package " + packageName);
             }
+        }
 
+        private void checkCallerPermission(String packageName) {
+            checkCallerPackageName(packageName);
             if (getContext().checkCallingPermission(android.Manifest.permission.INSTALL_PACKAGES)
                     == PackageManager.PERMISSION_GRANTED) {
                 return;
@@ -131,12 +137,43 @@
 
             final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
             final int mode = appOpsManager.checkOpNoThrow(
-                    AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, callingUid, packageName);
+                    AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, Binder.getCallingUid(), packageName);
             if (mode != AppOpsManager.MODE_ALLOWED) {
                 throw new SecurityException(
                         "Caller should have INSTALL_PACKAGES or REQUEST_INSTALL_PACKAGES");
             }
         }
+
+        @Override
+        public android.os.IInstalld.IFsveritySetupAuthToken createAuthToken(
+                ParcelFileDescriptor authFd) throws RemoteException {
+            Objects.requireNonNull(authFd);
+            try {
+                var authToken = getStorageManagerInternal().createFsveritySetupAuthToken(authFd,
+                        Binder.getCallingUid(), Binder.getCallingUserHandle().getIdentifier());
+                // fs-verity setup requires no writable fd to the file. Release the dup now that
+                // it's passed.
+                authFd.close();
+                return authToken;
+            } catch (IOException e) {
+                throw new RemoteException(e);
+            }
+        }
+
+        @Override
+        public int setupFsverity(android.os.IInstalld.IFsveritySetupAuthToken authToken,
+                String filePath, String packageName) throws RemoteException {
+            Objects.requireNonNull(authToken);
+            Objects.requireNonNull(filePath);
+            Objects.requireNonNull(packageName);
+            checkCallerPackageName(packageName);
+
+            try {
+                return getStorageManagerInternal().enableFsverity(authToken, filePath, packageName);
+            } catch (IOException e) {
+                throw new RemoteException(e);
+            }
+        }
     };
 
     public FileIntegrityService(final Context context) {
@@ -146,9 +183,19 @@
         } catch (CertificateException e) {
             Slog.wtf(TAG, "Cannot get an instance of X.509 certificate factory");
         }
+
         LocalServices.addService(FileIntegrityService.class, this);
     }
 
+    /**
+     * Returns StorageManagerInternal as a proxy to fs-verity related calls. This is to plumb
+     * the call through the canonical Installer instance in StorageManagerService, since the
+     * Installer instance isn't directly accessible.
+     */
+    private StorageManagerInternal getStorageManagerInternal() {
+        return LocalServices.getService(StorageManagerInternal.class);
+    }
+
     @Override
     public void onStart() {
         loadAllCertificates();
diff --git a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
index f744d00..565eb6e 100644
--- a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
+++ b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
@@ -18,7 +18,6 @@
 
 import static android.hardware.SensorManager.SENSOR_DELAY_NORMAL;
 
-import android.annotation.ColorInt;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.hardware.Sensor;
@@ -39,6 +38,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.server.FgThread;
 
 import java.util.ArrayDeque;
@@ -48,12 +48,10 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
-
 class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener,
         SensorEventListener {
 
-    @VisibleForTesting
-    static final double LIGHT_VALUE_MULTIPLIER = 1 / Math.log(1.1);
+    private static final double LIGHT_VALUE_MULTIPLIER = 1 / Math.log(1.1);
 
     private final Handler mHandler;
     private final Executor mExecutor;
@@ -69,11 +67,6 @@
 
     private LightsManager.LightsSession mLightsSession = null;
 
-    @ColorInt
-    private final int mDayColor;
-    @ColorInt
-    private final int mNightColor;
-
     private final Sensor mLightSensor;
 
     private boolean mIsAmbientLightListenerRegistered = false;
@@ -81,7 +74,9 @@
     /** When average of the time integral over the past {@link #mMovingAverageIntervalMillis}
      *  milliseconds of the log_1.1(lux(t)) is greater than this value, use the daytime brightness
      *  else use nighttime brightness. */
-    private final long mNightThreshold;
+    private final long[] mThresholds;
+
+    private final int[] mColors;
     private final ArrayDeque<Pair<Long, Integer>> mAmbientLightValues = new ArrayDeque<>();
     /** Tracks the Riemann sum of {@link #mAmbientLightValues} to avoid O(n) operations when sum is
      *  needed */
@@ -101,6 +96,20 @@
 
     @VisibleForTesting
     CameraPrivacyLightController(Context context, Looper looper) {
+        mColors = context.getResources().getIntArray(R.array.config_cameraPrivacyLightColors);
+        if (ArrayUtils.isEmpty(mColors)) {
+            mHandler = null;
+            mExecutor = null;
+            mContext = null;
+            mAppOpsManager = null;
+            mLightsManager = null;
+            mSensorManager = null;
+            mLightSensor = null;
+            mMovingAverageIntervalMillis = 0;
+            mThresholds = null;
+            // Return here before this class starts interacting with other services.
+            return;
+        }
         mContext = context;
 
         mHandler = new Handler(looper);
@@ -109,14 +118,20 @@
         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
         mLightsManager = mContext.getSystemService(LightsManager.class);
         mSensorManager = mContext.getSystemService(SensorManager.class);
-
-        mDayColor = mContext.getColor(R.color.camera_privacy_light_day);
-        mNightColor = mContext.getColor(R.color.camera_privacy_light_night);
         mMovingAverageIntervalMillis = mContext.getResources()
                 .getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis);
-        mNightThreshold = (long) (Math.log(mContext.getResources()
-                .getInteger(R.integer.config_cameraPrivacyLightAlsNightThreshold))
-                * LIGHT_VALUE_MULTIPLIER);
+        int[] thresholdsLux = mContext.getResources().getIntArray(
+                R.array.config_cameraPrivacyLightAlsLuxThresholds);
+        if (thresholdsLux.length != mColors.length - 1) {
+            throw new IllegalStateException("There must be exactly one more color than thresholds."
+                    + " Found " + mColors.length + " colors and " + thresholdsLux.length
+                    + " thresholds.");
+        }
+        mThresholds = new long[thresholdsLux.length];
+        for (int i = 0; i < thresholdsLux.length; i++) {
+            int luxValue = thresholdsLux[i];
+            mThresholds[i] = (long) (Math.log(luxValue) * LIGHT_VALUE_MULTIPLIER);
+        }
 
         List<Light> lights = mLightsManager.getLights();
         for (int i = 0; i < lights.size(); i++) {
@@ -223,13 +238,8 @@
             mLightsSession.close();
             mLightsSession = null;
         } else {
-            int lightColor;
-            if (mLightSensor != null && getLiveAmbientLightTotal()
-                    < getCurrentIntervalMillis() * mNightThreshold) {
-                lightColor = mNightColor;
-            } else {
-                lightColor = mDayColor;
-            }
+            int lightColor =
+                    mLightSensor == null ? mColors[mColors.length - 1] : computeCurrentLightColor();
 
             if (mLastLightColor == lightColor && mLightsSession != null) {
                 return;
@@ -252,6 +262,18 @@
         }
     }
 
+    private int computeCurrentLightColor() {
+        long liveAmbientLightTotal = getLiveAmbientLightTotal();
+        long currentInterval = getCurrentIntervalMillis();
+
+        for (int i = 0; i < mThresholds.length; i++) {
+            if (liveAmbientLightTotal < currentInterval * mThresholds[i]) {
+                return mColors[i];
+            }
+        }
+        return mColors[mColors.length - 1];
+    }
+
     private void updateSensorListener(boolean shouldSessionEnd) {
         if (shouldSessionEnd && mIsAmbientLightListenerRegistered) {
             mSensorManager.unregisterListener(this);
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 415d05e..0043122 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -120,9 +120,6 @@
     private final BroadcastReceiver mTrustableDowngradeReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (!TrustManagerService.ENABLE_ACTIVE_UNLOCK_FLAG) {
-                return;
-            }
             // are these the broadcasts we want to listen to
             if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
                 downgradeToTrustable();
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index bed69fc..cc95da5 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -58,7 +58,6 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -165,10 +164,6 @@
     @GuardedBy("mUserIsTrusted")
     private final SparseBooleanArray mUserIsTrusted = new SparseBooleanArray();
 
-    //TODO(b/215724686): remove flag
-    public static final boolean ENABLE_ACTIVE_UNLOCK_FLAG = SystemProperties.getBoolean(
-            "fw.enable_active_unlock_flag", true);
-
     private enum TrustState {
         UNTRUSTED, // the phone is not unlocked by any trustagents
         TRUSTABLE, // the phone is in a semi-locked state that can be unlocked if
@@ -569,69 +564,6 @@
             int flags,
             boolean isFromUnlock,
             @Nullable AndroidFuture<GrantTrustResult> resultCallback) {
-        if (ENABLE_ACTIVE_UNLOCK_FLAG) {
-            updateTrustWithRenewableUnlock(userId, flags, isFromUnlock, resultCallback);
-        } else {
-            updateTrustWithNonrenewableTrust(userId, flags, isFromUnlock);
-        }
-    }
-
-    private void updateTrustWithNonrenewableTrust(int userId, int flags, boolean isFromUnlock) {
-        boolean managed = aggregateIsTrustManaged(userId);
-        dispatchOnTrustManagedChanged(managed, userId);
-        if (mStrongAuthTracker.isTrustAllowedForUser(userId)
-                && isTrustUsuallyManagedInternal(userId) != managed) {
-            updateTrustUsuallyManaged(userId, managed);
-        }
-
-        boolean trusted = aggregateIsTrusted(userId);
-        IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
-        boolean showingKeyguard = true;
-        try {
-            showingKeyguard = wm.isKeyguardLocked();
-        } catch (RemoteException e) {
-        }
-
-        boolean changed;
-        synchronized (mUserIsTrusted) {
-            if (mSettingsObserver.getTrustAgentsNonrenewableTrust()) {
-                // For non-renewable trust agents can only set the device to trusted if it already
-                // trusted or the device is unlocked. Attempting to set the device as trusted
-                // when the device is locked will be ignored.
-                changed = mUserIsTrusted.get(userId) != trusted;
-                trusted = trusted
-                        && (!showingKeyguard || isFromUnlock || !changed)
-                        && userId == mCurrentUser;
-                if (DEBUG) {
-                    Slog.d(TAG, "Extend unlock setting trusted as " + Boolean.toString(trusted)
-                            + " && " + Boolean.toString(!showingKeyguard)
-                            + " && " + Boolean.toString(userId == mCurrentUser));
-                }
-            }
-            changed = mUserIsTrusted.get(userId) != trusted;
-            mUserIsTrusted.put(userId, trusted);
-        }
-        dispatchOnTrustChanged(
-                trusted,
-                false /* newlyUnlocked */,
-                userId,
-                flags,
-                getTrustGrantedMessages(userId));
-        if (changed) {
-            refreshDeviceLockedForUser(userId);
-            if (!trusted) {
-                maybeLockScreen(userId);
-            } else {
-                scheduleTrustTimeout(false /* override */, false /* isTrustableTimeout*/);
-            }
-        }
-    }
-
-    private void updateTrustWithRenewableUnlock(
-            int userId,
-            int flags,
-            boolean isFromUnlock,
-            @Nullable AndroidFuture<GrantTrustResult> resultCallback) {
         boolean managed = aggregateIsTrustManaged(userId);
         dispatchOnTrustManagedChanged(managed, userId);
         if (mStrongAuthTracker.isTrustAllowedForUser(userId)
@@ -2265,16 +2197,11 @@
 
         @Override
         public void handleAlarm() {
-            TrustableTimeoutAlarmListener otherAlarm;
-            boolean otherAlarmPresent;
-            if (ENABLE_ACTIVE_UNLOCK_FLAG) {
-                otherAlarm = mTrustableTimeoutAlarmListenerForUser.get(mUserId);
-                otherAlarmPresent = (otherAlarm != null) && otherAlarm.isQueued();
-                if (otherAlarmPresent) {
-                    synchronized (mAlarmLock) {
-                        disableNonrenewableTrustWhileRenewableTrustIsPresent();
-                    }
-                    return;
+            TrustableTimeoutAlarmListener otherAlarm =
+                    mTrustableTimeoutAlarmListenerForUser.get(mUserId);
+            if (otherAlarm != null && otherAlarm.isQueued()) {
+                synchronized (mAlarmLock) {
+                    disableNonrenewableTrustWhileRenewableTrustIsPresent();
                 }
             }
         }
@@ -2299,17 +2226,11 @@
 
         @Override
         public void handleAlarm() {
-            TrustedTimeoutAlarmListener otherAlarm;
-            boolean otherAlarmPresent;
-            if (ENABLE_ACTIVE_UNLOCK_FLAG) {
-                cancelBothTrustableAlarms(mUserId);
-                otherAlarm = mTrustTimeoutAlarmListenerForUser.get(mUserId);
-                otherAlarmPresent = (otherAlarm != null) && otherAlarm.isQueued();
-                if (otherAlarmPresent) {
-                    synchronized (mAlarmLock) {
-                        disableRenewableTrustWhileNonrenewableTrustIsPresent();
-                    }
-                    return;
+            cancelBothTrustableAlarms(mUserId);
+            TrustedTimeoutAlarmListener otherAlarm = mTrustTimeoutAlarmListenerForUser.get(mUserId);
+            if (otherAlarm != null && otherAlarm.isQueued()) {
+                synchronized (mAlarmLock) {
+                    disableRenewableTrustWhileNonrenewableTrustIsPresent();
                 }
             }
         }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6a2b824..2559b84 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -41,6 +41,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
@@ -94,6 +95,7 @@
 import android.view.InputChannel;
 import android.view.Surface;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
@@ -177,6 +179,11 @@
 
     private final ActivityManager mActivityManager;
 
+    private boolean mExternalInputLoggingDisplayNameFilterEnabled = false;
+    private final HashSet<String> mExternalInputLoggingDeviceOnScreenDisplayNames =
+            new HashSet<String>();
+    private final List<String> mExternalInputLoggingDeviceBrandNames = new ArrayList<String>();
+
     public TvInputManagerService(Context context) {
         super(context);
 
@@ -192,6 +199,8 @@
         synchronized (mLock) {
             getOrCreateUserStateLocked(mCurrentUserId);
         }
+
+        initExternalInputLoggingConfigs();
     }
 
     @Override
@@ -224,6 +233,21 @@
         }
     }
 
+    private void initExternalInputLoggingConfigs() {
+        mExternalInputLoggingDisplayNameFilterEnabled = mContext.getResources().getBoolean(
+                R.bool.config_tvExternalInputLoggingDisplayNameFilterEnabled);
+        if (!mExternalInputLoggingDisplayNameFilterEnabled) {
+            return;
+        }
+        final String[] deviceOnScreenDisplayNames = mContext.getResources().getStringArray(
+                R.array.config_tvExternalInputLoggingDeviceOnScreenDisplayNames);
+        final String[] deviceBrandNames = mContext.getResources().getStringArray(
+                R.array.config_tvExternalInputLoggingDeviceBrandNames);
+        mExternalInputLoggingDeviceOnScreenDisplayNames.addAll(
+                Arrays.asList(deviceOnScreenDisplayNames));
+        mExternalInputLoggingDeviceBrandNames.addAll(Arrays.asList(deviceBrandNames));
+    }
+
     private void registerBroadcastReceivers() {
         PackageMonitor monitor = new PackageMonitor() {
             private void buildTvInputList(String[] packages) {
@@ -3073,6 +3097,9 @@
                 hdmiPort = hdmiDeviceInfo.getPortId();
                 if (hdmiDeviceInfo.isCecDevice()) {
                     displayName = hdmiDeviceInfo.getDisplayName();
+                    if (mExternalInputLoggingDisplayNameFilterEnabled) {
+                        displayName = filterExternalInputLoggingDisplayName(displayName);
+                    }
                     vendorId = hdmiDeviceInfo.getVendorId();
                 }
             }
@@ -3082,6 +3109,22 @@
                 inputType, vendorId, hdmiPort, tifSessionId, displayName);
     }
 
+    private String filterExternalInputLoggingDisplayName(String displayName) {
+        String nullDisplayName = "NULL_DISPLAY_NAME", filteredDisplayName = "FILTERED_DISPLAY_NAME";
+        if (displayName == null) {
+            return nullDisplayName;
+        }
+        if (mExternalInputLoggingDeviceOnScreenDisplayNames.contains(displayName)) {
+            return displayName;
+        }
+        for (String brandName : mExternalInputLoggingDeviceBrandNames) {
+            if (displayName.toUpperCase().contains(brandName.toUpperCase())) {
+                return brandName;
+            }
+        }
+        return filteredDisplayName;
+    }
+
     private static final class UserState {
         // A mapping from the TV input id to its TvInputState.
         private Map<String, TvInputState> inputMap = new HashMap<>();
@@ -3429,6 +3472,10 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
+                    ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
+                    if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) {
+                        return;
+                    }
                     mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
                     addHardwareInputLocked(inputInfo);
                 }
@@ -3443,6 +3490,10 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
+                    ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
+                    if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) {
+                        return;
+                    }
                     mTvInputHardwareManager.addHdmiInput(id, inputInfo);
                     addHardwareInputLocked(inputInfo);
                     if (mOnScreenInputId != null && mOnScreenSessionState != null) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 598c1ae..ddc0519 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2000,7 +2000,12 @@
             WallpaperData wallpaper, IRemoteCallback reply, ServiceInfo serviceInfo) {
 
         if (serviceInfo == null) {
-            clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply);
+            if (wallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)) {
+                clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, null);
+                clearWallpaperLocked(FLAG_LOCK, wallpaper.userId, reply);
+            } else {
+                clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply);
+            }
             return;
         }
         Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked");
@@ -2032,7 +2037,7 @@
         WallpaperData data = null;
         synchronized (mLock) {
             if (mIsLockscreenLiveWallpaperEnabled) {
-                clearWallpaperLocked(callingPackage, which, userId, null);
+                clearWallpaperLocked(callingPackage, which, userId);
             } else {
                 clearWallpaperLocked(which, userId, null);
             }
@@ -2052,8 +2057,7 @@
         }
     }
 
-    private void clearWallpaperLocked(String callingPackage, int which, int userId,
-            IRemoteCallback reply) {
+    private void clearWallpaperLocked(String callingPackage, int which, int userId) {
 
         // Might need to bring it in the first time to establish our rewrite
         if (!mWallpaperMap.contains(userId)) {
@@ -2092,8 +2096,8 @@
                 finalWhich = which;
             }
 
-            boolean success = withCleanCallingIdentity(() -> setWallpaperComponentInternal(
-                    component, callingPackage, finalWhich, userId, reply));
+            boolean success = withCleanCallingIdentity(() -> setWallpaperComponent(
+                    component, callingPackage, finalWhich, userId));
             if (success) return;
         } catch (IllegalArgumentException e1) {
             e = e1;
@@ -2105,23 +2109,10 @@
         // wallpaper.
         Slog.e(TAG, "Default wallpaper component not found!", e);
         withCleanCallingIdentity(() -> clearWallpaperComponentLocked(wallpaper));
-        if (reply != null) {
-            try {
-                reply.sendResult(null);
-            } catch (RemoteException e1) {
-                Slog.w(TAG, "Failed to notify callback after wallpaper clear", e1);
-            }
-        }
     }
 
+    // TODO(b/266818039) remove this version of the method
     private void clearWallpaperLocked(int which, int userId, IRemoteCallback reply) {
-
-        if (mIsLockscreenLiveWallpaperEnabled) {
-            String callingPackage = mPackageManagerInternal.getNameForUid(getCallingUid());
-            clearWallpaperLocked(callingPackage, which, userId, reply);
-            return;
-        }
-
         if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
             throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear");
         }
@@ -3293,7 +3284,7 @@
     boolean setWallpaperComponent(ComponentName name, String callingPackage,
             @SetWallpaperFlags int which, int userId) {
         if (mIsLockscreenLiveWallpaperEnabled) {
-            return setWallpaperComponentInternal(name, callingPackage, which, userId, null);
+            return setWallpaperComponentInternal(name, callingPackage, which, userId);
         } else {
             setWallpaperComponentInternalLegacy(name, callingPackage, which, userId);
             return true;
@@ -3301,7 +3292,7 @@
     }
 
     private boolean setWallpaperComponentInternal(ComponentName name, String callingPackage,
-            @SetWallpaperFlags int which, int userIdIn, IRemoteCallback reply) {
+            @SetWallpaperFlags int which, int userIdIn) {
         if (DEBUG) {
             Slog.v(TAG, "Setting new live wallpaper: which=" + which + ", component: " + name);
         }
@@ -3350,7 +3341,6 @@
                             Slog.d(TAG, "publish system wallpaper changed!");
                         }
                         liveSync.complete();
-                        if (reply != null) reply.sendResult(null);
                     }
                 };
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9eec5f8..90e67df 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -225,7 +225,6 @@
 import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
 import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
 import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
-import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
 import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
@@ -2698,7 +2697,8 @@
 
     private void requestCopySplashScreen() {
         mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_COPYING;
-        if (!mAtmService.mTaskOrganizerController.copySplashScreenView(getTask())) {
+        if (mStartingSurface == null || !mAtmService.mTaskOrganizerController.copySplashScreenView(
+                getTask(), mStartingSurface.mTaskOrganizer)) {
             mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
             removeStartingWindow();
         }
@@ -2711,12 +2711,14 @@
      */
     void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) {
         removeTransferSplashScreenTimeout();
-        // unable to copy from shell, maybe it's not a splash screen. or something went wrong.
-        // either way, abort and reset the sequence.
-        if (parcelable == null
+        final SurfaceControl windowAnimationLeash = (parcelable == null
                 || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING
                 || mStartingWindow == null || mStartingWindow.mRemoved
-                || finishing) {
+                || finishing) ? null
+                : TaskOrganizerController.applyStartingWindowAnimation(mStartingWindow);
+        if (windowAnimationLeash == null) {
+            // Unable to copy from shell, maybe it's not a splash screen, or something went wrong.
+            // Either way, abort and reset the sequence.
             if (parcelable != null) {
                 parcelable.clearIfNeeded();
             }
@@ -2724,9 +2726,6 @@
             removeStartingWindow();
             return;
         }
-        // schedule attach splashScreen to client
-        final SurfaceControl windowAnimationLeash = TaskOrganizerController
-                .applyStartingWindowAnimation(mStartingWindow);
         try {
             mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
@@ -2766,7 +2765,8 @@
                 && (mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
                 || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_IDLE)) {
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Cleaning splash screen token=%s", this);
-            mAtmService.mTaskOrganizerController.onAppSplashScreenViewRemoved(getTask());
+            mAtmService.mTaskOrganizerController.onAppSplashScreenViewRemoved(getTask(),
+                    mStartingSurface != null ? mStartingSurface.mTaskOrganizer : null);
         }
     }
 
@@ -2854,7 +2854,6 @@
         } else if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_COPY_TO_CLIENT) {
             removeStartingWindow();
         }
-        lastData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
     }
 
     void removeStartingWindowAnimation(boolean prepareAnimation) {
@@ -3364,7 +3363,11 @@
         // current focused activity could be another activity in the same Task if activities are
         // displayed on adjacent TaskFragments.
         final ActivityRecord currentFocusedApp = mDisplayContent.mFocusedApp;
-        if (currentFocusedApp != null && currentFocusedApp.task == task) {
+        final int topFocusedDisplayId = mRootWindowContainer.getTopFocusedDisplayContent() != null
+                ? mRootWindowContainer.getTopFocusedDisplayContent().getDisplayId()
+                : INVALID_DISPLAY;
+        if (currentFocusedApp != null && currentFocusedApp.task == task
+                && topFocusedDisplayId == mDisplayContent.getDisplayId()) {
             final Task topFocusableTask = mDisplayContent.getTask(
                     (t) -> t.isLeafTask() && t.isFocusable(), true /*  traverseTopToBottom */);
             if (task == topFocusableTask) {
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 4180cd2..15a0445 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -40,8 +40,6 @@
     private static final boolean DEBUG = false;
 
     // Desktop mode feature flags.
-    private static final boolean DESKTOP_MODE_PROTO1_SUPPORTED =
-            SystemProperties.getBoolean("persist.wm.debug.desktop_mode", false);
     private static final boolean DESKTOP_MODE_PROTO2_SUPPORTED =
             SystemProperties.getBoolean("persist.wm.debug.desktop_mode_2", false);
     // Override default freeform task width when desktop mode is enabled. In dips.
@@ -142,6 +140,6 @@
 
     /** Whether desktop mode is supported. */
     static boolean isDesktopModeSupported() {
-        return DESKTOP_MODE_PROTO1_SUPPORTED || DESKTOP_MODE_PROTO2_SUPPORTED;
+        return DESKTOP_MODE_PROTO2_SUPPORTED;
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 2fb9869..6b33746 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -68,19 +68,15 @@
     }
 
     /**
-     * @see DisplayWindowPolicyController#canContainActivities(List, int)
+     * @see DisplayWindowPolicyController#canContainActivities
      */
     public boolean canContainActivities(@NonNull List<ActivityInfo> activities,
             @WindowConfiguration.WindowingMode int windowingMode) {
         if (mDisplayWindowPolicyController == null) {
-            for (int i = activities.size() - 1; i >= 0; i--) {
-                final ActivityInfo aInfo = activities.get(i);
-                if (aInfo.requiredDisplayCategory != null) {
-                    Slog.e(TAG,
-                            String.format("Activity with requiredDisplayCategory='%s' cannot be"
-                                            + " displayed on display %d because that display does"
-                                            + " not have a matching category",
-                                    aInfo.requiredDisplayCategory, mDisplayContent.mDisplayId));
+            for (int i = 0; i < activities.size(); ++i) {
+                // Missing controller means that this display has no categories for activity launch
+                // restriction.
+                if (hasDisplayCategory(activities.get(i))) {
                     return false;
                 }
             }
@@ -90,26 +86,31 @@
     }
 
     /**
-     * @see DisplayWindowPolicyController#canActivityBeLaunched(ActivityInfo, int, int, boolean)
+     * @see DisplayWindowPolicyController#canActivityBeLaunched
      */
     public boolean canActivityBeLaunched(ActivityInfo activityInfo,
             Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
             int launchingFromDisplayId, boolean isNewTask) {
         if (mDisplayWindowPolicyController == null) {
-            if (activityInfo.requiredDisplayCategory != null) {
-                Slog.e(TAG,
-                        String.format("Activity with requiredDisplayCategory='%s' cannot be"
-                                + " launched on display %d because that display does"
-                                + " not have a matching category",
-                                activityInfo.requiredDisplayCategory, mDisplayContent.mDisplayId));
-                return false;
-            }
-            return true;
+            // Missing controller means that this display has no categories for activity launch
+            // restriction.
+            return !hasDisplayCategory(activityInfo);
         }
         return mDisplayWindowPolicyController.canActivityBeLaunched(activityInfo, intent,
             windowingMode, launchingFromDisplayId, isNewTask);
     }
 
+    private boolean hasDisplayCategory(ActivityInfo aInfo) {
+        if (aInfo.requiredDisplayCategory != null) {
+            Slog.d(TAG,
+                    String.format("Checking activity launch with requiredDisplayCategory='%s' on"
+                                    + " display %d, which doesn't have a matching category.",
+                            aInfo.requiredDisplayCategory, mDisplayContent.mDisplayId));
+            return true;
+        }
+        return false;
+    }
+
     /**
      * @see DisplayWindowPolicyController#keepActivityOnWindowFlagsChanged(ActivityInfo, int, int)
      */
@@ -199,7 +200,6 @@
         return mDisplayWindowPolicyController.isEnteringPipAllowed(uid);
     }
 
-
     void dump(String prefix, PrintWriter pw) {
         if (mDisplayWindowPolicyController != null) {
             pw.println();
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index a23547e..2d281c4 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -76,7 +76,7 @@
      * This starting window should be removed after applying the start transaction of transition,
      * which ensures the app window has shown.
      */
-    @AfterTransaction int mRemoveAfterTransaction;
+    @AfterTransaction int mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
 
     /** Whether to prepare the removal animation. */
     boolean mPrepareRemoveAnimation;
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 0bb773a..a55c232 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -40,6 +40,7 @@
 import android.content.pm.ApplicationInfo;
 import android.os.UserHandle;
 import android.util.Slog;
+import android.window.ITaskOrganizer;
 import android.window.SplashScreenView;
 import android.window.TaskSnapshot;
 
@@ -79,12 +80,13 @@
     }
 
     StartingSurface createSplashScreenStartingSurface(ActivityRecord activity, int theme) {
-
         synchronized (mService.mGlobalLock) {
             final Task task = activity.getTask();
-            if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(
-                    task, activity, theme, null /* taskSnapshot */)) {
-                return new StartingSurface(task);
+            final TaskOrganizerController controller =
+                    mService.mAtmService.mTaskOrganizerController;
+            if (task != null && controller.addStartingWindow(task, activity, theme,
+                    null /* taskSnapshot */)) {
+                return new StartingSurface(task, controller.getTaskOrganizer());
             }
         }
         return null;
@@ -166,9 +168,12 @@
                 activity.mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(
                         activity, false /* checkOpening */);
             }
-                mService.mAtmService.mTaskOrganizerController.addStartingWindow(task,
-                        activity, 0 /* launchTheme */, taskSnapshot);
-            return new StartingSurface(task);
+            final TaskOrganizerController controller =
+                    mService.mAtmService.mTaskOrganizerController;
+            if (controller.addStartingWindow(task, activity, 0 /* launchTheme */, taskSnapshot)) {
+                return new StartingSurface(task, controller.getTaskOrganizer());
+            }
+            return null;
         }
     }
 
@@ -256,9 +261,12 @@
 
     final class StartingSurface {
         private final Task mTask;
+        // The task organizer which hold the client side reference of this surface.
+        final ITaskOrganizer mTaskOrganizer;
 
-        StartingSurface(Task task) {
+        StartingSurface(Task task, ITaskOrganizer taskOrganizer) {
             mTask = task;
+            mTaskOrganizer = taskOrganizer;
         }
 
         /**
@@ -268,7 +276,8 @@
          */
         public void remove(boolean animate) {
             synchronized (mService.mGlobalLock) {
-                mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, animate);
+                mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask,
+                        mTaskOrganizer, animate);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 3d01001..41e49b9 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -652,7 +652,7 @@
         if (rootTask == null || activity.mStartingData == null) {
             return false;
         }
-        final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+        final ITaskOrganizer lastOrganizer = getTaskOrganizer();
         if (lastOrganizer == null) {
             return false;
         }
@@ -672,12 +672,13 @@
         return true;
     }
 
-    void removeStartingWindow(Task task, boolean prepareAnimation) {
+    void removeStartingWindow(Task task, ITaskOrganizer taskOrganizer, boolean prepareAnimation) {
         final Task rootTask = task.getRootTask();
         if (rootTask == null) {
             return;
         }
-        final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+        final ITaskOrganizer lastOrganizer = taskOrganizer != null ? taskOrganizer
+                : getTaskOrganizer();
         if (lastOrganizer == null) {
             return;
         }
@@ -771,12 +772,13 @@
         }
     }
 
-    boolean copySplashScreenView(Task task) {
+    boolean copySplashScreenView(Task task, ITaskOrganizer taskOrganizer) {
         final Task rootTask = task.getRootTask();
         if (rootTask == null) {
             return false;
         }
-        final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+        final ITaskOrganizer lastOrganizer = taskOrganizer != null ? taskOrganizer
+                : getTaskOrganizer();
         if (lastOrganizer == null) {
             return false;
         }
@@ -799,12 +801,12 @@
      * @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int)
      * @see SplashScreenView#remove()
      */
-    public void onAppSplashScreenViewRemoved(Task task) {
+    public void onAppSplashScreenViewRemoved(Task task, ITaskOrganizer organizer) {
         final Task rootTask = task.getRootTask();
         if (rootTask == null) {
             return;
         }
-        final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+        final ITaskOrganizer lastOrganizer = organizer != null ? organizer : getTaskOrganizer();
         if (lastOrganizer == null) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e9af42b..81f91c7 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1127,6 +1127,9 @@
                         // though, notify the controller to prevent degenerate cases.
                         if (!r.isVisibleRequested()) {
                             mController.mValidateCommitVis.add(r);
+                        } else {
+                            // Make sure onAppTransitionFinished can be notified.
+                            mParticipants.add(r);
                         }
                         return;
                     }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 31afcbf..4d73358 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -519,7 +519,8 @@
         for (int i = mFixedRotationTransformState.mAssociatedTokens.size() - 1; i >= 0; i--) {
             final ActivityRecord r =
                     mFixedRotationTransformState.mAssociatedTokens.get(i).asActivityRecord();
-            if (r != null && r.isInTransition()) {
+            // Only care about the transition at Activity/Task level.
+            if (r != null && r.inTransitionSelfOrParent() && !r.mDisplayContent.inTransition()) {
                 return true;
             }
         }
diff --git a/services/core/jni/gnss/GnssVisibilityControlCallback.cpp b/services/core/jni/gnss/GnssVisibilityControlCallback.cpp
index ec215f1..bc57c1d 100644
--- a/services/core/jni/gnss/GnssVisibilityControlCallback.cpp
+++ b/services/core/jni/gnss/GnssVisibilityControlCallback.cpp
@@ -73,7 +73,7 @@
 
 template <>
 jstring ToJstring(JNIEnv* env, const String16& value) {
-    const char16_t* str = value.string();
+    const char16_t* str = value.c_str();
     size_t len = value.size();
     return env->NewString(reinterpret_cast<const jchar*>(str), len);
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index af1bac8..2be2bb9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2594,6 +2594,12 @@
                 mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, true,
                         userHandle);
             }
+            // Enforcing the restriction of private profile creation in case device owner is set.
+            if (!mUserManager.hasUserRestriction(
+                    UserManager.DISALLOW_ADD_PRIVATE_PROFILE, userHandle)) {
+                mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, true,
+                        userHandle);
+            }
             // Creation of managed profile is restricted in case device owner is set, enforcing this
             // restriction by setting user level restriction at time of device owner setup.
             if (!mUserManager.hasUserRestriction(
@@ -4036,6 +4042,15 @@
                     mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
                             false, user);
                 }
+
+                // When a device owner is set, the system automatically restricts adding a
+                // private profile.
+                // Remove this restriction when the device owner is cleared.
+                if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
+                        user)) {
+                    mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
+                            false, user);
+                }
             }
         } else {
             // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the original state
@@ -4061,6 +4076,15 @@
                         false,
                         userHandle);
             }
+
+            // When a device owner is set, the system automatically restricts adding a
+            // private profile.
+            // Remove this restriction when the device owner is cleared.
+            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
+                    userHandle)) {
+                mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
+                        false, userHandle);
+            }
         }
     }
 
@@ -9423,6 +9447,11 @@
                         mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
                                 true,
                                 UserHandle.of(u));
+
+                        // Restrict adding a private profile when a device owner is set.
+                        mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
+                                true,
+                                UserHandle.of(u));
                     }
                 } else {
                     mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
@@ -9435,6 +9464,9 @@
                     mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
                             true,
                             UserHandle.of(userId));
+                    mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
+                            true,
+                            UserHandle.of(userId));
                 }
                 // TODO Send to system too?
                 sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
@@ -13200,6 +13232,8 @@
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_ADD_CLONE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
         USER_RESTRICTION_PERMISSIONS.put(
+                UserManager.DISALLOW_ADD_PRIVATE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
+        USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_ADD_WIFI_CONFIG, new String[]{MANAGE_DEVICE_POLICY_WIFI});
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 7a877b9..0fc8c5e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -426,6 +426,7 @@
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_USER, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_MANAGED_PROFILE, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_CLONE_PROFILE, /* flags= */ 0);
+        USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.ENSURE_VERIFY_APPS, POLICY_FLAG_GLOBAL_ONLY_POLICY);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, /* flags= */ 0);
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, /* flags= */ 0);
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index ea5ce65..49962ea 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -52,7 +52,6 @@
     public static final int IPV6_ADDR_BITS = 128;
     public static final int IPV6_ADDR_LEN = 16;
     public static final int IPV6_MIN_MTU = 1280;
-    public static final int RFC7421_PREFIX_LENGTH = 64;
 
     /**
      * ICMP common (v4/v6) constants.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
index d99af77..e5fae49 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
@@ -88,7 +88,8 @@
 
     @Test
     fun freezePackage() {
-        val freezer = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms, TEST_EXIT_REASON)
+        val freezer = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms,
+                TEST_EXIT_REASON, null /* request */)
         verify(pms, times(1))
             .killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON),
                     eq(TEST_EXIT_REASON))
@@ -104,9 +105,9 @@
     @Test
     fun freezePackage_twice() {
         val freezer1 = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms,
-                TEST_EXIT_REASON)
+                TEST_EXIT_REASON, null  /* request */)
         val freezer2 = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms,
-                TEST_EXIT_REASON)
+                TEST_EXIT_REASON, null  /* request */)
         verify(pms, times(2))
             .killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON),
                     eq(TEST_EXIT_REASON))
@@ -127,7 +128,7 @@
     @Test
     fun freezePackage_withoutClosing() {
         var freezer: PackageFreezer? = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms,
-                TEST_EXIT_REASON)
+                TEST_EXIT_REASON, null  /* request */)
         verify(pms, times(1))
             .killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON),
                     eq(TEST_EXIT_REASON))
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
index 20cfd59..dc04b6a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
@@ -24,10 +24,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.app.AppOpsManager;
 import android.content.Context;
@@ -43,14 +45,13 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.permission.PermissionManager;
-import android.util.ArraySet;
+import android.testing.TestableLooper;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.R;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -62,24 +63,19 @@
 import java.util.List;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 public class CameraPrivacyLightControllerTest {
+    private int[] mDefaultColors = {0, 1, 2};
+    private int[] mDefaultAlsThresholdsLux = {10, 50};
+    private int mDefaultAlsAveragingIntervalMillis = 5000;
 
-    private int mDayColor = 1;
-    private int mNightColor = 0;
-    private int mCameraPrivacyLightAlsAveragingIntervalMillis = 5000;
-    private int mCameraPrivacyLightAlsNightThreshold = (int) getLightSensorValue(15);
+    private TestableLooper mTestableLooper;
 
     private MockitoSession mMockitoSession;
 
     @Mock
-    private Context mContext;
-
-    @Mock
-    private Resources mResources;
-
-    @Mock
     private LightsManager mLightsManager;
 
     @Mock
@@ -103,11 +99,64 @@
     private ArgumentCaptor<SensorEventListener> mLightSensorListenerCaptor =
             ArgumentCaptor.forClass(SensorEventListener.class);
 
-    private Set<String> mExemptedPackages = new ArraySet<>();
-    private List<Light> mLights = new ArrayList<>();
+    private Set<String> mExemptedPackages;
+    private List<Light> mLights;
 
     private int mNextLightId = 1;
 
+    public CameraPrivacyLightController prepareDefaultCameraPrivacyLightController() {
+        return prepareDefaultCameraPrivacyLightController(List.of(getNextLight(true)));
+    }
+
+    public CameraPrivacyLightController prepareDefaultCameraPrivacyLightController(
+            List<Light> lights) {
+        return prepareCameraPrivacyLightController(lights, Set.of(), true, mDefaultColors,
+                mDefaultAlsThresholdsLux, mDefaultAlsAveragingIntervalMillis);
+    }
+
+    public CameraPrivacyLightController prepareCameraPrivacyLightController(List<Light> lights,
+            Set<String> exemptedPackages, boolean hasLightSensor, int[] lightColors,
+            int[] alsThresholds, int averagingInterval) {
+        Looper looper = Looper.myLooper();
+        if (looper == null) {
+            Looper.prepare();
+            looper = Looper.myLooper();
+        }
+        if (mTestableLooper == null) {
+            try {
+                mTestableLooper = new TestableLooper(looper);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        Context context = mock(Context.class);
+        Resources resources = mock(Resources.class);
+        doReturn(resources).when(context).getResources();
+        doReturn(lightColors).when(resources).getIntArray(R.array.config_cameraPrivacyLightColors);
+        doReturn(alsThresholds).when(resources)
+                .getIntArray(R.array.config_cameraPrivacyLightAlsLuxThresholds);
+        doReturn(averagingInterval).when(resources)
+                .getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis);
+
+        doReturn(mLightsManager).when(context).getSystemService(LightsManager.class);
+        doReturn(mAppOpsManager).when(context).getSystemService(AppOpsManager.class);
+        doReturn(mSensorManager).when(context).getSystemService(SensorManager.class);
+
+        mLights = lights;
+        mExemptedPackages = exemptedPackages;
+        doReturn(mLights).when(mLightsManager).getLights();
+        doReturn(mLightsSession).when(mLightsManager).openSession(anyInt());
+        if (!hasLightSensor) {
+            mLightSensor = null;
+        }
+        doReturn(mLightSensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
+        doReturn(exemptedPackages)
+                .when(() -> PermissionManager.getIndicatorExemptedPackages(any()));
+
+        return new CameraPrivacyLightController(context, looper);
+    }
+
     @Before
     public void setUp() {
         mMockitoSession = ExtendedMockito.mockitoSession()
@@ -115,49 +164,33 @@
                 .strictness(Strictness.WARN)
                 .spyStatic(PermissionManager.class)
                 .startMocking();
-
-        doReturn(mDayColor).when(mContext).getColor(R.color.camera_privacy_light_day);
-        doReturn(mNightColor).when(mContext).getColor(R.color.camera_privacy_light_night);
-
-        doReturn(mResources).when(mContext).getResources();
-        doReturn(mCameraPrivacyLightAlsAveragingIntervalMillis).when(mResources)
-                .getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis);
-        doReturn(mCameraPrivacyLightAlsNightThreshold).when(mResources)
-                .getInteger(R.integer.config_cameraPrivacyLightAlsNightThreshold);
-
-        doReturn(mLightsManager).when(mContext).getSystemService(LightsManager.class);
-        doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
-        doReturn(mSensorManager).when(mContext).getSystemService(SensorManager.class);
-
-        doReturn(mLights).when(mLightsManager).getLights();
-        doReturn(mLightsSession).when(mLightsManager).openSession(anyInt());
-        doReturn(mLightSensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
-
-        doReturn(mExemptedPackages)
-                .when(() -> PermissionManager.getIndicatorExemptedPackages(any()));
     }
 
     @After
     public void tearDown() {
-        mExemptedPackages.clear();
-        mLights.clear();
-
         mMockitoSession.finishMocking();
     }
 
     @Test
+    public void testNoInteractionsWithServicesIfNoColorsSpecified() {
+        prepareCameraPrivacyLightController(List.of(getNextLight(true)), Collections.EMPTY_SET,
+                true, new int[0], mDefaultAlsThresholdsLux, mDefaultAlsAveragingIntervalMillis);
+
+        verifyZeroInteractions(mLightsManager);
+        verifyZeroInteractions(mAppOpsManager);
+        verifyZeroInteractions(mSensorManager);
+    }
+
+    @Test
     public void testAppsOpsListenerNotRegisteredWithoutCameraLights() {
-        mLights.add(getNextLight(false));
-        createCameraPrivacyLightController();
+        prepareDefaultCameraPrivacyLightController(List.of(getNextLight(false)));
 
         verify(mAppOpsManager, times(0)).startWatchingActive(any(), any(), any());
     }
 
     @Test
     public void testAppsOpsListenerRegisteredWithCameraLight() {
-        mLights.add(getNextLight(true));
-
-        createCameraPrivacyLightController();
+        prepareDefaultCameraPrivacyLightController();
 
         verify(mAppOpsManager, times(1)).startWatchingActive(any(), any(), any());
     }
@@ -165,11 +198,12 @@
     @Test
     public void testAllCameraLightsAreRequestedOnOpActive() {
         Random r = new Random(0);
+        List<Light> lights = new ArrayList<>();
         for (int i = 0; i < 30; i++) {
-            mLights.add(getNextLight(r.nextBoolean()));
+            lights.add(getNextLight(r.nextBoolean()));
         }
 
-        createCameraPrivacyLightController();
+        prepareDefaultCameraPrivacyLightController(lights);
 
         // Verify no session has been opened at this point.
         verify(mLightsManager, times(0)).openSession(anyInt());
@@ -181,8 +215,6 @@
         verify(mLightsManager, times(1)).openSession(anyInt());
 
         verify(mLightsSession).requestLights(mLightsRequestCaptor.capture());
-        assertEquals("requestLights() not invoked exactly once",
-                1, mLightsRequestCaptor.getAllValues().size());
 
         List<Integer> expectedCameraLightIds = mLights.stream()
                 .filter(l -> l.getType() == Light.LIGHT_TYPE_CAMERA)
@@ -199,40 +231,25 @@
 
     @Test
     public void testWillOnlyOpenOnceWhenTwoPackagesStartOp() {
-        mLights.add(getNextLight(true));
-
-        createCameraPrivacyLightController();
-
-        verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
-
-        AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue();
-        listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true);
+        prepareDefaultCameraPrivacyLightController();
+        notifyCamOpChanged(10101, "pkg1", true);
         verify(mLightsManager, times(1)).openSession(anyInt());
-        listener.onOpActiveChanged(OPSTR_CAMERA, 10102, "pkg2", true);
+        notifyCamOpChanged(10102, "pkg2", true);
         verify(mLightsManager, times(1)).openSession(anyInt());
     }
 
     @Test
     public void testWillCloseOnFinishOp() {
-        mLights.add(getNextLight(true));
-
-        createCameraPrivacyLightController();
-
-        verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
-
-        AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue();
-        listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true);
-
+        prepareDefaultCameraPrivacyLightController();
+        notifyCamOpChanged(10101, "pkg1", true);
         verify(mLightsSession, times(0)).close();
-        listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", false);
+        notifyCamOpChanged(10101, "pkg1", false);
         verify(mLightsSession, times(1)).close();
     }
 
     @Test
     public void testWillCloseOnFinishOpForAllPackages() {
-        mLights.add(getNextLight(true));
-
-        createCameraPrivacyLightController();
+        prepareDefaultCameraPrivacyLightController();
 
         int numUids = 100;
         List<Integer> uids = new ArrayList<>(numUids);
@@ -240,64 +257,52 @@
             uids.add(10001 + i);
         }
 
-        verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
-
-        AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue();
-
         for (int i = 0; i < numUids; i++) {
-            listener.onOpActiveChanged(OPSTR_CAMERA, uids.get(i), "pkg" + (int) uids.get(i), true);
+            notifyCamOpChanged(uids.get(i), "pkg" + (int) uids.get(i), true);
         }
 
         // Change the order which their ops are finished
         Collections.shuffle(uids, new Random(0));
 
         for (int i = 0; i < numUids - 1; i++) {
-            listener.onOpActiveChanged(OPSTR_CAMERA, uids.get(i), "pkg" + (int) uids.get(i), false);
+            notifyCamOpChanged(uids.get(i), "pkg" + (int) uids.get(i), false);
         }
 
         verify(mLightsSession, times(0)).close();
         int lastUid = uids.get(numUids - 1);
-        listener.onOpActiveChanged(OPSTR_CAMERA, lastUid, "pkg" + lastUid, false);
+        notifyCamOpChanged(lastUid, "pkg" + lastUid, false);
         verify(mLightsSession, times(1)).close();
     }
 
     @Test
     public void testWontOpenForExemptedPackage() {
-        mLights.add(getNextLight(true));
-        mExemptedPackages.add("pkg1");
+        String exemptPackage = "pkg1";
+        prepareCameraPrivacyLightController(List.of(getNextLight(true)),
+                Set.of(exemptPackage), true, mDefaultColors, mDefaultAlsThresholdsLux,
+                mDefaultAlsAveragingIntervalMillis);
 
-        createCameraPrivacyLightController();
-
-        verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
-
-        AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue();
-        listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true);
+        notifyCamOpChanged(10101, exemptPackage, true);
         verify(mLightsManager, times(0)).openSession(anyInt());
     }
 
     @Test
     public void testNoLightSensor() {
-        mLights.add(getNextLight(true));
-        doReturn(null).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
-
-        createCameraPrivacyLightController();
+        prepareCameraPrivacyLightController(List.of(getNextLight(true)),
+                Set.of(), true, mDefaultColors, mDefaultAlsThresholdsLux,
+                mDefaultAlsAveragingIntervalMillis);
 
         openCamera();
 
         verify(mLightsSession).requestLights(mLightsRequestCaptor.capture());
         LightsRequest lightsRequest = mLightsRequestCaptor.getValue();
         for (LightState lightState : lightsRequest.getLightStates()) {
-            assertEquals(mDayColor, lightState.getColor());
+            assertEquals(mDefaultColors[mDefaultColors.length - 1], lightState.getColor());
         }
     }
 
     @Test
     public void testALSListenerNotRegisteredUntilCameraIsOpened() {
-        mLights.add(getNextLight(true));
-        Sensor sensor = mock(Sensor.class);
-        doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
-
-        CameraPrivacyLightController cplc = createCameraPrivacyLightController();
+        prepareDefaultCameraPrivacyLightController();
 
         verify(mSensorManager, never()).registerListener(any(SensorEventListener.class),
                 any(Sensor.class), anyInt(), any(Handler.class));
@@ -307,113 +312,44 @@
         verify(mSensorManager, times(1)).registerListener(mLightSensorListenerCaptor.capture(),
                 any(Sensor.class), anyInt(), any(Handler.class));
 
-        mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", false);
+        notifyCamOpChanged(10001, "pkg", false);
         verify(mSensorManager, times(1)).unregisterListener(mLightSensorListenerCaptor.getValue());
     }
 
-    @Ignore
     @Test
-    public void testDayColor() {
-        testBrightnessToColor(20, mDayColor);
-    }
-
-    @Ignore
-    @Test
-    public void testNightColor() {
-        testBrightnessToColor(10, mNightColor);
-    }
-
-    private void testBrightnessToColor(int brightnessValue, int color) {
-        mLights.add(getNextLight(true));
-        Sensor sensor = mock(Sensor.class);
-        doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
-
-        CameraPrivacyLightController cplc = createCameraPrivacyLightController();
+    public void testAlsThresholds() {
+        CameraPrivacyLightController cplc = prepareDefaultCameraPrivacyLightController();
+        long elapsedTime = 0;
         cplc.setElapsedRealTime(0);
-
         openCamera();
+        for (int i = 0; i < mDefaultColors.length; i++) {
+            int expectedColor = mDefaultColors[i];
+            int alsLuxValue = i
+                    == mDefaultAlsThresholdsLux.length
+                    ? mDefaultAlsThresholdsLux[i - 1] : mDefaultAlsThresholdsLux[i] - 1;
 
-        verify(mSensorManager).registerListener(mLightSensorListenerCaptor.capture(),
-                any(Sensor.class), anyInt(), any(Handler.class));
-        SensorEventListener sensorListener = mLightSensorListenerCaptor.getValue();
-        float[] sensorEventValues = new float[1];
-        SensorEvent sensorEvent = new SensorEvent(sensor, 0, 0, sensorEventValues);
+            notifySensorEvent(cplc, elapsedTime, alsLuxValue);
+            elapsedTime += mDefaultAlsAveragingIntervalMillis + 1;
+            notifySensorEvent(cplc, elapsedTime, alsLuxValue);
 
-        sensorEventValues[0] = getLightSensorValue(brightnessValue);
-        sensorListener.onSensorChanged(sensorEvent);
-
-        verify(mLightsSession, atLeastOnce()).requestLights(mLightsRequestCaptor.capture());
-        for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
-            assertEquals(color, lightState.getColor());
+            verify(mLightsSession, atLeastOnce()).requestLights(mLightsRequestCaptor.capture());
+            for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
+                assertEquals(expectedColor, lightState.getColor());
+            }
         }
     }
 
-    @Ignore
-    @Test
-    public void testDayToNightTransistion() {
-        mLights.add(getNextLight(true));
-        Sensor sensor = mock(Sensor.class);
-        doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
-
-        CameraPrivacyLightController cplc = createCameraPrivacyLightController();
-        cplc.setElapsedRealTime(0);
-
-        openCamera();
-        // There will be an initial call at brightness 0
-        verify(mLightsSession, times(1)).requestLights(any(LightsRequest.class));
-
-        verify(mSensorManager).registerListener(mLightSensorListenerCaptor.capture(),
-                any(Sensor.class), anyInt(), any(Handler.class));
-        SensorEventListener sensorListener = mLightSensorListenerCaptor.getValue();
-
-        onSensorEvent(cplc, sensorListener, sensor, 0, 20);
-
-        // 5 sec avg = 20
-        onSensorEvent(cplc, sensorListener, sensor, 5000, 30);
-
-        verify(mLightsSession, times(2)).requestLights(mLightsRequestCaptor.capture());
-        for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
-            assertEquals(mDayColor, lightState.getColor());
-        }
-
-        // 5 sec avg = 22
-
-        onSensorEvent(cplc, sensorListener, sensor, 6000, 10);
-
-        // 5 sec avg = 18
-
-        onSensorEvent(cplc, sensorListener, sensor, 8000, 5);
-
-        // Should have always been day
-        verify(mLightsSession, times(2)).requestLights(mLightsRequestCaptor.capture());
-        for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
-            assertEquals(mDayColor, lightState.getColor());
-        }
-
-        // 5 sec avg = 12
-
-        onSensorEvent(cplc, sensorListener, sensor, 10000, 5);
-
-        // Should now be night
-        verify(mLightsSession, times(3)).requestLights(mLightsRequestCaptor.capture());
-        for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
-            assertEquals(mNightColor, lightState.getColor());
-        }
+    private void notifyCamOpChanged(int uid, String pkg, boolean active) {
+        verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
+        mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, uid, pkg, active);
     }
 
-    private void onSensorEvent(CameraPrivacyLightController cplc,
-            SensorEventListener sensorListener, Sensor sensor, long timestamp, int value) {
+    private void notifySensorEvent(CameraPrivacyLightController cplc, long timestamp, int value) {
         cplc.setElapsedRealTime(timestamp);
-        sensorListener.onSensorChanged(new SensorEvent(sensor, 0, timestamp,
-                new float[] {getLightSensorValue(value)}));
-    }
-
-    // Use the test thread so that the test is deterministic
-    private CameraPrivacyLightController createCameraPrivacyLightController() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-        return new CameraPrivacyLightController(mContext, Looper.myLooper());
+        verify(mSensorManager, atLeastOnce()).registerListener(mLightSensorListenerCaptor.capture(),
+                        eq(mLightSensor), anyInt(), any());
+        mLightSensorListenerCaptor.getValue().onSensorChanged(new SensorEvent(mLightSensor, 0,
+                TimeUnit.MILLISECONDS.toNanos(timestamp), new float[] {value}));
     }
 
     private Light getNextLight(boolean cameraType) {
@@ -427,10 +363,6 @@
         return light;
     }
 
-    private float getLightSensorValue(int i) {
-        return (float) Math.exp(i / CameraPrivacyLightController.LIGHT_VALUE_MULTIPLIER);
-    }
-
     private void openCamera() {
         verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
         mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", true);
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index aba24fb..7f8ad45 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -261,7 +261,8 @@
         // Verify disconnection has been cancelled and we're seeing two connections attempts,
         // with the device connected at the end of the test
         verify(mSpyDevInventory, times(2)).onSetBtActiveDevice(
-                any(AudioDeviceBroker.BtDeviceInfo.class), anyInt());
+                any(AudioDeviceBroker.BtDeviceInfo.class), anyInt() /*codec*/,
+                anyInt() /*streamType*/);
         Assert.assertTrue("Mock device not connected",
                 mSpyDevInventory.isA2dpDeviceConnected(mFakeBtDevice));
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
index 64e776e..a621c0c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -181,6 +181,7 @@
                 .getAuthenticationStatsForUser(USER_ID_1);
         assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
         assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
         assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
     }
 
@@ -203,6 +204,8 @@
                 .getAuthenticationStatsForUser(USER_ID_1);
         assertThat(authenticationStats.getTotalAttempts()).isEqualTo(500);
         assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(400);
+        assertThat(authenticationStats.getEnrollmentNotifications())
+                .isEqualTo(MAXIMUM_ENROLLMENT_NOTIFICATIONS);
         assertThat(authenticationStats.getFrr()).isWithin(0f).of(0.8f);
     }
 
@@ -230,6 +233,7 @@
                 .getAuthenticationStatsForUser(USER_ID_1);
         assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
         assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
         assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
     }
 
@@ -256,6 +260,7 @@
                 .getAuthenticationStatsForUser(USER_ID_1);
         assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
         assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
         assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
     }
 
@@ -284,6 +289,8 @@
         assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
         assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
         assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+        // Assert that notification count has been updated.
+        assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(1);
     }
 
     @Test
@@ -311,5 +318,7 @@
         assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
         assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
         assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+        // Assert that notification count has been updated.
+        assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(1);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
index dde2a3c..0c0d47a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
@@ -217,6 +217,31 @@
     }
 
     @Test
+    public void persistFrrStats_multiUser_newUser_shouldUpdateRecord() throws JSONException {
+        AuthenticationStats authenticationStats1 = new AuthenticationStats(USER_ID_1,
+                300 /* totalAttempts */, 10 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+        AuthenticationStats authenticationStats2 = new AuthenticationStats(USER_ID_2,
+                100 /* totalAttempts */, 5 /* rejectedAttempts */,
+                1 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
+        // Sets up the shared preference with user 1 only.
+        when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+                Set.of(buildFrrStats(authenticationStats1)));
+
+        // Add data for user 2.
+        mAuthenticationStatsPersister.persistFrrStats(authenticationStats2.getUserId(),
+                authenticationStats2.getTotalAttempts(),
+                authenticationStats2.getRejectedAttempts(),
+                authenticationStats2.getEnrollmentNotifications(),
+                authenticationStats2.getModality());
+
+        verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+        assertThat(mStringSetArgumentCaptor.getValue())
+                .contains(buildFrrStats(authenticationStats2));
+    }
+
+    @Test
     public void removeFrrStats_existingUser_shouldUpdateRecord() throws JSONException {
         AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
                 300 /* totalAttempts */, 10 /* rejectedAttempts */,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 00e35ec..41af9e3 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -129,7 +129,6 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -296,19 +295,20 @@
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
-        ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+        ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
                 /* displayOnRemoveDevices= */ true,
                 targetDisplayCategory);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
-                activityInfos.get(0), mAssociationInfo.getDisplayName());
-        gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+                activityInfo, mAssociationInfo.getDisplayName());
+        gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
         return blockedAppIntent;
     }
 
 
-    private ArrayList<ActivityInfo> getActivityInfoList(
+    private ActivityInfo getActivityInfo(
             String packageName, String name, boolean displayOnRemoveDevices,
             String requiredDisplayCategory) {
         ActivityInfo activityInfo = new ActivityInfo();
@@ -318,7 +318,7 @@
                 ? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES;
         activityInfo.applicationInfo = mApplicationInfoMock;
         activityInfo.requiredDisplayCategory = requiredDisplayCategory;
-        return new ArrayList<>(Arrays.asList(activityInfo));
+        return activityInfo;
     }
 
     @Before
@@ -1414,14 +1414,15 @@
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
-        ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+        ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
                 /* displayOnRemoveDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
-                activityInfos.get(0), mAssociationInfo.getDisplayName());
-        gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+                activityInfo, mAssociationInfo.getDisplayName());
+        gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
 
         verify(mContext, never()).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1434,14 +1435,15 @@
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
-        ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+        ActivityInfo activityInfo = getActivityInfo(
                 PERMISSION_CONTROLLER_PACKAGE_NAME,
                 PERMISSION_CONTROLLER_PACKAGE_NAME,
                 /* displayOnRemoveDevices */  false,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
-                activityInfos.get(0), mAssociationInfo.getDisplayName());
-        gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+                activityInfo, mAssociationInfo.getDisplayName());
+        gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
 
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1454,14 +1456,15 @@
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
-        ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+        ActivityInfo activityInfo = getActivityInfo(
                 SETTINGS_PACKAGE_NAME,
                 SETTINGS_PACKAGE_NAME,
                 /* displayOnRemoveDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
-                activityInfos.get(0), mAssociationInfo.getDisplayName());
-        gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+                activityInfo, mAssociationInfo.getDisplayName());
+        gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
 
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1474,14 +1477,15 @@
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
-        ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+        ActivityInfo activityInfo = getActivityInfo(
                 VENDING_PACKAGE_NAME,
                 VENDING_PACKAGE_NAME,
                 /* displayOnRemoveDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
-                activityInfos.get(0), mAssociationInfo.getDisplayName());
-        gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+                activityInfo, mAssociationInfo.getDisplayName());
+        gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
 
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1494,14 +1498,15 @@
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
-        ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+        ActivityInfo activityInfo = getActivityInfo(
                 GOOGLE_DIALER_PACKAGE_NAME,
                 GOOGLE_DIALER_PACKAGE_NAME,
                 /* displayOnRemoveDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
-                activityInfos.get(0), mAssociationInfo.getDisplayName());
-        gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+                activityInfo, mAssociationInfo.getDisplayName());
+        gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
 
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1514,14 +1519,15 @@
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
-        ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+        ActivityInfo activityInfo = getActivityInfo(
                 GOOGLE_MAPS_PACKAGE_NAME,
                 GOOGLE_MAPS_PACKAGE_NAME,
                 /* displayOnRemoveDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
-                activityInfos.get(0), mAssociationInfo.getDisplayName());
-        gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+                activityInfo, mAssociationInfo.getDisplayName());
+        gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
 
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1561,12 +1567,12 @@
         addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
-        ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+        ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
                 /* displayOnRemoveDevices */ true,
                 /* targetDisplayCategory */ null);
-        assertThat(gwpc.canActivityBeLaunched(activityInfos.get(0), intent,
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
                 WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
                 .isTrue();
     }
@@ -1585,7 +1591,7 @@
         addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
-        ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+        ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
                 /* displayOnRemoveDevices */ true,
@@ -1597,7 +1603,7 @@
 
         // register interceptor and intercept intent
         mDeviceImpl.registerIntentInterceptor(interceptor, intentFilter);
-        assertThat(gwpc.canActivityBeLaunched(activityInfos.get(0), intent,
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
                 WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
                 .isFalse();
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1609,7 +1615,7 @@
 
         // unregister interceptor and launch activity
         mDeviceImpl.unregisterIntentInterceptor(interceptor);
-        assertThat(gwpc.canActivityBeLaunched(activityInfos.get(0), intent,
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
                 WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
                 .isTrue();
     }
@@ -1628,7 +1634,7 @@
         addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
-        ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+        ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
                 /* displayOnRemoveDevices */ true,
@@ -1640,7 +1646,7 @@
         // register interceptor with different filter
         mDeviceImpl.registerIntentInterceptor(interceptor, intentFilter);
 
-        assertThat(gwpc.canActivityBeLaunched(activityInfos.get(0), intent,
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
                 WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
                 .isTrue();
     }
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 f408ef0..f4dac2c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1144,6 +1144,10 @@
                 eq(UserManager.DISALLOW_ADD_CLONE_PROFILE),
                 eq(true), eq(UserHandle.SYSTEM));
 
+        verify(getServices().userManager, times(1)).setUserRestriction(
+                eq(UserManager.DISALLOW_ADD_PRIVATE_PROFILE),
+                eq(true), eq(UserHandle.SYSTEM));
+
         verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
                 MockUtils.checkIntentAction(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
                 MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
@@ -1422,6 +1426,10 @@
                 .setUserRestriction(eq(UserManager.DISALLOW_ADD_CLONE_PROFILE), eq(false),
                 MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
 
+        verify(getServices().userManager)
+                .setUserRestriction(eq(UserManager.DISALLOW_ADD_PRIVATE_PROFILE), eq(false),
+                MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
+
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(UserHandle.USER_SYSTEM), MockUtils.checkUserRestrictions(),
                 MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM), eq(true));
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index ecd35a5..b22798e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -1174,6 +1174,24 @@
         }
     }
 
+    // Make sure the creation of a private profile fails if DISALLOW_ADD_PRIVATE_PROFILE is true.
+    @MediumTest
+    @Test
+    public void testCreateProfileForUser_disallowAddPrivateProfile() {
+        final int mainUserId = ActivityManager.getCurrentUser();
+        final UserHandle mainUserHandle = asHandle(mainUserId);
+        mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
+                true, mainUserHandle);
+        try {
+            UserInfo privateProfileInfo = createProfileForUser("Private",
+                    UserManager.USER_TYPE_PROFILE_PRIVATE, mainUserId);
+            assertThat(privateProfileInfo).isNull();
+        } finally {
+            mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, false,
+                    mainUserHandle);
+        }
+    }
+
     @MediumTest
     @Test
     public void testAddRestrictedProfile() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
index a387d4a..9907bd6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -72,6 +72,8 @@
     public void testCanDeviceOwnerChange() {
         assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
         assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_WALLPAPER));
+        assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(
+                UserManager.DISALLOW_ADD_PRIVATE_PROFILE));
         assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_ADD_USER));
         assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_USER_SWITCH));
     }
@@ -83,6 +85,8 @@
                 UserManager.DISALLOW_WALLPAPER, true));
         assertFalse(UserRestrictionsUtils.canProfileOwnerChange(
                 UserManager.DISALLOW_USER_SWITCH, true));
+        assertFalse(UserRestrictionsUtils.canProfileOwnerChange(
+                UserManager.DISALLOW_ADD_PRIVATE_PROFILE, true));
         assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
                 UserManager.DISALLOW_ADD_USER, true));
         assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index e540068..e22c104 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -2470,6 +2470,143 @@
         assertEquals(12345, mZenModeEventLogger.getPackageUid(4));
     }
 
+    @Test
+    public void testUpdateConsolidatedPolicy_defaultRulesOnly() {
+        setupZenConfig();
+
+        // When there's one automatic rule active and it doesn't specify a policy, test that the
+        // resulting consolidated policy is one that matches the default rule settings.
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                null,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
+                Process.SYSTEM_UID, true);
+
+        // enable the rule
+        mZenModeHelper.setAutomaticZenRuleState(id,
+                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+                Process.SYSTEM_UID, true);
+
+        // inspect the consolidated policy. Based on setupZenConfig() values.
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms());
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.allowMedia());
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem());
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders());
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowCalls());
+        assertEquals(PRIORITY_SENDERS_STARRED, mZenModeHelper.mConsolidatedPolicy.allowCallsFrom());
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages());
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations());
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers());
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.showBadges());
+    }
+
+    @Test
+    public void testUpdateConsolidatedPolicy_customPolicyOnly() {
+        setupZenConfig();
+
+        // when there's only one automatic rule active and it has a custom policy, make sure that's
+        // what the consolidated policy reflects whether or not it's stricter than what the default
+        // would specify.
+        ZenPolicy customPolicy = new ZenPolicy.Builder()
+                .allowAlarms(true)  // more lenient than default
+                .allowMedia(true)  // more lenient than default
+                .allowRepeatCallers(false)  // more restrictive than default
+                .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE)  // more restrictive than default
+                .showBadges(true)  // more lenient
+                .showPeeking(false)  // more restrictive
+                .build();
+
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                customPolicy,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
+                Process.SYSTEM_UID, true);
+
+        // enable the rule; this will update the consolidated policy
+        mZenModeHelper.setAutomaticZenRuleState(id,
+                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+                Process.SYSTEM_UID, true);
+
+        // since this is the only active rule, the consolidated policy should match the custom
+        // policy for every field specified, and take default values for unspecified things
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowAlarms());  // custom
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMedia());  // custom
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem());  // default
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders());  // default
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.allowCalls());  // custom
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages()); // default
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations());  // default
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers());  // custom
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.showBadges());  // custom
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.showPeeking());  // custom
+    }
+
+    @Test
+    public void testUpdateConsolidatedPolicy_defaultAndCustomActive() {
+        setupZenConfig();
+
+        // when there are two rules active, one inheriting the default policy and one setting its
+        // own custom policy, they should be merged to form the most restrictive combination.
+
+        // rule 1: no custom policy
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                null,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
+                Process.SYSTEM_UID, true);
+
+        // enable rule 1
+        mZenModeHelper.setAutomaticZenRuleState(id,
+                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+                Process.SYSTEM_UID, true);
+
+        // custom policy for rule 2
+        ZenPolicy customPolicy = new ZenPolicy.Builder()
+                .allowAlarms(true)  // more lenient than default
+                .allowMedia(true)  // more lenient than default
+                .allowRepeatCallers(false)  // more restrictive than default
+                .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE)  // more restrictive than default
+                .showBadges(true)  // more lenient
+                .showPeeking(false)  // more restrictive
+                .build();
+
+        AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                customPolicy,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
+                "test", Process.SYSTEM_UID, true);
+
+        // enable rule 2; this will update the consolidated policy
+        mZenModeHelper.setAutomaticZenRuleState(id2,
+                new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
+                Process.SYSTEM_UID, true);
+
+        // now both rules should be on, and the consolidated policy should reflect the most
+        // restrictive option of each of the two
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms());  // default stricter
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.allowMedia());  // default stricter
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem());  // default, unset in custom
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders());  // default
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.allowCalls());  // custom stricter
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages()); // default, unset in custom
+        assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations());  // default
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers());  // custom stricter
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.showBadges());  // default stricter
+        assertFalse(mZenModeHelper.mConsolidatedPolicy.showPeeking());  // custom stricter
+    }
+
     private void setupZenConfig() {
         mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
         mZenModeHelper.mConfig.allowAlarms = false;
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 44cf333..085241f 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -63,7 +63,6 @@
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 
 import com.android.server.LocalServices;
@@ -1005,7 +1004,6 @@
                 mVibratorProviders.get(3).getEffectSegments(vibrationId));
     }
 
-    @FlakyTest
     @Test
     public void vibrate_multipleSyncedCallbackTriggered_finishSteps() throws Exception {
         int[] vibratorIds = new int[]{1, 2};
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index a2b7da3..ae4ebc1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1698,14 +1698,17 @@
         final Task task = app.getTask();
         final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
         mDisplayContent.setFixedRotationLaunchingApp(app2, (mDisplayContent.getRotation() + 1) % 4);
-        doReturn(true).when(app).isInTransition();
+        doReturn(true).when(app).inTransitionSelfOrParent();
         // If the task contains a transition, this should be no-op.
         mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
 
         assertTrue(app2.hasFixedRotationTransform());
         assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
 
-        doReturn(false).when(app).isInTransition();
+        // The display should be unlikely to be in transition, but if it happens, the fixed
+        // rotation should proceed to finish because the activity/task level transition is finished.
+        doReturn(true).when(mDisplayContent).inTransition();
+        doReturn(false).when(app).inTransitionSelfOrParent();
         // Although this notifies app instead of app2 that uses the fixed rotation, app2 should
         // still finish the transform because there is no more transition event.
         mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index 5dfb901..30a8941 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -44,7 +44,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.List;
 import java.util.Set;
 
 /**
@@ -106,7 +105,6 @@
         assertEquals(uidAmount, mDwpc.mRunningUids.size());
         assertTrue(mDwpc.mRunningUids.contains(TEST_USER_0_ID) == expectedUid0);
         assertTrue(mDwpc.mRunningUids.contains(TEST_USER_1_ID) == expectedUid1);
-
     }
 
     private ActivityRecord launchActivityOnDisplay(DisplayContent display, int uid) {
@@ -197,6 +195,11 @@
 
     @Test
     public void testCanActivityBeLaunched_requiredDisplayCategory() {
+        doReturn(null).when(mWm.mDisplayManagerInternal)
+                .getDisplayWindowPolicyController(anyInt());
+        mSecondaryDisplay = createNewDisplay();
+        assertFalse(mSecondaryDisplay.mDwpcHelper.hasController());
+
         ActivityStarter starter = new ActivityStarter(mock(ActivityStartController.class), mAtm,
                 mSupervisor, mock(ActivityStartInterceptor.class));
         final Task task = new TaskBuilder(mSupervisor).setDisplay(mSecondaryDisplay).build();
@@ -233,20 +236,14 @@
         public boolean canActivityBeLaunched(@NonNull ActivityInfo activity, Intent intent,
                 @WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
                 boolean isNewTask) {
-            return false;
+            return canContainActivity(activity, windowingMode, launchingFromDisplayId, isNewTask);
         }
 
         @Override
-        public boolean canContainActivities(@NonNull List<ActivityInfo> activities,
-                @WindowConfiguration.WindowingMode int windowingMode) {
-            final int activityCount = activities.size();
-            for (int i = 0; i < activityCount; i++) {
-                final ActivityInfo aInfo = activities.get(i);
-                if (aInfo.getComponentName().equals(DISALLOWED_ACTIVITY)) {
-                    return false;
-                }
-            }
-            return true;
+        protected boolean canContainActivity(@NonNull ActivityInfo activity,
+                @WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
+                boolean isNewTask) {
+            return !activity.getComponentName().equals(DISALLOWED_ACTIVITY);
         }
 
         @Override
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 13e5ff1..0bb75d8 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1999,7 +1999,7 @@
             "lte_plus_threshold_bandwidth_khz_int";
 
     /**
-     * The combined channel bandwidth threshold (non-inclusive) in KHz required to display the
+     * The combined channel bandwidth threshold (inclusive) in KHz required to display the
      * NR advanced (i.e. 5G+) data icon. It is 0 by default, meaning minimum bandwidth check is
      * not enabled. Other factors like bands or frequency can also determine whether the NR
      * advanced data icon is shown or not.
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 9994002a..75b5f55 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -101,26 +101,26 @@
     }
 
     /**
-     * Exception from the satellite service containing the {@link SatelliteError} error code.
+     * Exception from the satellite service containing the {@link SatelliteResult} error code.
      */
     public static class SatelliteException extends Exception {
-        @SatelliteError private final int mErrorCode;
+        @SatelliteResult private final int mErrorCode;
 
         /**
          * Create a SatelliteException with a given error code.
          *
-         * @param errorCode The {@link SatelliteError}.
+         * @param errorCode The {@link SatelliteResult}.
          */
-        public SatelliteException(@SatelliteError int errorCode) {
+        public SatelliteException(@SatelliteResult int errorCode) {
             mErrorCode = errorCode;
         }
 
         /**
          * Get the error code returned from the satellite service.
          *
-         * @return The {@link SatelliteError}.
+         * @return The {@link SatelliteResult}.
          */
-        @SatelliteError public int getErrorCode() {
+        @SatelliteResult public int getErrorCode() {
             return mErrorCode;
         }
     }
@@ -185,134 +185,134 @@
     /**
      * The request was successfully processed.
      */
-    public static final int SATELLITE_ERROR_NONE = 0;
+    public static final int SATELLITE_RESULT_SUCCESS = 0;
     /**
      * A generic error which should be used only when other specific errors cannot be used.
      */
-    public static final int SATELLITE_ERROR = 1;
+    public static final int SATELLITE_RESULT_ERROR = 1;
     /**
      * Error received from the satellite server.
      */
-    public static final int SATELLITE_SERVER_ERROR = 2;
+    public static final int SATELLITE_RESULT_SERVER_ERROR = 2;
     /**
      * Error received from the vendor service. This generic error code should be used
      * only when the error cannot be mapped to other specific service error codes.
      */
-    public static final int SATELLITE_SERVICE_ERROR = 3;
+    public static final int SATELLITE_RESULT_SERVICE_ERROR = 3;
     /**
      * Error received from satellite modem. This generic error code should be used only when
      * the error cannot be mapped to other specific modem error codes.
      */
-    public static final int SATELLITE_MODEM_ERROR = 4;
+    public static final int SATELLITE_RESULT_MODEM_ERROR = 4;
     /**
      * Error received from the satellite network. This generic error code should be used only when
      * the error cannot be mapped to other specific network error codes.
      */
-    public static final int SATELLITE_NETWORK_ERROR = 5;
+    public static final int SATELLITE_RESULT_NETWORK_ERROR = 5;
     /**
      * Telephony is not in a valid state to receive requests from clients.
      */
-    public static final int SATELLITE_INVALID_TELEPHONY_STATE = 6;
+    public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6;
     /**
      * Satellite modem is not in a valid state to receive requests from clients.
      */
-    public static final int SATELLITE_INVALID_MODEM_STATE = 7;
+    public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7;
     /**
      * Either vendor service, or modem, or Telephony framework has received a request with
      * invalid arguments from its clients.
      */
-    public static final int SATELLITE_INVALID_ARGUMENTS = 8;
+    public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8;
     /**
      * Telephony framework failed to send a request or receive a response from the vendor service
      * or satellite modem due to internal error.
      */
-    public static final int SATELLITE_REQUEST_FAILED = 9;
+    public static final int SATELLITE_RESULT_REQUEST_FAILED = 9;
     /**
      * Radio did not start or is resetting.
      */
-    public static final int SATELLITE_RADIO_NOT_AVAILABLE = 10;
+    public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10;
     /**
      * The request is not supported by either the satellite modem or the network.
      */
-    public static final int SATELLITE_REQUEST_NOT_SUPPORTED = 11;
+    public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11;
     /**
      * Satellite modem or network has no resources available to handle requests from clients.
      */
-    public static final int SATELLITE_NO_RESOURCES = 12;
+    public static final int SATELLITE_RESULT_NO_RESOURCES = 12;
     /**
      * Satellite service is not provisioned yet.
      */
-    public static final int SATELLITE_SERVICE_NOT_PROVISIONED = 13;
+    public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13;
     /**
      * Satellite service provision is already in progress.
      */
-    public static final int SATELLITE_SERVICE_PROVISION_IN_PROGRESS = 14;
+    public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14;
     /**
      * The ongoing request was aborted by either the satellite modem or the network.
      * This error is also returned when framework decides to abort current send request as one
      * of the previous send request failed.
      */
-    public static final int SATELLITE_REQUEST_ABORTED = 15;
+    public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15;
     /**
      * The device/subscriber is barred from accessing the satellite service.
      */
-    public static final int SATELLITE_ACCESS_BARRED = 16;
+    public static final int SATELLITE_RESULT_ACCESS_BARRED = 16;
     /**
      * Satellite modem timeout to receive ACK or response from the satellite network after
      * sending a request to the network.
      */
-    public static final int SATELLITE_NETWORK_TIMEOUT = 17;
+    public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17;
     /**
      * Satellite network is not reachable from the modem.
      */
-    public static final int SATELLITE_NOT_REACHABLE = 18;
+    public static final int SATELLITE_RESULT_NOT_REACHABLE = 18;
     /**
      * The device/subscriber is not authorized to register with the satellite service provider.
      */
-    public static final int SATELLITE_NOT_AUTHORIZED = 19;
+    public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19;
     /**
      * The device does not support satellite.
      */
-    public static final int SATELLITE_NOT_SUPPORTED = 20;
+    public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20;
 
     /**
      * The current request is already in-progress.
      */
-    public static final int SATELLITE_REQUEST_IN_PROGRESS = 21;
+    public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21;
 
     /**
      * Satellite modem is currently busy due to which current request cannot be processed.
      */
-    public static final int SATELLITE_MODEM_BUSY = 22;
+    public static final int SATELLITE_RESULT_MODEM_BUSY = 22;
 
     /** @hide */
-    @IntDef(prefix = {"SATELLITE_"}, value = {
-            SATELLITE_ERROR_NONE,
-            SATELLITE_ERROR,
-            SATELLITE_SERVER_ERROR,
-            SATELLITE_SERVICE_ERROR,
-            SATELLITE_MODEM_ERROR,
-            SATELLITE_NETWORK_ERROR,
-            SATELLITE_INVALID_TELEPHONY_STATE,
-            SATELLITE_INVALID_MODEM_STATE,
-            SATELLITE_INVALID_ARGUMENTS,
-            SATELLITE_REQUEST_FAILED,
-            SATELLITE_RADIO_NOT_AVAILABLE,
-            SATELLITE_REQUEST_NOT_SUPPORTED,
-            SATELLITE_NO_RESOURCES,
-            SATELLITE_SERVICE_NOT_PROVISIONED,
-            SATELLITE_SERVICE_PROVISION_IN_PROGRESS,
-            SATELLITE_REQUEST_ABORTED,
-            SATELLITE_ACCESS_BARRED,
-            SATELLITE_NETWORK_TIMEOUT,
-            SATELLITE_NOT_REACHABLE,
-            SATELLITE_NOT_AUTHORIZED,
-            SATELLITE_NOT_SUPPORTED,
-            SATELLITE_REQUEST_IN_PROGRESS,
-            SATELLITE_MODEM_BUSY
+    @IntDef(prefix = {"SATELLITE_RESULT_"}, value = {
+            SATELLITE_RESULT_SUCCESS,
+            SATELLITE_RESULT_ERROR,
+            SATELLITE_RESULT_SERVER_ERROR,
+            SATELLITE_RESULT_SERVICE_ERROR,
+            SATELLITE_RESULT_MODEM_ERROR,
+            SATELLITE_RESULT_NETWORK_ERROR,
+            SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+            SATELLITE_RESULT_INVALID_MODEM_STATE,
+            SATELLITE_RESULT_INVALID_ARGUMENTS,
+            SATELLITE_RESULT_REQUEST_FAILED,
+            SATELLITE_RESULT_RADIO_NOT_AVAILABLE,
+            SATELLITE_RESULT_REQUEST_NOT_SUPPORTED,
+            SATELLITE_RESULT_NO_RESOURCES,
+            SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
+            SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS,
+            SATELLITE_RESULT_REQUEST_ABORTED,
+            SATELLITE_RESULT_ACCESS_BARRED,
+            SATELLITE_RESULT_NETWORK_TIMEOUT,
+            SATELLITE_RESULT_NOT_REACHABLE,
+            SATELLITE_RESULT_NOT_AUTHORIZED,
+            SATELLITE_RESULT_NOT_SUPPORTED,
+            SATELLITE_RESULT_REQUEST_IN_PROGRESS,
+            SATELLITE_RESULT_MODEM_BUSY
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface SatelliteError {}
+    public @interface SatelliteResult {}
 
     /**
      * Unknown Non-Terrestrial radio technology. This generic radio technology should be used
@@ -403,7 +403,7 @@
      *                        {@code false} to disable.
      * @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable.
      * @param executor The executor on which the error code listener will be called.
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -412,7 +412,7 @@
 
     public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
             @NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> resultListener) {
+            @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
 
@@ -446,7 +446,7 @@
      *                 will return a {@code boolean} with value {@code true} if the satellite modem
      *                 is enabled and {@code false} otherwise.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
+     *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -464,7 +464,7 @@
                 ResultReceiver receiver = new ResultReceiver(null) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == SATELLITE_ERROR_NONE) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
                             if (resultData.containsKey(KEY_SATELLITE_ENABLED)) {
                                 boolean isSatelliteEnabled =
                                         resultData.getBoolean(KEY_SATELLITE_ENABLED);
@@ -473,8 +473,8 @@
                             } else {
                                 loge("KEY_SATELLITE_ENABLED does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(
-                                                new SatelliteException(SATELLITE_REQUEST_FAILED))));
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
                             }
                         } else {
                             executor.execute(() -> Binder.withCleanCallingIdentity(() ->
@@ -501,7 +501,7 @@
      *                 will return a {@code boolean} with value {@code true} if demo mode is enabled
      *                 and {@code false} otherwise.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
+     *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -519,7 +519,7 @@
                 ResultReceiver receiver = new ResultReceiver(null) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == SATELLITE_ERROR_NONE) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
                             if (resultData.containsKey(KEY_DEMO_MODE_ENABLED)) {
                                 boolean isDemoModeEnabled =
                                         resultData.getBoolean(KEY_DEMO_MODE_ENABLED);
@@ -528,8 +528,8 @@
                             } else {
                                 loge("KEY_DEMO_MODE_ENABLED does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(
-                                                new SatelliteException(SATELLITE_REQUEST_FAILED))));
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
                             }
                         } else {
                             executor.execute(() -> Binder.withCleanCallingIdentity(() ->
@@ -556,7 +556,7 @@
      *                 will return a {@code boolean} with value {@code true} if the satellite
      *                 service is supported on the device and {@code false} otherwise.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
+     *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
@@ -572,7 +572,7 @@
                 ResultReceiver receiver = new ResultReceiver(null) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == SATELLITE_ERROR_NONE) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
                             if (resultData.containsKey(KEY_SATELLITE_SUPPORTED)) {
                                 boolean isSatelliteSupported =
                                         resultData.getBoolean(KEY_SATELLITE_SUPPORTED);
@@ -581,8 +581,8 @@
                             } else {
                                 loge("KEY_SATELLITE_SUPPORTED does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(
-                                                new SatelliteException(SATELLITE_REQUEST_FAILED))));
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
                             }
                         } else {
                             executor.execute(() -> Binder.withCleanCallingIdentity(() ->
@@ -608,7 +608,7 @@
      *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
      *                 will return the {@link SatelliteCapabilities} of the satellite service.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
+     *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -626,7 +626,7 @@
                 ResultReceiver receiver = new ResultReceiver(null) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == SATELLITE_ERROR_NONE) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
                             if (resultData.containsKey(KEY_SATELLITE_CAPABILITIES)) {
                                 SatelliteCapabilities capabilities =
                                         resultData.getParcelable(KEY_SATELLITE_CAPABILITIES,
@@ -636,8 +636,8 @@
                             } else {
                                 loge("KEY_SATELLITE_CAPABILITIES does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(
-                                                new SatelliteException(SATELLITE_REQUEST_FAILED))));
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
                             }
                         } else {
                             executor.execute(() -> Binder.withCleanCallingIdentity(() ->
@@ -794,13 +794,13 @@
      * Start receiving satellite transmission updates.
      * This can be called by the pointing UI when the user starts pointing to the satellite.
      * Modem should continue to report the pointing input as the device or satellite moves.
-     * Satellite transmission updates are started only on {@link #SATELLITE_ERROR_NONE}.
+     * Satellite transmission updates are started only on {@link #SATELLITE_RESULT_SUCCESS}.
      * All other results indicate that this operation failed.
      * Once satellite transmission updates begin, position and datagram transfer state updates
      * will be sent through {@link SatelliteTransmissionUpdateCallback}.
      *
      * @param executor The executor on which the callback and error code listener will be called.
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      * @param callback The callback to notify of satellite transmission updates.
      *
      * @throws SecurityException if the caller doesn't have required permission.
@@ -809,7 +809,7 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
 
     public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> resultListener,
+            @SatelliteResult @NonNull Consumer<Integer> resultListener,
             @NonNull SatelliteTransmissionUpdateCallback callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
@@ -866,12 +866,12 @@
      * Stop receiving satellite transmission updates.
      * This can be called by the pointing UI when the user stops pointing to the satellite.
      * Satellite transmission updates are stopped and the callback is unregistered only on
-     * {@link #SATELLITE_ERROR_NONE}. All other results that this operation failed.
+     * {@link #SATELLITE_RESULT_SUCCESS}. All other results that this operation failed.
      *
      * @param callback The callback that was passed to {@link
      * #startSatelliteTransmissionUpdates(Executor, Consumer, SatelliteTransmissionUpdateCallback)}.
      * @param executor The executor on which the error code listener will be called.
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -881,7 +881,7 @@
     public void stopSatelliteTransmissionUpdates(
             @NonNull SatelliteTransmissionUpdateCallback callback,
             @NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> resultListener) {
+            @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(callback);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
@@ -905,7 +905,7 @@
                 } else {
                     loge("stopSatelliteTransmissionUpdates: No internal callback.");
                     executor.execute(() -> Binder.withCleanCallingIdentity(
-                            () -> resultListener.accept(SATELLITE_INVALID_ARGUMENTS)));
+                            () -> resultListener.accept(SATELLITE_RESULT_INVALID_ARGUMENTS)));
                 }
             } else {
                 throw new IllegalStateException("telephony service is null.");
@@ -927,7 +927,7 @@
      *                           request. Even when the cancellation is signaled, Telephony will
      *                           still trigger the callback to return the result of this request.
      * @param executor The executor on which the error code listener will be called.
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -937,7 +937,7 @@
     public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> resultListener) {
+            @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(token);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
@@ -980,7 +980,7 @@
      *              This should match with the token passed as input in
      *              {@link #provisionSatelliteService(String, byte[], CancellationSignal, Executor,
      *              Consumer)}
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -989,7 +989,7 @@
 
     public void deprovisionSatelliteService(@NonNull String token,
             @NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> resultListener) {
+            @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(token);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
@@ -1020,14 +1020,14 @@
      * @param executor The executor on which the callback will be called.
      * @param callback The callback to handle the satellite provision state changed event.
      *
-     * @return The {@link SatelliteError} result of the operation.
+     * @return The {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
 
-    @SatelliteError public int registerForSatelliteProvisionStateChanged(
+    @SatelliteResult public int registerForSatelliteProvisionStateChanged(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteProvisionStateCallback callback) {
         Objects.requireNonNull(executor);
@@ -1055,7 +1055,7 @@
             loge("registerForSatelliteProvisionStateChanged() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
-        return SATELLITE_REQUEST_FAILED;
+        return SATELLITE_RESULT_REQUEST_FAILED;
     }
 
     /**
@@ -1102,7 +1102,7 @@
      *                 will return a {@code boolean} with value {@code true} if the device is
      *                 provisioned with a satellite provider and {@code false} otherwise.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
+     *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -1120,7 +1120,7 @@
                 ResultReceiver receiver = new ResultReceiver(null) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == SATELLITE_ERROR_NONE) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
                             if (resultData.containsKey(KEY_SATELLITE_PROVISIONED)) {
                                 boolean isSatelliteProvisioned =
                                         resultData.getBoolean(KEY_SATELLITE_PROVISIONED);
@@ -1129,8 +1129,8 @@
                             } else {
                                 loge("KEY_SATELLITE_PROVISIONED does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(
-                                                new SatelliteException(SATELLITE_REQUEST_FAILED))));
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
                             }
                         } else {
                             executor.execute(() -> Binder.withCleanCallingIdentity(() ->
@@ -1154,14 +1154,14 @@
      * @param executor The executor on which the callback will be called.
      * @param callback The callback to handle the satellite modem state changed event.
      *
-     * @return The {@link SatelliteError} result of the operation.
+     * @return The {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
 
-    @SatelliteError public int registerForSatelliteModemStateChanged(
+    @SatelliteResult public int registerForSatelliteModemStateChanged(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteStateCallback callback) {
         Objects.requireNonNull(executor);
@@ -1186,7 +1186,7 @@
             loge("registerForSatelliteModemStateChanged() RemoteException:" + ex);
             ex.rethrowFromSystemServer();
         }
-        return SATELLITE_REQUEST_FAILED;
+        return SATELLITE_RESULT_REQUEST_FAILED;
     }
 
     /**
@@ -1232,14 +1232,14 @@
      * @param callback The callback to handle incoming datagrams over satellite.
      *                 This callback with be invoked when a new datagram is received from satellite.
      *
-     * @return The {@link SatelliteError} result of the operation.
+     * @return The {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
 
-    @SatelliteError public int registerForSatelliteDatagram(
+    @SatelliteResult public int registerForSatelliteDatagram(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteDatagramCallback callback) {
         Objects.requireNonNull(executor);
@@ -1280,7 +1280,7 @@
             loge("registerForSatelliteDatagram() RemoteException:" + ex);
             ex.rethrowFromSystemServer();
         }
-        return SATELLITE_REQUEST_FAILED;
+        return SATELLITE_RESULT_REQUEST_FAILED;
     }
 
     /**
@@ -1327,7 +1327,7 @@
      * Consumer)} )}
      *
      * @param executor The executor on which the result listener will be called.
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -1335,7 +1335,7 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
 
     public void pollPendingSatelliteDatagrams(@NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> resultListener) {
+            @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
 
@@ -1380,7 +1380,7 @@
      *                                 user activity and the application's ability to determine the
      *                                 best possible UX experience for the user.
      * @param executor The executor on which the result listener will be called.
-     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -1390,7 +1390,7 @@
     public void sendSatelliteDatagram(@DatagramType int datagramType,
             @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
             @NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> resultListener) {
+            @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(datagram);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
@@ -1426,7 +1426,7 @@
      *                 communication is allowed for the current location and
      *                 {@code false} otherwise.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
+     *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -1445,7 +1445,7 @@
                 ResultReceiver receiver = new ResultReceiver(null) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == SATELLITE_ERROR_NONE) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
                             if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) {
                                 boolean isSatelliteCommunicationAllowed =
                                         resultData.getBoolean(KEY_SATELLITE_COMMUNICATION_ALLOWED);
@@ -1454,8 +1454,8 @@
                             } else {
                                 loge("KEY_SATELLITE_COMMUNICATION_ALLOWED does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(
-                                                new SatelliteException(SATELLITE_REQUEST_FAILED))));
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
                             }
                         } else {
                             executor.execute(() -> Binder.withCleanCallingIdentity(() ->
@@ -1484,7 +1484,7 @@
      *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
      *                 will return the time after which the satellite will be visible.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
+     *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -1502,7 +1502,7 @@
                 ResultReceiver receiver = new ResultReceiver(null) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == SATELLITE_ERROR_NONE) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
                             if (resultData.containsKey(KEY_SATELLITE_NEXT_VISIBILITY)) {
                                 int nextVisibilityDuration =
                                         resultData.getInt(KEY_SATELLITE_NEXT_VISIBILITY);
@@ -1512,8 +1512,8 @@
                             } else {
                                 loge("KEY_SATELLITE_NEXT_VISIBILITY does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(
-                                                new SatelliteException(SATELLITE_REQUEST_FAILED))));
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
                             }
                         } else {
                             executor.execute(() -> Binder.withCleanCallingIdentity(() ->
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index d7d892a..7ac06b0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -43,7 +43,7 @@
      */
     void onSendDatagramStateChanged(
             @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
-            @SatelliteManager.SatelliteError int errorCode);
+            @SatelliteManager.SatelliteResult int errorCode);
 
     /**
      * Called when satellite datagram receive state changed.
@@ -54,5 +54,5 @@
      */
     void onReceiveDatagramStateChanged(
             @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount,
-            @SatelliteManager.SatelliteError int errorCode);
+            @SatelliteManager.SatelliteResult int errorCode);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index 5e69215..d687162 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -19,7 +19,6 @@
 import android.telephony.satellite.stub.NTRadioTechnology;
 import android.telephony.satellite.stub.PointingInfo;
 import android.telephony.satellite.stub.SatelliteDatagram;
-import android.telephony.satellite.stub.SatelliteError;
 import android.telephony.satellite.stub.SatelliteModemState;
 
 /**
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteError.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteResult.aidl
similarity index 77%
rename from telephony/java/android/telephony/satellite/stub/SatelliteError.aidl
rename to telephony/java/android/telephony/satellite/stub/SatelliteResult.aidl
index 6a110a9..639b483 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteError.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteResult.aidl
@@ -20,91 +20,87 @@
  * {@hide}
  */
 @Backing(type="int")
-enum SatelliteError {
+enum SatelliteResult {
     /**
      * The request was successfully processed.
      */
-    ERROR_NONE = 0,
+    SATELLITE_RESULT_SUCCESS = 0,
     /**
      * A generic error which should be used only when other specific errors cannot be used.
      */
-    SATELLITE_ERROR = 1,
+    SATELLITE_RESULT_ERROR = 1,
     /**
      * Error received from the satellite server.
      */
-    SERVER_ERROR = 2,
+    SATELLITE_RESULT_SERVER_ERROR = 2,
     /**
      * Error received from the vendor service. This generic error code should be used
      * only when the error cannot be mapped to other specific service error codes.
      */
-    SERVICE_ERROR = 3,
+    SATELLITE_RESULT_SERVICE_ERROR = 3,
     /**
      * Error received from satellite modem. This generic error code should be used only when
      * the error cannot be mapped to other specific modem error codes.
      */
-    MODEM_ERROR = 4,
+    SATELLITE_RESULT_MODEM_ERROR = 4,
     /**
      * Error received from the satellite network. This generic error code should be used only when
      * the error cannot be mapped to other specific network error codes.
      */
-    NETWORK_ERROR = 5,
-    /**
-     * Telephony is not in a valid state to receive requests from clients.
-     */
-    INVALID_TELEPHONY_STATE = 6,
+    SATELLITE_RESULT_NETWORK_ERROR = 5,
     /**
      * Satellite modem is not in a valid state to receive requests from clients.
      */
-    INVALID_MODEM_STATE = 7,
+    SATELLITE_RESULT_INVALID_MODEM_STATE = 6,
     /**
      * Either vendor service, or modem, or Telephony framework has received a request with
      * invalid arguments from its clients.
      */
-    INVALID_ARGUMENTS = 8,
+    SATELLITE_RESULT_INVALID_ARGUMENTS = 7,
     /**
      * Telephony framework failed to send a request or receive a response from the vendor service
      * or satellite modem due to internal error.
      */
-    REQUEST_FAILED = 9,
+    SATELLITE_RESULT_REQUEST_FAILED = 8,
     /**
      * Radio did not start or is resetting.
      */
-    RADIO_NOT_AVAILABLE = 10,
+    SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 9,
     /**
      * The request is not supported by either the satellite modem or the network.
      */
-    REQUEST_NOT_SUPPORTED = 11,
+    SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 10,
     /**
      * Satellite modem or network has no resources available to handle requests from clients.
      */
-    NO_RESOURCES = 12,
+    SATELLITE_RESULT_NO_RESOURCES = 11,
     /**
      * Satellite service is not provisioned yet.
      */
-    SERVICE_NOT_PROVISIONED = 13,
+    SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 12,
     /**
      * Satellite service provision is already in progress.
      */
-    SERVICE_PROVISION_IN_PROGRESS = 14,
+    SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 13,
     /**
      * The ongoing request was aborted by either the satellite modem or the network.
      */
-    REQUEST_ABORTED = 15,
+    SATELLITE_RESULT_REQUEST_ABORTED = 14,
     /**
      * The device/subscriber is barred from accessing the satellite service.
      */
-    SATELLITE_ACCESS_BARRED = 16,
+    SATELLITE_RESULT_ACCESS_BARRED = 15,
     /**
      * Satellite modem timeout to receive ACK or response from the satellite network after
      * sending a request to the network.
      */
-    NETWORK_TIMEOUT = 17,
+    SATELLITE_RESULT_NETWORK_TIMEOUT = 16,
     /**
      * Satellite network is not reachable from the modem.
      */
-    SATELLITE_NOT_REACHABLE = 18,
+    SATELLITE_RESULT_NOT_REACHABLE = 17,
     /**
      * The device/subscriber is not authorized to register with the satellite service provider.
      */
-    NOT_AUTHORIZED = 19
+    SATELLITE_RESULT_NOT_AUTHORIZED = 18
 }
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index b94d14f..0fc2617 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -1540,7 +1540,7 @@
     }
 
     const String8& featureOfBase = bundle->getFeatureOfPackage();
-    if (!featureOfBase.isEmpty()) {
+    if (!featureOfBase.empty()) {
         if (bundle->getVerbose()) {
             printf("Including base feature resources from package: %s\n",
                     featureOfBase.c_str());
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 5a06b10..60f3f27 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -1133,7 +1133,7 @@
                 if (code == ResXMLTree::END_TAG) {
                     depth--;
                     if (depth < 2) {
-                        if (withinSupportsInput && !supportedInput.isEmpty()) {
+                        if (withinSupportsInput && !supportedInput.empty()) {
                             printf("supports-input: '");
                             const size_t N = supportedInput.size();
                             for (size_t i=0; i<N; i++) {
@@ -1300,7 +1300,7 @@
                             ResTable::normalizeForOutput(versionName.c_str()).c_str());
 
                     String8 splitName = AaptXml::getAttribute(tree, NULL, "split");
-                    if (!splitName.isEmpty()) {
+                    if (!splitName.empty()) {
                         printf(" split='%s'", ResTable::normalizeForOutput(
                                     splitName.c_str()).c_str());
                     }
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 9c944e0..4a360ed 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1285,7 +1285,7 @@
         packageType = ResourceTable::SharedLibrary;
     } else if (bundle->getExtending()) {
         packageType = ResourceTable::System;
-    } else if (!bundle->getFeatureOfPackage().isEmpty()) {
+    } else if (!bundle->getFeatureOfPackage().empty()) {
         packageType = ResourceTable::AppFeature;
     }
 
@@ -3144,7 +3144,7 @@
 
     tree.restart();
 
-    if (!startTags.isEmpty()) {
+    if (!startTags.empty()) {
         bool haveStart = false;
         while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
             if (code != ResXMLTree::START_TAG) {
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 449e080..bc252cf 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -1813,7 +1813,7 @@
     mTypeIdOffset = findLargestTypeIdForPackage(assets->getIncludedResources(), mAssetsPackage);
 
     const String8& featureAfter = bundle->getFeatureAfterPackage();
-    if (!featureAfter.isEmpty()) {
+    if (!featureAfter.empty()) {
         AssetManager featureAssetManager;
         if (!featureAssetManager.addAssetPath(featureAfter, NULL)) {
             fprintf(stderr, "ERROR: Feature package '%s' not found.\n",
@@ -1823,7 +1823,7 @@
 
         const ResTable& featureTable = featureAssetManager.getResources(false);
         mTypeIdOffset = std::max(mTypeIdOffset,
-                findLargestTypeIdForPackage(featureTable, mAssetsPackage)); 
+                findLargestTypeIdForPackage(featureTable, mAssetsPackage));
     }
 
     return NO_ERROR;
@@ -3252,7 +3252,7 @@
 
             // If we're building splits, then each invocation of the flattening
             // step will have 'missing' entries. Don't warn/error for this case.
-            if (bundle->getSplitConfigurations().isEmpty()) {
+            if (bundle->getSplitConfigurations().empty()) {
                 bool missing_entry = false;
                 const char* log_prefix = bundle->getErrorOnMissingConfigEntry() ?
                         "error" : "warning";
@@ -4858,7 +4858,7 @@
 
     Vector<sp<XMLNode> > nodesToVisit;
     nodesToVisit.push(root);
-    while (!nodesToVisit.isEmpty()) {
+    while (!nodesToVisit.empty()) {
         sp<XMLNode> node = nodesToVisit.top();
         nodesToVisit.pop();
 
diff --git a/tools/aapt/SourcePos.cpp b/tools/aapt/SourcePos.cpp
index e130286..354a65c 100644
--- a/tools/aapt/SourcePos.cpp
+++ b/tools/aapt/SourcePos.cpp
@@ -78,7 +78,7 @@
         break;
     }
     
-    if (!this->file.isEmpty()) {
+    if (!this->file.empty()) {
         if (this->line >= 0) {
             fprintf(to, "%s:%d: %s%s\n", this->file.c_str(), this->line, type, this->error.c_str());
         } else {
diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp
index 52949da..a0679a6 100644
--- a/tools/aapt2/DominatorTree_test.cpp
+++ b/tools/aapt2/DominatorTree_test.cpp
@@ -50,8 +50,7 @@
  private:
   void VisitConfig(const DominatorTree::Node* node, const int indent) {
     auto config_string = node->value()->config.toString();
-    buffer_ << std::string(indent, ' ')
-            << (config_string.isEmpty() ? "<default>" : config_string)
+    buffer_ << std::string(indent, ' ') << (config_string.empty() ? "<default>" : config_string)
             << std::endl;
   }
 
diff --git a/tools/obbtool/Main.cpp b/tools/obbtool/Main.cpp
index 64808c0..7014068 100644
--- a/tools/obbtool/Main.cpp
+++ b/tools/obbtool/Main.cpp
@@ -135,7 +135,7 @@
     }
 
     printf("OBB info for '%s':\n", filename);
-    printf("Package name: %s\n", obb->getPackageName().string());
+    printf("Package name: %s\n", obb->getPackageName().c_str());
     printf("     Version: %d\n", obb->getVersion());
     printf("       Flags: 0x%08x\n", obb->getFlags());
     printf("     Overlay: %s\n", obb->isOverlay() ? "true" : "false");
diff --git a/tools/split-select/SplitDescription.cpp b/tools/split-select/SplitDescription.cpp
index 4e2b48e..7150008 100644
--- a/tools/split-select/SplitDescription.cpp
+++ b/tools/split-select/SplitDescription.cpp
@@ -70,7 +70,7 @@
 String8 SplitDescription::toString() const {
     String8 extension;
     if (abi != abi::Variant_none) {
-        if (extension.isEmpty()) {
+        if (extension.empty()) {
             extension.append(":");
         } else {
             extension.append("-");