Merge "Add myself into the pip2 package OWNERS" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 7005349..fd92979b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -18,6 +18,7 @@
 
     // 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}",
@@ -78,3 +79,16 @@
     aconfig_declarations: "android.security.flags-aconfig",
     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"],
+}
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/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/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/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/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/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/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/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/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..16eb19a 100644
--- a/core/java/android/service/trust/OWNERS
+++ b/core/java/android/service/trust/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 36824
 
-cbrubaker@google.com
 jacobhobbie@google.com
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/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/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/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/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/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 93ce91f..c641e87 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
@@ -167,6 +167,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 +177,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);
     }
 
     //
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/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/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/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/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/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 0a100ba..b3b44cb 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,70 @@
 
 @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()
-            }
-        },
+    var settingsMenu: View? = null
+
+    Box(
         modifier = modifier,
+    ) {
+        LongPressSurface(
+            viewModel = longPressViewModel,
+            isSettingsMenuVisible = { settingsMenu?.isVisible == true },
+            settingsMenuBounds = {
+                val bounds = android.graphics.Rect()
+                settingsMenu?.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)
+                settingsMenu = keyguardRootView.requireViewById(R.id.keyguard_settings_button)
+                keyguardRootView
+            },
+            update = { keyguardRootView ->
+                keyguardRootView.requireViewById<View>(R.id.lock_icon_view)
+            },
+            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/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/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/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/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..907e106 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")
diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
index c41b5e4..2856011 100644
--- a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -366,6 +366,52 @@
         }
     }
 
+    /**
+     * Obtains the image size from the image header, without decoding the full image.
+     *
+     * @param icon an [Icon] representing the source of the image
+     * @return the [Size] if it could be determined from the image header, or `null` otherwise
+     */
+    suspend fun loadSize(icon: Icon, context: Context): Size? =
+        withContext(backgroundDispatcher) { loadSizeSync(icon, context) }
+
+    /**
+     * Obtains the image size from the image header, without decoding the full image.
+     *
+     * @param icon an [Icon] representing the source of the image
+     * @return the [Size] if it could be determined from the image header, or `null` otherwise
+     */
+    @WorkerThread
+    fun loadSizeSync(icon: Icon, context: Context): Size? {
+        return when (icon.type) {
+            Icon.TYPE_URI,
+            Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+                val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
+                loadSizeSync(source)
+            }
+            else -> null
+        }
+    }
+
+    /**
+     * Obtains the image size from the image header, without decoding the full image.
+     *
+     * @param source [ImageDecoder.Source] of the image
+     * @return the [Size] if it could be determined from the image header, or `null` otherwise
+     */
+    @WorkerThread
+    fun loadSizeSync(source: ImageDecoder.Source): Size? {
+        return try {
+            ImageDecoder.decodeHeader(source).size
+        } catch (e: IOException) {
+            Log.w(TAG, "Failed to load source $source", e)
+            return null
+        } catch (e: DecodeException) {
+            Log.w(TAG, "Failed to decode source $source", e)
+            return null
+        }
+    }
+
     companion object {
         const val TAG = "ImageLoader"
 
@@ -452,7 +498,7 @@
          * originate from other processes so we need to make sure we load them from the right
          * package source.
          *
-         * @return [Resources] to load the icon drawble or null if icon doesn't carry a resource or
+         * @return [Resources] to load the icon drawable or null if icon doesn't carry a resource or
          *   the resource package couldn't be resolved.
          */
         @WorkerThread
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/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/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..d2034d7 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;
@@ -257,16 +276,19 @@
         }
     }
 
+    @GuardedBy("mDozingLock")
     public void startNotificationLogging() {
         if (!mLogging) {
             mLogging = true;
             if (DEBUG) {
                 Log.i(TAG, "startNotificationLogging");
             }
+            boolean 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 +296,6 @@
         }
     }
 
-    private void setDozing(boolean dozing) {
-        synchronized (mDozingLock) {
-            mDozing = dozing;
-            maybeUpdateLoggingStatus();
-        }
-    }
-
     private void logNotificationVisibilityChanges(
             Collection<NotificationVisibility> newlyVisible,
             Collection<NotificationVisibility> noLongerVisible) {
@@ -362,39 +377,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 +386,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..0ff1a95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -200,8 +200,6 @@
 
     void onKeyguardViewManagerStatesUpdated();
 
-    boolean isDeviceInVrMode();
-
     NotificationPresenter getPresenter();
 
     /**
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..37038a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -41,7 +41,6 @@
     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
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..8cdf7d8 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,7 +217,6 @@
 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;
@@ -462,6 +457,7 @@
     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 +485,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;
@@ -508,13 +502,6 @@
 
     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 +627,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 +656,6 @@
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
             NotificationGutsManager notificationGutsManager,
-            NotificationLogger notificationLogger,
             NotificationInterruptStateProvider notificationInterruptStateProvider,
             ShadeExpansionStateManager shadeExpansionStateManager,
             KeyguardViewMediator keyguardViewMediator,
@@ -745,6 +706,7 @@
             Lazy<CentralSurfacesCommandQueueCallbacks> commandQueueCallbacksLazy,
             PluginManager pluginManager,
             ShadeController shadeController,
+            WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
@@ -803,7 +765,6 @@
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
         mGutsManager = notificationGutsManager;
-        mNotificationLogger = notificationLogger;
         mNotificationInterruptStateProvider = notificationInterruptStateProvider;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mKeyguardViewMediator = keyguardViewMediator;
@@ -856,6 +817,7 @@
         mCommandQueueCallbacksLazy = commandQueueCallbacksLazy;
         mPluginManager = pluginManager;
         mShadeController = shadeController;
+        mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
@@ -926,14 +888,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 +1125,10 @@
                 new FoldStateListener(mContext, this::onFoldedStateChanged));
     }
 
-    @VisibleForTesting
+    /**
+     * @deprecated use {@link
+     * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead.
+     */    @VisibleForTesting
     void initShadeVisibilityListener() {
         mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
             @Override
@@ -1563,6 +1520,7 @@
                 mNotifListContainer,
                 mStackScrollerController.getNotifStackController(),
                 mNotificationActivityStarter);
+        mWindowRootViewVisibilityInteractor.setUp(mPresenter, mNotificationsController);
     }
 
     /**
@@ -1709,11 +1667,6 @@
     }
 
     @Override
-    public boolean isDeviceInVrMode() {
-        return mPresenter.isDeviceInVrMode();
-    }
-
-    @Override
     public NotificationPresenter getPresenter() {
         return mPresenter;
     }
@@ -1947,8 +1900,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 +2100,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();
@@ -2708,11 +2583,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 +2629,6 @@
             releaseGestureWakeLock();
             mLaunchCameraWhenFinishedWaking = false;
             mDeviceInteractive = false;
-            updateVisibleToUser();
 
             updateNotificationPanelTouchState();
             getNotificationShadeWindowViewController().cancelCurrentTouch();
@@ -2858,7 +2727,6 @@
                         /* wakingUp= */ true,
                         mShouldDelayWakeUpAnimation);
 
-                updateVisibleToUser();
                 updateIsKeyguard();
                 mShouldDelayLockscreenTransitionFromAod = mDozeParameters.getAlwaysOn()
                         && mFeatureFlags.isEnabled(
@@ -3187,9 +3055,6 @@
 
     protected boolean mVisible;
 
-    // mScreenOnFromKeyguard && mVisible.
-    private boolean mVisibleToUser;
-
     protected DevicePolicyManager mDevicePolicyManager;
     private final PowerManager mPowerManager;
     protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -3313,21 +3178,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);
-        }
     }
 
     /**
@@ -3503,7 +3355,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 62a8cfd..b0f8276 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -763,7 +763,7 @@
      * Sets the amount of vertical over scroll that should be performed on the notifications scrim.
      */
     public void setNotificationsOverScrollAmount(int overScrollAmount) {
-        mNotificationsScrim.setTranslationY(overScrollAmount);
+        if (mNotificationsScrim != null) mNotificationsScrim.setTranslationY(overScrollAmount);
     }
 
     /**
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/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/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/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/graphics/ImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
index ccd631e..8f66344 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
@@ -9,6 +9,7 @@
 import android.graphics.drawable.Icon
 import android.graphics.drawable.VectorDrawable
 import android.net.Uri
+import android.util.Size
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
@@ -78,12 +79,19 @@
         }
 
     @Test
-    fun invalidIcon_returnsNull() =
+    fun invalidIcon_loadDrawable_returnsNull() =
         testScope.runTest {
             assertThat(imageLoader.loadDrawable(Icon.createWithFilePath("this is broken"))).isNull()
         }
 
     @Test
+    fun invalidIcon_loadSize_returnsNull() =
+        testScope.runTest {
+            assertThat(imageLoader.loadSize(Icon.createWithFilePath("this is broken"), context))
+                .isNull()
+        }
+
+    @Test
     fun invalidIS_returnsNull() =
         testScope.runTest {
             assertThat(
@@ -172,6 +180,17 @@
         }
 
     @Test
+    fun validBitmapIcon_loadSize_returnsNull() =
+        testScope.runTest {
+            val bitmap =
+                BitmapFactory.decodeResource(
+                    context.resources,
+                    R.drawable.dessert_zombiegingerbread
+                )
+            assertThat(imageLoader.loadSize(Icon.createWithBitmap(bitmap), context)).isNull()
+        }
+
+    @Test
     fun validUriIcon_returnsBitmapDrawable() =
         testScope.runTest {
             val bitmap =
@@ -186,6 +205,17 @@
         }
 
     @Test
+    fun validUriIcon_returnsSize() =
+        testScope.runTest {
+            val drawable = context.resources.getDrawable(R.drawable.dessert_zombiegingerbread)
+            val uri =
+                "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+            val loadedSize =
+                imageLoader.loadSize(Icon.createWithContentUri(Uri.parse(uri)), context)
+            assertSizeEqualToDrawableSize(loadedSize, drawable)
+        }
+
+    @Test
     fun validDataIcon_returnsBitmapDrawable() =
         testScope.runTest {
             val bitmap =
@@ -205,6 +235,54 @@
         }
 
     @Test
+    fun validDataIcon_loadSize_returnsNull() =
+        testScope.runTest {
+            val bitmap =
+                BitmapFactory.decodeResource(
+                    context.resources,
+                    R.drawable.dessert_zombiegingerbread
+                )
+            val bos =
+                ByteArrayOutputStream(
+                    bitmap.byteCount * 2
+                ) // Compressed bitmap should be smaller than its source.
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos)
+
+            val array = bos.toByteArray()
+            assertThat(imageLoader.loadSize(Icon.createWithData(array, 0, array.size), context))
+                .isNull()
+        }
+
+    @Test
+    fun validResourceIcon_returnsBitmapDrawable() =
+        testScope.runTest {
+            val bitmap = context.resources.getDrawable(R.drawable.dessert_zombiegingerbread)
+            val loadedDrawable =
+                imageLoader.loadDrawable(
+                    Icon.createWithResource(
+                        "com.android.systemui.tests",
+                        R.drawable.dessert_zombiegingerbread
+                    )
+                )
+            assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap)
+        }
+
+    @Test
+    fun validResourceIcon_loadSize_returnsNull() =
+        testScope.runTest {
+            assertThat(
+                    imageLoader.loadSize(
+                        Icon.createWithResource(
+                            "com.android.systemui.tests",
+                            R.drawable.dessert_zombiegingerbread
+                        ),
+                        context
+                    )
+                )
+                .isNull()
+        }
+
+    @Test
     fun validSystemResourceIcon_returnsBitmapDrawable() =
         testScope.runTest {
             val bitmap =
@@ -217,6 +295,18 @@
         }
 
     @Test
+    fun validSystemResourceIcon_loadSize_returnsNull() =
+        testScope.runTest {
+            assertThat(
+                    imageLoader.loadSize(
+                        Icon.createWithResource("android", android.R.drawable.ic_dialog_alert),
+                        context
+                    )
+                )
+                .isNull()
+        }
+
+    @Test
     fun invalidDifferentPackageResourceIcon_returnsNull() =
         testScope.runTest {
             val loadedDrawable =
@@ -230,6 +320,20 @@
         }
 
     @Test
+    fun invalidDifferentPackageResourceIcon_loadSize_returnsNull() =
+        testScope.runTest {
+            assertThat(
+                    imageLoader.loadDrawable(
+                        Icon.createWithResource(
+                            "noooope.wrong.package",
+                            R.drawable.dessert_zombiegingerbread
+                        )
+                    )
+                )
+                .isNull()
+        }
+
+    @Test
     fun validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRatio() =
         testScope.runTest {
             val loadedDrawable =
@@ -343,4 +447,10 @@
         assertThat(actual?.width).isEqualTo(expected.width)
         assertThat(actual?.height).isEqualTo(expected.height)
     }
+
+    private fun assertSizeEqualToDrawableSize(actual: Size?, expected: Drawable) {
+        assertThat(actual).isNotNull()
+        assertThat(actual?.width).isEqualTo(expected.intrinsicWidth)
+        assertThat(actual?.height).isEqualTo(expected.intrinsicHeight)
+    }
 }
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/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..5a1450f 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,10 +159,7 @@
 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;
@@ -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;
 
@@ -227,7 +215,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 +241,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;
@@ -325,18 +311,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 +367,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());
 
@@ -448,6 +415,7 @@
                 mCommandQueue,
                 mMainExecutor,
                 mock(LogBuffer.class),
+                mock(WindowRootViewVisibilityInteractor.class),
                 mKeyguardStateController,
                 mStatusBarStateController,
                 mStatusBarKeyguardViewManager,
@@ -490,7 +458,6 @@
                 new FalsingCollectorFake(),
                 mBroadcastDispatcher,
                 mNotificationGutsManager,
-                notificationLogger,
                 mNotificationInterruptStateProvider,
                 new ShadeExpansionStateManager(),
                 mKeyguardViewMediator,
@@ -541,6 +508,7 @@
                 () -> mCentralSurfacesCommandQueueCallbacks,
                 mPluginManager,
                 mShadeController,
+                mWindowRootViewVisibilityInteractor,
                 mStatusBarKeyguardViewManager,
                 mViewMediatorCallback,
                 mInitController,
@@ -578,16 +546,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 +570,6 @@
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "sysui:GestureWakeLock");
         mCentralSurfaces.startKeyguard();
         mInitController.executePostInitTasks();
-        notificationLogger.setUpWithContainer(mNotificationListContainer);
         mCentralSurfaces.registerCallbacks();
     }
 
@@ -799,151 +759,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/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..bf9cdbe 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);
                         }
@@ -19014,6 +19023,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..0fc8aba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -95,8 +95,6 @@
 
         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 */,
@@ -120,8 +118,6 @@
 
         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 */,
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..ff35b19 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3296,13 +3296,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 +3377,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 +3408,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 +3536,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);
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/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..f8313e7 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1582,7 +1582,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 +2033,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 +3208,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 +3232,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 +4293,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..6c26531 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -831,4 +831,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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ac78429..c0c3ec4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4294,16 +4294,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 +4633,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 +4676,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);
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/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 307867c..d2adfdd 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4893,8 +4893,6 @@
             pw.print("]");
         }
         pw.println();
-        File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
-        pw.print(prefix); pw.print("  dataDir="); pw.println(dataDir.getAbsolutePath());
         if (pkg != null) {
             pw.print(prefix); pw.print("  versionName="); pw.println(pkg.getVersionName());
             pw.print(prefix); pw.print("  usesNonSdkApi="); pw.println(pkg.isNonSdkApiRequested());
@@ -5195,6 +5193,10 @@
             pw.print("      installReason=");
             pw.println(userState.getInstallReason());
 
+            final File dataDir = PackageInfoUtils.getDataDir(ps, user.id);
+            pw.print("      dataDir=");
+            pw.println(dataDir == null ? "null" : dataDir.getAbsolutePath());
+
             final PackageUserStateInternal pus = ps.readUserState(user.id);
             pw.print("      firstInstallTime=");
             date.setTime(pus.getFirstInstallTimeMillis());
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/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 31856f1..f4f03f4 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -544,11 +544,6 @@
      */
     public boolean compileLayouts(@NonNull PackageStateInternal ps, @NonNull AndroidPackage pkg) {
         try {
-            final String packageName = pkg.getPackageName();
-            final String apkPath = pkg.getSplits().get(0).getPath();
-            // TODO(b/143971007): Use a cross-user directory
-            File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
-            final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
             if (ps.isPrivileged() || pkg.isUseEmbeddedDex()
                     || pkg.isDefaultToDeviceProtectedStorage()) {
                 // Privileged apps prefer to load trusted code so they don't use compiled views.
@@ -558,6 +553,14 @@
                 // selinux permissions required for writing to user_de.
                 return false;
             }
+            final String packageName = pkg.getPackageName();
+            final String apkPath = pkg.getSplits().get(0).getPath();
+            final File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
+            if (dataDir == null) {
+                // The app is not installed on the target user and doesn't have a data dir
+                return false;
+            }
+            final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
             Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
                     ") to " + outDexFile);
             final long callingId = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/pm/dex/ViewCompiler.java b/services/core/java/com/android/server/pm/dex/ViewCompiler.java
index 6405ea5..c5b65a3 100644
--- a/services/core/java/com/android/server/pm/dex/ViewCompiler.java
+++ b/services/core/java/com/android/server/pm/dex/ViewCompiler.java
@@ -40,8 +40,11 @@
     public boolean compileLayouts(PackageStateInternal ps, String apkPath) {
         try {
             final String packageName = ps.getPackageName();
-            // TODO(b/143971007): Use a cross-user directory
-            File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
+            final File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
+            if (dataDir == null) {
+                // The app is not installed on the target user and doesn't have a data dir
+                return false;
+            }
             final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
             Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
                 ") to " + outDexFile);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 27812df..fc07909 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -443,7 +443,7 @@
 
         updateApplicationInfo(info, flags, state);
 
-        initForUser(info, pkg, userId);
+        initForUser(info, pkg, userId, state);
 
         // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
         PackageStateUnserialized pkgState = pkgSetting.getTransientState();
@@ -689,7 +689,7 @@
         info.splitDependencies = pkg.getSplitDependencies().size() == 0
                 ? null : pkg.getSplitDependencies();
 
-        initForUser(info, pkg, userId);
+        initForUser(info, pkg, userId, state);
 
         info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
         info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
@@ -1001,7 +1001,7 @@
     }
 
     private static void initForUser(ApplicationInfo output, AndroidPackage input,
-            @UserIdInt int userId) {
+            @UserIdInt int userId, PackageUserStateInternal state) {
         PackageImpl pkg = ((PackageImpl) input);
         String packageName = input.getPackageName();
         output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid()));
@@ -1011,6 +1011,12 @@
             return;
         }
 
+        if (!pkg.isSystem() && state.getCeDataInode() <= 0) {
+            // The data dir has been deleted
+            output.dataDir = null;
+            return;
+        }
+
         // For performance reasons, all these paths are built as strings
         if (userId == UserHandle.USER_SYSTEM) {
             output.credentialProtectedDataDir =
@@ -1045,7 +1051,7 @@
     // This duplicates the ApplicationInfo variant because it uses field assignment and the classes
     // don't inherit from each other, unfortunately. Consolidating logic would introduce overhead.
     private static void initForUser(InstrumentationInfo output, AndroidPackage input,
-            @UserIdInt int userId) {
+            @UserIdInt int userId, PackageUserStateInternal state) {
         PackageImpl pkg = ((PackageImpl) input);
         String packageName = input.getPackageName();
         if ("android".equals(packageName)) {
@@ -1053,6 +1059,12 @@
             return;
         }
 
+        if (!pkg.isSystem() && state.getCeDataInode() <= 0) {
+            // The data dir has been deleted
+            output.dataDir = null;
+            return;
+        }
+
         // For performance reasons, all these paths are built as strings
         if (userId == UserHandle.USER_SYSTEM) {
             output.credentialProtectedDataDir =
@@ -1084,12 +1096,21 @@
         }
     }
 
-    @NonNull
+    /**
+     * Returns the data dir of the app for the target user. Return null if the app isn't installed
+     * on the target user and doesn't have a data dir on the target user.
+     */
+    @Nullable
     public static File getDataDir(PackageStateInternal ps, int userId) {
         if ("android".equals(ps.getPackageName())) {
             return Environment.getDataSystemDirectory();
         }
 
+        if (!ps.isSystem() && ps.getUserStateOrDefault(userId).getCeDataInode() <= 0) {
+            // The data dir has been deleted
+            return null;
+        }
+
         if (ps.isDefaultToDeviceProtectedStorage()
                 && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
             return Environment.getDataUserDePackageDirectory(ps.getVolumeUuid(), userId,
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..234e3f4 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<>();
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..6085488 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) {
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/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/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index e2939c1..98c6c42 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -612,6 +612,9 @@
 
         final PackageSetting pkgSetting = scanResult.mPkgSetting;
         assertBasicPackageSetting(scanResult, packageName, isInstant, pkgSetting);
+        // pretend that the data dir has been set up already, so that the generated applicationInfo
+        // includes the expected data dir string
+        pkgSetting.setCeDataInode(/* ceDataInode= */100, /* userId= */0);
 
         final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
                 pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
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/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/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/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("-");