Merge "SF: fix metadata blob copy" into tm-dev
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index bb1d206..08a3d9a 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -246,7 +246,7 @@
         { OPT,      "events/gpu_mem/gpu_mem_total/enable" },
         { OPT,      "events/fastrpc/fastrpc_dma_stat/enable" },
     } },
-    { "thermal",  "Thermal event", 0, {
+    { "thermal",  "Thermal event", ATRACE_TAG_THERMAL, {
         { REQ,      "events/thermal/thermal_temperature/enable" },
         { OPT,      "events/thermal/cdev_update/enable" },
     } },
diff --git a/cmds/dumpstate/DumpPool.cpp b/cmds/dumpstate/DumpPool.cpp
index c2c8a72..4d3a67b 100644
--- a/cmds/dumpstate/DumpPool.cpp
+++ b/cmds/dumpstate/DumpPool.cpp
@@ -33,6 +33,20 @@
 
 const std::string DumpPool::PREFIX_TMPFILE_NAME = "dump-tmp.";
 
+
+void WaitForTask(std::future<std::string> future, const std::string& title, int out_fd) {
+    DurationReporter duration_reporter("Wait for " + title, true);
+
+    std::string result = future.get();
+    if (result.empty()) {
+        return;
+    }
+    DumpFileToFd(out_fd, title, result);
+    if (unlink(result.c_str())) {
+        MYLOGE("Failed to unlink (%s): %s\n", result.c_str(), strerror(errno));
+    }
+}
+
 DumpPool::DumpPool(const std::string& tmp_root) : tmp_root_(tmp_root), shutdown_(false),
         log_duration_(true) {
     assert(!tmp_root.empty());
@@ -40,31 +54,10 @@
 }
 
 DumpPool::~DumpPool() {
-    shutdown();
-}
-
-void DumpPool::start(int thread_counts) {
-    assert(thread_counts > 0);
-    assert(threads_.empty());
-    if (thread_counts > MAX_THREAD_COUNT) {
-        thread_counts = MAX_THREAD_COUNT;
-    }
-    MYLOGI("Start thread pool:%d", thread_counts);
-    shutdown_ = false;
-    for (int i = 0; i < thread_counts; i++) {
-        threads_.emplace_back(std::thread([=]() {
-            setThreadName(pthread_self(), i + 1);
-            loop();
-        }));
-    }
-}
-
-void DumpPool::shutdown() {
     std::unique_lock lock(lock_);
     if (shutdown_ || threads_.empty()) {
         return;
     }
-    futures_map_.clear();
     while (!tasks_.empty()) tasks_.pop();
 
     shutdown_ = true;
@@ -76,27 +69,22 @@
     }
     threads_.clear();
     deleteTempFiles(tmp_root_);
-    MYLOGI("shutdown thread pool");
+    MYLOGI("shutdown thread pool\n");
 }
 
-void DumpPool::waitForTask(const std::string& task_name, const std::string& title,
-        int out_fd) {
-    DurationReporter duration_reporter("Wait for " + task_name, true);
-    auto iterator = futures_map_.find(task_name);
-    if (iterator == futures_map_.end()) {
-        MYLOGW("Task %s does not exist", task_name.c_str());
-        return;
+void DumpPool::start(int thread_counts) {
+    assert(thread_counts > 0);
+    assert(threads_.empty());
+    if (thread_counts > MAX_THREAD_COUNT) {
+        thread_counts = MAX_THREAD_COUNT;
     }
-    Future future = iterator->second;
-    futures_map_.erase(iterator);
-
-    std::string result = future.get();
-    if (result.empty()) {
-        return;
-    }
-    DumpFileToFd(out_fd, title, result);
-    if (unlink(result.c_str())) {
-        MYLOGE("Failed to unlink (%s): %s\n", result.c_str(), strerror(errno));
+    MYLOGI("Start thread pool:%d\n", thread_counts);
+    shutdown_ = false;
+    for (int i = 0; i < thread_counts; i++) {
+        threads_.emplace_back(std::thread([=]() {
+            setThreadName(pthread_self(), i + 1);
+            loop();
+        }));
     }
 }
 
diff --git a/cmds/dumpstate/DumpPool.h b/cmds/dumpstate/DumpPool.h
index 0c3c2cc..9fb0fcc 100644
--- a/cmds/dumpstate/DumpPool.h
+++ b/cmds/dumpstate/DumpPool.h
@@ -18,7 +18,6 @@
 #define FRAMEWORK_NATIVE_CMD_DUMPPOOL_H_
 
 #include <future>
-#include <map>
 #include <queue>
 #include <string>
 
@@ -32,8 +31,26 @@
 class DumpPoolTest;
 
 /*
+ * Waits until the task is finished. Dumps the task results to the specified
+ * out_fd.
+ *
+ * |future| The task future.
+ * |title| Dump title string to the out_fd, an empty string for nothing.
+ * |out_fd| The target file to dump the result from the task.
+ */
+void WaitForTask(std::future<std::string> future, const std::string& title, int out_fd);
+
+/*
+ * Waits until the task is finished. Dumps the task results to the STDOUT_FILENO.
+ */
+
+inline void WaitForTask(std::future<std::string> future) {
+    WaitForTask(std::move(future), "", STDOUT_FILENO);
+}
+
+/*
  * A thread pool with the fixed number of threads to execute multiple dump tasks
- * simultaneously for the dumpstate. The dump task is a callable function. It
+ * simultaneously for dumpstate. The dump task is a callable function. It
  * could include a file descriptor as a parameter to redirect dump results, if
  * it needs to output results to the bugreport. This can avoid messing up
  * bugreport's results when multiple dump tasks are running at the same time.
@@ -44,13 +61,16 @@
  * }
  * ...
  * DumpPool pool(tmp_root);
- * pool.enqueueTaskWithFd("TaskName", &DumpFoo, std::placeholders::_1);
+ * auto task = pool.enqueueTaskWithFd("TaskName", &DumpFoo, std::placeholders::_1);
  * ...
- * pool.waitForTask("TaskName");
+ * WaitForTask(task);
  *
  * DumpFoo is a callable function included a out_fd parameter. Using the
  * enqueueTaskWithFd method in DumpPool to enqueue the task to the pool. The
  * std::placeholders::_1 is a placeholder for DumpPool to pass a fd argument.
+ *
+ * std::futures returned by `enqueueTask*()` must all have their `get` methods
+ * called, or have been destroyed before the DumpPool itself is destroyed.
  */
 class DumpPool {
   friend class android::os::dumpstate::DumpPoolTest;
@@ -63,6 +83,12 @@
      * files.
      */
     explicit DumpPool(const std::string& tmp_root);
+
+    /*
+     * Will waits until all threads exit the loop. Destroying DumpPool before destroying the
+     * associated std::futures created by `enqueueTask*` will cause an abort on Android because
+     * Android is built with `-fno-exceptions`.
+     */
     ~DumpPool();
 
     /*
@@ -73,68 +99,47 @@
     void start(int thread_counts = MAX_THREAD_COUNT);
 
     /*
-     * Requests to shutdown the pool and waits until all threads exit the loop.
-     */
-    void shutdown();
-
-    /*
      * Adds a task into the queue of the thread pool.
      *
-     * |task_name| The name of the task. It's also the title of the
+     * |duration_title| The name of the task. It's also the title of the
      * DurationReporter log.
      * |f| Callable function to execute the task.
      * |args| A list of arguments.
      *
      * TODO(b/164369078): remove this api to have just one enqueueTask for consistency.
      */
-    template<class F, class... Args> void enqueueTask(const std::string& task_name, F&& f,
-            Args&&... args) {
+    template<class F, class... Args>
+    std::future<std::string> enqueueTask(const std::string& duration_title, F&& f, Args&&... args) {
         std::function<void(void)> func = std::bind(std::forward<F>(f),
                 std::forward<Args>(args)...);
-        futures_map_[task_name] = post(task_name, func);
+        auto future = post(duration_title, func);
         if (threads_.empty()) {
             start();
         }
+        return future;
     }
 
     /*
      * Adds a task into the queue of the thread pool. The task takes a file
      * descriptor as a parameter to redirect dump results to a temporary file.
      *
-     * |task_name| The name of the task. It's also the title of the
-     * DurationReporter log.
+     * |duration_title| The title of the DurationReporter log.
      * |f| Callable function to execute the task.
      * |args| A list of arguments. A placeholder std::placeholders::_1 as a fd
      * argument needs to be included here.
      */
-    template<class F, class... Args> void enqueueTaskWithFd(const std::string& task_name, F&& f,
-            Args&&... args) {
+    template<class F, class... Args> std::future<std::string> enqueueTaskWithFd(
+            const std::string& duration_title, F&& f, Args&&... args) {
         std::function<void(int)> func = std::bind(std::forward<F>(f),
                 std::forward<Args>(args)...);
-        futures_map_[task_name] = post(task_name, func);
+        auto future = post(duration_title, func);
         if (threads_.empty()) {
             start();
         }
+        return future;
     }
 
     /*
-     * Waits until the task is finished. Dumps the task results to the STDOUT_FILENO.
-     */
-    void waitForTask(const std::string& task_name) {
-        waitForTask(task_name, "", STDOUT_FILENO);
-    }
-
-    /*
-     * Waits until the task is finished. Dumps the task results to the specified
-     * out_fd.
-     *
-     * |task_name| The name of the task.
-     * |title| Dump title string to the out_fd, an empty string for nothing.
-     * |out_fd| The target file to dump the result from the task.
-     */
-    void waitForTask(const std::string& task_name, const std::string& title, int out_fd);
-
-    /*
      * Deletes temporary files created by DumpPool.
      */
     void deleteTempFiles();
@@ -143,22 +148,22 @@
 
   private:
     using Task = std::packaged_task<std::string()>;
-    using Future = std::shared_future<std::string>;
 
     template<class T> void invokeTask(T dump_func, const std::string& duration_title, int out_fd);
 
-    template<class T> Future post(const std::string& task_name, T dump_func) {
+    template<class T>
+    std::future<std::string> post(const std::string& duration_title, T dump_func) {
         Task packaged_task([=]() {
             std::unique_ptr<TmpFile> tmp_file_ptr = createTempFile();
             if (!tmp_file_ptr) {
                 return std::string("");
             }
-            invokeTask(dump_func, task_name, tmp_file_ptr->fd.get());
+            invokeTask(dump_func, duration_title, tmp_file_ptr->fd.get());
             fsync(tmp_file_ptr->fd.get());
             return std::string(tmp_file_ptr->path);
         });
         std::unique_lock lock(lock_);
-        auto future = packaged_task.get_future().share();
+        auto future = packaged_task.get_future();
         tasks_.push(std::move(packaged_task));
         condition_variable_.notify_one();
         return future;
@@ -194,7 +199,6 @@
 
     std::vector<std::thread> threads_;
     std::queue<Task> tasks_;
-    std::map<std::string, Future> futures_map_;
 
     DISALLOW_COPY_AND_ASSIGN(DumpPool);
 };
diff --git a/cmds/dumpstate/DumpstateUtil.cpp b/cmds/dumpstate/DumpstateUtil.cpp
index c833d0e..aa42541 100644
--- a/cmds/dumpstate/DumpstateUtil.cpp
+++ b/cmds/dumpstate/DumpstateUtil.cpp
@@ -48,11 +48,26 @@
     sigemptyset(&child_mask);
     sigaddset(&child_mask, SIGCHLD);
 
+    // block SIGCHLD before we check if a process has exited
     if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
         printf("*** sigprocmask failed: %s\n", strerror(errno));
         return false;
     }
 
+    // if the child has exited already, handle and reset signals before leaving
+    pid_t child_pid = waitpid(pid, status, WNOHANG);
+    if (child_pid != pid) {
+        if (child_pid > 0) {
+            printf("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid);
+            sigprocmask(SIG_SETMASK, &old_mask, nullptr);
+            return false;
+        }
+    } else {
+        sigprocmask(SIG_SETMASK, &old_mask, nullptr);
+        return true;
+    }
+
+    // wait for a SIGCHLD
     timespec ts;
     ts.tv_sec = MSEC_TO_SEC(timeout_ms);
     ts.tv_nsec = (timeout_ms % 1000) * 1000000;
@@ -76,7 +91,7 @@
         return false;
     }
 
-    pid_t child_pid = waitpid(pid, status, WNOHANG);
+    child_pid = waitpid(pid, status, WNOHANG);
     if (child_pid != pid) {
         if (child_pid != -1) {
             printf("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid);
@@ -232,7 +247,6 @@
             dprintf(out_fd, "*** Error dumping %s (%s): %s\n", path.c_str(), title.c_str(),
                     strerror(err));
         }
-        fsync(out_fd);
         return -1;
     }
     return DumpFileFromFdToFd(title, path, fd.get(), out_fd, PropertiesHelper::IsDryRun());
@@ -280,7 +294,6 @@
 
     if (!title.empty()) {
         dprintf(fd, "------ %s (%s) ------\n", title.c_str(), command);
-        fsync(fd);
     }
 
     const std::string& logging_message = options.LoggingMessage();
@@ -299,14 +312,13 @@
             // There is no title, but we should still print a dry-run message
             dprintf(fd, "%s: skipped on dry run\n", command_string.c_str());
         }
-        fsync(fd);
         return 0;
     }
 
     const char* path = args[0];
 
     uint64_t start = Nanotime();
-    pid_t pid = fork();
+    pid_t pid = vfork();
 
     /* handle error case */
     if (pid < 0) {
@@ -323,7 +335,7 @@
                         strerror(errno));
             }
             MYLOGE("*** could not drop root before running %s: %s\n", command, strerror(errno));
-            return -1;
+            _exit(EXIT_FAILURE);
         }
 
         if (options.ShouldCloseAllFileDescriptorsOnExec()) {
@@ -376,7 +388,6 @@
     /* handle parent case */
     int status;
     bool ret = waitpid_with_timeout(pid, options.TimeoutInMs(), &status);
-    fsync(fd);
 
     uint64_t elapsed = Nanotime() - start;
     if (!ret) {
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 1003aae..0e9ce89 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -120,6 +120,7 @@
 using android::os::dumpstate::DumpPool;
 using android::os::dumpstate::PropertiesHelper;
 using android::os::dumpstate::TaskQueue;
+using android::os::dumpstate::WaitForTask;
 
 // Keep in sync with
 // frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -218,9 +219,9 @@
     RUN_SLOW_FUNCTION_AND_LOG(log_title, func_ptr, __VA_ARGS__);               \
     RETURN_IF_USER_DENIED_CONSENT();
 
-#define WAIT_TASK_WITH_CONSENT_CHECK(task_name, pool_ptr) \
+#define WAIT_TASK_WITH_CONSENT_CHECK(future) \
     RETURN_IF_USER_DENIED_CONSENT();                      \
-    pool_ptr->waitForTask(task_name);                     \
+    WaitForTask(future);                     \
     RETURN_IF_USER_DENIED_CONSENT();
 
 static const char* WAKE_LOCK_NAME = "dumpstate_wakelock";
@@ -1549,15 +1550,18 @@
     DurationReporter duration_reporter("DUMPSTATE");
 
     // Enqueue slow functions into the thread pool, if the parallel run is enabled.
+    std::future<std::string> dump_hals, dump_incident_report, dump_board, dump_checkins;
     if (ds.dump_pool_) {
         // Pool was shutdown in DumpstateDefaultAfterCritical method in order to
         // drop root user. Restarts it with two threads for the parallel run.
         ds.dump_pool_->start(/* thread_counts = */2);
 
-        ds.dump_pool_->enqueueTaskWithFd(DUMP_HALS_TASK, &DumpHals, _1);
-        ds.dump_pool_->enqueueTask(DUMP_INCIDENT_REPORT_TASK, &DumpIncidentReport);
-        ds.dump_pool_->enqueueTaskWithFd(DUMP_BOARD_TASK, &Dumpstate::DumpstateBoard, &ds, _1);
-        ds.dump_pool_->enqueueTaskWithFd(DUMP_CHECKINS_TASK, &DumpCheckins, _1);
+        dump_hals = ds.dump_pool_->enqueueTaskWithFd(DUMP_HALS_TASK, &DumpHals, _1);
+        dump_incident_report = ds.dump_pool_->enqueueTask(
+            DUMP_INCIDENT_REPORT_TASK, &DumpIncidentReport);
+        dump_board = ds.dump_pool_->enqueueTaskWithFd(
+            DUMP_BOARD_TASK, &Dumpstate::DumpstateBoard, &ds, _1);
+        dump_checkins = ds.dump_pool_->enqueueTaskWithFd(DUMP_CHECKINS_TASK, &DumpCheckins, _1);
     }
 
     // Dump various things. Note that anything that takes "long" (i.e. several seconds) should
@@ -1591,7 +1595,7 @@
                                          CommandOptions::AS_ROOT);
 
     if (ds.dump_pool_) {
-        WAIT_TASK_WITH_CONSENT_CHECK(DUMP_HALS_TASK, ds.dump_pool_);
+        WAIT_TASK_WITH_CONSENT_CHECK(std::move(dump_hals));
     } else {
         RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK_AND_LOG(DUMP_HALS_TASK, DumpHals);
     }
@@ -1688,7 +1692,7 @@
     ds.AddDir(SNAPSHOTCTL_LOG_DIR, false);
 
     if (ds.dump_pool_) {
-        WAIT_TASK_WITH_CONSENT_CHECK(DUMP_BOARD_TASK, ds.dump_pool_);
+        WAIT_TASK_WITH_CONSENT_CHECK(std::move(dump_board));
     } else {
         RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK_AND_LOG(DUMP_BOARD_TASK, ds.DumpstateBoard);
     }
@@ -1717,7 +1721,7 @@
     ds.AddDir("/data/misc/bluetooth/logs", true);
 
     if (ds.dump_pool_) {
-        WAIT_TASK_WITH_CONSENT_CHECK(DUMP_CHECKINS_TASK, ds.dump_pool_);
+        WAIT_TASK_WITH_CONSENT_CHECK(std::move(dump_checkins));
     } else {
         RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK_AND_LOG(DUMP_CHECKINS_TASK, DumpCheckins);
     }
@@ -1751,7 +1755,7 @@
     dump_frozen_cgroupfs();
 
     if (ds.dump_pool_) {
-        WAIT_TASK_WITH_CONSENT_CHECK(DUMP_INCIDENT_REPORT_TASK, ds.dump_pool_);
+        WAIT_TASK_WITH_CONSENT_CHECK(std::move(dump_incident_report));
     } else {
         RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK_AND_LOG(DUMP_INCIDENT_REPORT_TASK,
                 DumpIncidentReport);
@@ -1776,6 +1780,7 @@
     time_t logcat_ts = time(nullptr);
 
     /* collect stack traces from Dalvik and native processes (needs root) */
+    std::future<std::string> dump_traces;
     if (dump_pool_) {
         RETURN_IF_USER_DENIED_CONSENT();
         // One thread is enough since we only need to enqueue DumpTraces here.
@@ -1783,7 +1788,8 @@
 
         // DumpTraces takes long time, post it to the another thread in the
         // pool, if pool is available
-        dump_pool_->enqueueTask(DUMP_TRACES_TASK, &Dumpstate::DumpTraces, &ds, &dump_traces_path);
+        dump_traces = dump_pool_->enqueueTask(
+            DUMP_TRACES_TASK, &Dumpstate::DumpTraces, &ds, &dump_traces_path);
     } else {
         RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK_AND_LOG(DUMP_TRACES_TASK, ds.DumpTraces,
                 &dump_traces_path);
@@ -1832,12 +1838,11 @@
 
     if (dump_pool_) {
         RETURN_IF_USER_DENIED_CONSENT();
-        dump_pool_->waitForTask(DUMP_TRACES_TASK);
+        WaitForTask(std::move(dump_traces));
 
-        // Current running thread in the pool is the root user also. Shutdown
-        // the pool and restart later to ensure all threads in the pool could
-        // drop the root user.
-        dump_pool_->shutdown();
+        // Current running thread in the pool is the root user also. Delete
+        // the pool and make a new one later to ensure none of threads in the pool are root.
+        dump_pool_ = std::make_unique<DumpPool>(bugreport_internal_dir_);
     }
     if (!DropRootUser()) {
         return Dumpstate::RunStatus::ERROR;
@@ -1868,8 +1873,9 @@
     } else {
         // DumpHals takes long time, post it to the another thread in the pool,
         // if pool is available.
+        std::future<std::string> dump_hals;
         if (ds.dump_pool_) {
-            ds.dump_pool_->enqueueTaskWithFd(DUMP_HALS_TASK, &DumpHals, _1);
+            dump_hals = ds.dump_pool_->enqueueTaskWithFd(DUMP_HALS_TASK, &DumpHals, _1);
         }
         // Contains various system properties and process startup info.
         do_dmesg();
@@ -1879,7 +1885,7 @@
         DoKmsg();
         // DumpHals contains unrelated hardware info (camera, NFC, biometrics, ...).
         if (ds.dump_pool_) {
-            ds.dump_pool_->waitForTask(DUMP_HALS_TASK);
+            WaitForTask(std::move(dump_hals));
         } else {
             RUN_SLOW_FUNCTION_AND_LOG(DUMP_HALS_TASK, DumpHals);
         }
@@ -1913,12 +1919,14 @@
 
     // Starts thread pool after the root user is dropped, and two additional threads
     // are created for DumpHals in the DumpstateRadioCommon and DumpstateBoard.
+    std::future<std::string> dump_board;
     if (ds.dump_pool_) {
         ds.dump_pool_->start(/*thread_counts =*/2);
 
         // DumpstateBoard takes long time, post it to the another thread in the pool,
         // if pool is available.
-        ds.dump_pool_->enqueueTaskWithFd(DUMP_BOARD_TASK, &Dumpstate::DumpstateBoard, &ds, _1);
+        dump_board = ds.dump_pool_->enqueueTaskWithFd(
+            DUMP_BOARD_TASK, &Dumpstate::DumpstateBoard, &ds, _1);
     }
 
     DumpstateRadioCommon(include_sensitive_info);
@@ -2001,7 +2009,7 @@
     printf("========================================================\n");
 
     if (ds.dump_pool_) {
-        ds.dump_pool_->waitForTask(DUMP_BOARD_TASK);
+        WaitForTask(std::move(dump_board));
     } else {
         RUN_SLOW_FUNCTION_AND_LOG(DUMP_BOARD_TASK, ds.DumpstateBoard);
     }
@@ -3225,8 +3233,7 @@
 
 void Dumpstate::ShutdownDumpPool() {
     if (dump_pool_) {
-        dump_pool_->shutdown();
-        dump_pool_ = nullptr;
+        dump_pool_.reset();
     }
     if (zip_entry_tasks_) {
         zip_entry_tasks_->run(/* do_cancel = */true);
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 4a99cd8..ee6b1ae 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -478,7 +478,7 @@
     // This is useful for debugging.
     std::string log_path_;
 
-    // Full path of the bugreport file, be it zip or text, inside bugreport_internal_dir_.
+    // Full path of the bugreport zip file inside bugreport_internal_dir_.
     std::string path_;
 
     // Full path of the file containing the screenshot (when requested).
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index 42beb2b..70b4e5c 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -1720,14 +1720,13 @@
         dprintf(out_fd, "C");
     };
     setLogDuration(/* log_duration = */false);
-    dump_pool_->enqueueTaskWithFd(/* task_name = */"1", dump_func_1, std::placeholders::_1);
-    dump_pool_->enqueueTaskWithFd(/* task_name = */"2", dump_func_2, std::placeholders::_1);
-    dump_pool_->enqueueTaskWithFd(/* task_name = */"3", dump_func_3, std::placeholders::_1);
+    auto t1 = dump_pool_->enqueueTaskWithFd("", dump_func_1, std::placeholders::_1);
+    auto t2 = dump_pool_->enqueueTaskWithFd("", dump_func_2, std::placeholders::_1);
+    auto t3 = dump_pool_->enqueueTaskWithFd("", dump_func_3, std::placeholders::_1);
 
-    dump_pool_->waitForTask("1", "", out_fd_.get());
-    dump_pool_->waitForTask("2", "", out_fd_.get());
-    dump_pool_->waitForTask("3", "", out_fd_.get());
-    dump_pool_->shutdown();
+    WaitForTask(std::move(t1), "", out_fd_.get());
+    WaitForTask(std::move(t2), "", out_fd_.get());
+    WaitForTask(std::move(t3), "", out_fd_.get());
 
     std::string result;
     ReadFileToString(out_path_, &result);
@@ -1741,9 +1740,8 @@
         run_1 = true;
     };
 
-    dump_pool_->enqueueTask(/* task_name = */"1", dump_func_1);
-    dump_pool_->waitForTask("1", "", out_fd_.get());
-    dump_pool_->shutdown();
+    auto t1 = dump_pool_->enqueueTask(/* duration_title = */"1", dump_func_1);
+    WaitForTask(std::move(t1), "", out_fd_.get());
 
     std::string result;
     ReadFileToString(out_path_, &result);
@@ -1752,27 +1750,6 @@
     EXPECT_THAT(getTempFileCounts(kTestDataPath), Eq(0));
 }
 
-TEST_F(DumpPoolTest, Shutdown_withoutCrash) {
-    bool run_1 = false;
-    auto dump_func_1 = [&]() {
-        run_1 = true;
-    };
-    auto dump_func = []() {
-        sleep(1);
-    };
-
-    dump_pool_->start(/* thread_counts = */1);
-    dump_pool_->enqueueTask(/* task_name = */"1", dump_func_1);
-    dump_pool_->enqueueTask(/* task_name = */"2", dump_func);
-    dump_pool_->enqueueTask(/* task_name = */"3", dump_func);
-    dump_pool_->enqueueTask(/* task_name = */"4", dump_func);
-    dump_pool_->waitForTask("1", "", out_fd_.get());
-    dump_pool_->shutdown();
-
-    EXPECT_TRUE(run_1);
-    EXPECT_THAT(getTempFileCounts(kTestDataPath), Eq(0));
-}
-
 class TaskQueueTest : public DumpstateBaseTest {
 public:
     void SetUp() {
diff --git a/cmds/installd/Android.bp b/cmds/installd/Android.bp
index fd38ddf..c9f680b 100644
--- a/cmds/installd/Android.bp
+++ b/cmds/installd/Android.bp
@@ -10,7 +10,6 @@
 cc_defaults {
     name: "installd_defaults",
 
-    cpp_std: "c++2a",
     cflags: [
         "-Wall",
         "-Werror",
@@ -42,7 +41,6 @@
         "libbinder",
         "libcrypto",
         "libcutils",
-        "libext2_uuid",
         "liblog",
         "liblogwrap",
         "libprocessgroup",
@@ -53,6 +51,7 @@
     ],
     static_libs: [
         "libasync_safe",
+        "libext2_uuid",
     ],
     export_shared_lib_headers: [
         "libbinder",
@@ -241,8 +240,6 @@
 
 cc_binary {
     name: "otapreopt",
-
-    cpp_std: "c++2a",
     cflags: [
         "-Wall",
         "-Werror",
@@ -266,13 +263,13 @@
         "libasync_safe",
         "libdiskusage",
         "libotapreoptparameters",
+        "libext2_uuid",
     ],
 
     shared_libs: [
         "libbase",
         "libcrypto",
         "libcutils",
-        "libext2_uuid",
         "liblog",
         "liblogwrap",
         "libprocessgroup",
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index f0dcd02..e7bde21 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -18,13 +18,9 @@
 
 #define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER
 
-#include <algorithm>
 #include <errno.h>
-#include <fstream>
 #include <fts.h>
-#include <functional>
 #include <inttypes.h>
-#include <regex>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -40,6 +36,11 @@
 #include <sys/wait.h>
 #include <sys/xattr.h>
 #include <unistd.h>
+#include <algorithm>
+#include <filesystem>
+#include <fstream>
+#include <functional>
+#include <regex>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -720,6 +721,76 @@
             return error("Failed to prepare profiles for " + packageName);
         }
     }
+
+    // TODO(b/220095381): Due to boot time regression, we have omitted call to
+    // createAppDirectoryForSupplementalData from here temporarily (unless it's for testing)
+    if (uuid_ != nullptr && strcmp(uuid_, "TEST") == 0) {
+        auto status = createAppDirectoryForSupplementalData(uuid, packageName, userId, appId,
+                                                            previousAppId, seInfo, flags);
+        if (!status.isOk()) {
+            return status;
+        }
+    }
+
+    return ok();
+}
+
+/**
+ * Responsible for creating /data/user/0/supplemental/<app-name> directory and other
+ * app level sub directories, such as ./shared
+ */
+binder::Status InstalldNativeService::createAppDirectoryForSupplementalData(
+        const std::optional<std::string>& uuid, const std::string& packageName, int32_t userId,
+        int32_t appId, int32_t previousAppId, const std::string& seInfo, int32_t flags) {
+    int32_t supplementalUid = multiuser_get_supplemental_uid(userId, appId);
+    if (supplementalUid == -1) {
+        // There no valid supplemental process for this app. Skip creation of data directory
+        return ok();
+    }
+
+    // TODO(b/211763739): what if uuid is not nullptr or TEST?
+    const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+
+    constexpr int storageFlags[2] = {FLAG_STORAGE_CE, FLAG_STORAGE_DE};
+    for (int i = 0; i < 2; i++) {
+        int currentFlag = storageFlags[i];
+        if ((flags & currentFlag) == 0) {
+            continue;
+        }
+        bool isCeData = (currentFlag == FLAG_STORAGE_CE);
+
+        // /data/misc_{ce,de}/<user-id>/supplemental directory gets created by vold
+        // during user creation
+
+        // Prepare the app directory
+        auto appPath = create_data_misc_supplemental_package_path(uuid_, isCeData, userId,
+                                                                  packageName.c_str());
+        if (prepare_app_dir(appPath, 0751, AID_SYSTEM)) {
+            return error("Failed to prepare " + appPath);
+        }
+
+        // Now prepare the shared directory which will be accessible by all codes
+        auto sharedPath = create_data_misc_supplemental_shared_path(uuid_, isCeData, userId,
+                                                                    packageName.c_str());
+
+        int32_t previousSupplementalUid = multiuser_get_supplemental_uid(userId, previousAppId);
+        int32_t cacheGid = multiuser_get_cache_gid(userId, appId);
+        if (cacheGid == -1) {
+            return exception(binder::Status::EX_ILLEGAL_STATE,
+                             StringPrintf("cacheGid cannot be -1 for supplemental data"));
+        }
+        auto status = createAppDataDirs(sharedPath, supplementalUid, &previousSupplementalUid,
+                                        cacheGid, seInfo, 0700);
+        if (!status.isOk()) {
+            return status;
+        }
+
+        // TODO(b/211763739): We also need to handle art profile creations
+
+        // TODO(b/211763739): And return the CE inode of the supplemental root directory and
+        // app directory under it so we can clear contents while CE storage is locked
+    }
+
     return ok();
 }
 
@@ -994,15 +1065,15 @@
             }
 
             auto path = StringPrintf("%s/Android/data/%s", extPath.c_str(), pkgname);
-            if (rename_delete_dir_contents_and_dir(path, true) != 0) {
+            if (delete_dir_contents_and_dir(path, true) != 0) {
                 res = error("Failed to delete contents of " + path);
             }
             path = StringPrintf("%s/Android/media/%s", extPath.c_str(), pkgname);
-            if (rename_delete_dir_contents_and_dir(path, true) != 0) {
+            if (delete_dir_contents_and_dir(path, true) != 0) {
                 res = error("Failed to delete contents of " + path);
             }
             path = StringPrintf("%s/Android/obb/%s", extPath.c_str(), pkgname);
-            if (rename_delete_dir_contents_and_dir(path, true) != 0) {
+            if (delete_dir_contents_and_dir(path, true) != 0) {
                 res = error("Failed to delete contents of " + path);
             }
         }
@@ -1550,27 +1621,27 @@
     binder::Status res = ok();
     if (flags & FLAG_STORAGE_DE) {
         auto path = create_data_user_de_path(uuid_, userId);
-        if (rename_delete_dir_contents_and_dir(path, true) != 0) {
+        if (delete_dir_contents_and_dir(path, true) != 0) {
             res = error("Failed to delete " + path);
         }
         if (uuid_ == nullptr) {
             path = create_data_misc_legacy_path(userId);
-            if (rename_delete_dir_contents_and_dir(path, true) != 0) {
+            if (delete_dir_contents_and_dir(path, true) != 0) {
                 res = error("Failed to delete " + path);
             }
             path = create_primary_cur_profile_dir_path(userId);
-            if (rename_delete_dir_contents_and_dir(path, true) != 0) {
+            if (delete_dir_contents_and_dir(path, true) != 0) {
                 res = error("Failed to delete " + path);
             }
         }
     }
     if (flags & FLAG_STORAGE_CE) {
         auto path = create_data_user_ce_path(uuid_, userId);
-        if (rename_delete_dir_contents_and_dir(path, true) != 0) {
+        if (delete_dir_contents_and_dir(path, true) != 0) {
             res = error("Failed to delete " + path);
         }
         path = findDataMediaPath(uuid, userId);
-        if (rename_delete_dir_contents_and_dir(path, true) != 0) {
+        if (delete_dir_contents_and_dir(path, true) != 0) {
             res = error("Failed to delete " + path);
         }
     }
diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h
index ecc60b5..6b4ba1f 100644
--- a/cmds/installd/InstalldNativeService.h
+++ b/cmds/installd/InstalldNativeService.h
@@ -47,14 +47,9 @@
             int32_t flags);
 
     binder::Status createAppData(const std::optional<std::string>& uuid,
-            const std::string& packageName, int32_t userId, int32_t flags, int32_t appId,
-            int32_t previousAppId, const std::string& seInfo, int32_t targetSdkVersion,
-            int64_t* _aidl_return);
-    binder::Status createAppDataLocked(const std::optional<std::string>& uuid,
-                                       const std::string& packageName, int32_t userId,
-                                       int32_t flags, int32_t appId, int32_t previousAppId,
-                                       const std::string& seInfo, int32_t targetSdkVersion,
-                                       int64_t* _aidl_return);
+                                 const std::string& packageName, int32_t userId, int32_t flags,
+                                 int32_t appId, int32_t previousAppId, const std::string& seInfo,
+                                 int32_t targetSdkVersion, int64_t* _aidl_return);
 
     binder::Status createAppData(
             const android::os::CreateAppDataArgs& args,
@@ -203,6 +198,18 @@
     std::unordered_map<uid_t, int64_t> mCacheQuotas;
 
     std::string findDataMediaPath(const std::optional<std::string>& uuid, userid_t userid);
+
+    binder::Status createAppDataLocked(const std::optional<std::string>& uuid,
+                                       const std::string& packageName, int32_t userId,
+                                       int32_t flags, int32_t appId, int32_t previousAppId,
+                                       const std::string& seInfo, int32_t targetSdkVersion,
+                                       int64_t* _aidl_return);
+
+    binder::Status createAppDirectoryForSupplementalData(const std::optional<std::string>& uuid,
+                                                         const std::string& packageName,
+                                                         int32_t userId, int32_t appId,
+                                                         int32_t previousAppId,
+                                                         const std::string& seInfo, int32_t flags);
 };
 
 }  // namespace installd
diff --git a/cmds/installd/tests/Android.bp b/cmds/installd/tests/Android.bp
index a16587e..e390bab 100644
--- a/cmds/installd/tests/Android.bp
+++ b/cmds/installd/tests/Android.bp
@@ -8,54 +8,56 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_defaults {
-    name: "installd_tests_defaults",
+cc_test {
+    name: "installd_utils_test",
     test_suites: ["device-tests"],
     clang: true,
-    cpp_std: "c++2a",
+    srcs: ["installd_utils_test.cpp"],
     cflags: [
         "-Wall",
         "-Werror",
     ],
     shared_libs: [
         "libbase",
-        "libcutils",
-        "libext2_uuid",
         "libutils",
+        "libcutils",
     ],
     static_libs: [
-        "liblog",
-    ],
-}
-
-cc_test {
-    name: "installd_utils_test",
-    defaults: ["installd_tests_defaults"],
-    srcs: ["installd_utils_test.cpp"],
-    static_libs: [
         "libasync_safe",
         "libdiskusage",
+        "libext2_uuid",
         "libinstalld",
+        "liblog",
     ],
     test_config: "installd_utils_test.xml",
 }
 
 cc_test {
     name: "installd_cache_test",
-    defaults: ["installd_tests_defaults"],
+    test_suites: ["device-tests"],
+    clang: true,
     srcs: ["installd_cache_test.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
     shared_libs: [
+        "libbase",
         "libbinder",
         "libcrypto",
+        "libcutils",
         "libprocessgroup",
         "libselinux",
+        "libutils",
         "server_configurable_flags",
     ],
     static_libs: [
         "libasync_safe",
         "libdiskusage",
+        "libext2_uuid",
         "libinstalld",
         "libziparchive",
+        "liblog",
         "liblogwrap",
     ],
     test_config: "installd_cache_test.xml",
@@ -78,21 +80,31 @@
 
 cc_test {
     name: "installd_service_test",
-    defaults: ["installd_tests_defaults"],
+    test_suites: ["device-tests"],
+    clang: true,
     srcs: ["installd_service_test.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
     shared_libs: [
+        "libbase",
         "libbinder",
         "libcrypto",
+        "libcutils",
         "libprocessgroup",
         "libselinux",
+        "libutils",
         "packagemanager_aidl-cpp",
         "server_configurable_flags",
     ],
     static_libs: [
         "libasync_safe",
         "libdiskusage",
+        "libext2_uuid",
         "libinstalld",
         "libziparchive",
+        "liblog",
         "liblogwrap",
     ],
     test_config: "installd_service_test.xml",
@@ -115,19 +127,29 @@
 
 cc_test {
     name: "installd_dexopt_test",
-    defaults: ["installd_tests_defaults"],
+    test_suites: ["device-tests"],
+    clang: true,
     srcs: ["installd_dexopt_test.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
     shared_libs: [
+        "libbase",
         "libbinder",
         "libcrypto",
+        "libcutils",
         "libprocessgroup",
         "libselinux",
+        "libutils",
         "server_configurable_flags",
     ],
     static_libs: [
         "libasync_safe",
         "libdiskusage",
+        "libext2_uuid",
         "libinstalld",
+        "liblog",
         "liblogwrap",
         "libziparchive",
         "libz",
@@ -152,21 +174,42 @@
 
 cc_test {
     name: "installd_otapreopt_test",
-    defaults: ["installd_tests_defaults"],
+    test_suites: ["device-tests"],
+    clang: true,
     srcs: ["installd_otapreopt_test.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
     shared_libs: [
+        "libbase",
+        "libcutils",
+        "libutils",
         "server_configurable_flags",
     ],
     static_libs: [
+        "liblog",
         "libotapreoptparameters",
     ],
 }
 
 cc_test {
     name: "installd_file_test",
-    defaults: ["installd_tests_defaults"],
+    test_suites: ["device-tests"],
+    clang: true,
     srcs: ["installd_file_test.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "libutils",
+    ],
     static_libs: [
+        "libext2_uuid",
         "libinstalld",
+        "liblog",
     ],
 }
diff --git a/cmds/installd/tests/installd_service_test.cpp b/cmds/installd/tests/installd_service_test.cpp
index 806797f..703a096 100644
--- a/cmds/installd/tests/installd_service_test.cpp
+++ b/cmds/installd/tests/installd_service_test.cpp
@@ -74,8 +74,14 @@
 }
 namespace installd {
 
-constexpr const char* kTestUuid = "TEST";
-constexpr const char* kTestPath = "/data/local/tmp/user/0";
+static constexpr const char* kTestUuid = "TEST";
+static constexpr const char* kTestPath = "/data/local/tmp/user/0";
+static constexpr const uid_t kSystemUid = 1000;
+static constexpr const int32_t kTestUserId = 0;
+static constexpr const uid_t kTestAppId = 19999;
+
+const gid_t kTestAppUid = multiuser_get_uid(kTestUserId, kTestAppId);
+const uid_t kTestAppSupplementalUid = multiuser_get_supplemental_uid(kTestUserId, kTestAppId);
 
 #define FLAG_FORCE InstalldNativeService::FLAG_FORCE
 
@@ -156,7 +162,7 @@
 }
 
 static bool exists_renamed_deleted_dir() {
-    return find_file(kTestPath, [](std::string_view name, bool is_dir) {
+    return find_file(kTestPath, [](const std::string& name, bool is_dir) {
         return is_dir && is_renamed_deleted_dir(name);
     });
 }
@@ -930,5 +936,120 @@
           "com.foo", 10000, "", 0, 41, FLAG_STORAGE_DE));
 }
 
+class AppSupplementalDataTest : public testing::Test {
+public:
+    void CheckFileAccess(const std::string& path, uid_t uid, mode_t mode) {
+        const auto fullPath = "/data/local/tmp/" + path;
+        ASSERT_TRUE(exists(fullPath.c_str())) << "For path: " << fullPath;
+        struct stat st;
+        ASSERT_EQ(0, stat(fullPath.c_str(), &st));
+        ASSERT_EQ(uid, st.st_uid) << "For path: " << fullPath;
+        ASSERT_EQ(uid, st.st_gid) << "For path: " << fullPath;
+        ASSERT_EQ(mode, st.st_mode) << "For path: " << fullPath;
+    }
+
+    bool exists(const char* path) { return ::access(path, F_OK) == 0; }
+
+    // Creates a default CreateAppDataArgs object
+    android::os::CreateAppDataArgs createAppDataArgs() {
+        android::os::CreateAppDataArgs args;
+        args.uuid = kTestUuid;
+        args.packageName = "com.foo";
+        args.userId = kTestUserId;
+        args.appId = kTestAppId;
+        args.seInfo = "default";
+        args.flags = FLAG_STORAGE_CE | FLAG_STORAGE_DE;
+        return args;
+    }
+
+protected:
+    InstalldNativeService* service;
+
+    virtual void SetUp() {
+        setenv("ANDROID_LOG_TAGS", "*:v", 1);
+        android::base::InitLogging(nullptr);
+
+        service = new InstalldNativeService();
+        clearAppData();
+        ASSERT_TRUE(mkdirs("/data/local/tmp/user/0", 0700));
+        ASSERT_TRUE(mkdirs("/data/local/tmp/user_de/0", 0700));
+        ASSERT_TRUE(mkdirs("/data/local/tmp/misc_ce/0/supplemental", 0700));
+        ASSERT_TRUE(mkdirs("/data/local/tmp/misc_de/0/supplemental", 0700));
+
+        init_globals_from_data_and_root();
+    }
+
+    virtual void TearDown() {
+        delete service;
+        clearAppData();
+    }
+
+private:
+    void clearAppData() {
+        ASSERT_EQ(0, delete_dir_contents_and_dir("/data/local/tmp/user_de", true));
+        ASSERT_EQ(0, delete_dir_contents_and_dir("/data/local/tmp/misc_ce", true));
+        ASSERT_EQ(0, delete_dir_contents_and_dir("/data/local/tmp/misc_de", true));
+        ASSERT_EQ(0, delete_dir_contents_and_dir("/data/local/tmp/user_de", true));
+    }
+};
+
+TEST_F(AppSupplementalDataTest, CreateAppData_CreatesSupplementalAppData) {
+    android::os::CreateAppDataResult result;
+    android::os::CreateAppDataArgs args = createAppDataArgs();
+    args.packageName = "com.foo";
+    args.flags = FLAG_STORAGE_CE | FLAG_STORAGE_DE;
+
+    // Create the app user data.
+    ASSERT_BINDER_SUCCESS(service->createAppData(args, &result));
+
+    CheckFileAccess("misc_ce/0/supplemental/com.foo", kSystemUid, S_IFDIR | 0751);
+    CheckFileAccess("misc_ce/0/supplemental/com.foo/shared", kTestAppSupplementalUid,
+                    S_IFDIR | 0700);
+    CheckFileAccess("misc_ce/0/supplemental/com.foo/shared/cache", kTestAppSupplementalUid,
+                    S_IFDIR | S_ISGID | 0771);
+    CheckFileAccess("misc_ce/0/supplemental/com.foo/shared/code_cache", kTestAppSupplementalUid,
+                    S_IFDIR | S_ISGID | 0771);
+
+    CheckFileAccess("misc_de/0/supplemental/com.foo", kSystemUid, S_IFDIR | 0751);
+    CheckFileAccess("misc_de/0/supplemental/com.foo/shared", kTestAppSupplementalUid,
+                    S_IFDIR | 0700);
+    CheckFileAccess("misc_de/0/supplemental/com.foo/shared/cache", kTestAppSupplementalUid,
+                    S_IFDIR | S_ISGID | 0771);
+    CheckFileAccess("misc_de/0/supplemental/com.foo/shared/code_cache", kTestAppSupplementalUid,
+                    S_IFDIR | S_ISGID | 0771);
+}
+
+TEST_F(AppSupplementalDataTest, CreateAppData_CreatesSupplementalAppData_WithoutDeFlag) {
+    android::os::CreateAppDataResult result;
+    android::os::CreateAppDataArgs args = createAppDataArgs();
+    args.packageName = "com.foo";
+    args.flags = FLAG_STORAGE_CE;
+
+    // Create the app user data.
+    ASSERT_BINDER_SUCCESS(service->createAppData(args, &result));
+
+    // Only CE paths should exist
+    CheckFileAccess("misc_ce/0/supplemental/com.foo", kSystemUid, S_IFDIR | 0751);
+
+    // DE paths should not exist
+    ASSERT_FALSE(exists("/data/local/tmp/misc_de/0/supplemental/com.foo"));
+}
+
+TEST_F(AppSupplementalDataTest, CreateAppData_CreatesSupplementalAppData_WithoutCeFlag) {
+    android::os::CreateAppDataResult result;
+    android::os::CreateAppDataArgs args = createAppDataArgs();
+    args.packageName = "com.foo";
+    args.flags = FLAG_STORAGE_DE;
+
+    // Create the app user data.
+    ASSERT_BINDER_SUCCESS(service->createAppData(args, &result));
+
+    // CE paths should not exist
+    ASSERT_FALSE(exists("/data/local/tmp/misc_ce/0/supplemental/com.foo"));
+
+    // Only DE paths should exist
+    CheckFileAccess("misc_de/0/supplemental/com.foo", kSystemUid, S_IFDIR | 0751);
+}
+
 }  // namespace installd
 }  // namespace android
diff --git a/cmds/installd/tests/installd_utils_test.cpp b/cmds/installd/tests/installd_utils_test.cpp
index ed87b67..a7a8624 100644
--- a/cmds/installd/tests/installd_utils_test.cpp
+++ b/cmds/installd/tests/installd_utils_test.cpp
@@ -555,6 +555,24 @@
     EXPECT_EQ(0, MatchExtension("docx"));
 }
 
+TEST_F(UtilsTest, TestIsRenamedDeletedDir) {
+    EXPECT_FALSE(is_renamed_deleted_dir(""));
+    EXPECT_FALSE(is_renamed_deleted_dir("1"));
+    EXPECT_FALSE(is_renamed_deleted_dir("="));
+    EXPECT_FALSE(is_renamed_deleted_dir("=="));
+    EXPECT_FALSE(is_renamed_deleted_dir("d=="));
+    EXPECT_FALSE(is_renamed_deleted_dir("ed=="));
+    EXPECT_FALSE(is_renamed_deleted_dir("ted=="));
+    EXPECT_FALSE(is_renamed_deleted_dir("eted=="));
+    EXPECT_FALSE(is_renamed_deleted_dir("leted=="));
+    EXPECT_FALSE(is_renamed_deleted_dir("eleted=="));
+    EXPECT_FALSE(is_renamed_deleted_dir("deleted=="));
+    EXPECT_FALSE(is_renamed_deleted_dir("=deleted=="));
+    EXPECT_TRUE(is_renamed_deleted_dir("==deleted=="));
+    EXPECT_TRUE(is_renamed_deleted_dir("123==deleted=="));
+    EXPECT_TRUE(is_renamed_deleted_dir("5b14b6458a44==deleted=="));
+}
+
 TEST_F(UtilsTest, TestRollbackPaths) {
     EXPECT_EQ("/data/misc_ce/0/rollback/239/com.foo",
             create_data_misc_ce_rollback_package_path(nullptr, 0, 239, "com.foo"));
@@ -638,5 +656,39 @@
     ASSERT_NE(0, create_dir_if_needed("/data/local/tmp/user/0/bar/baz", 0700));
 }
 
+TEST_F(UtilsTest, TestSupplementalDataPaths) {
+    // Ce data paths
+    EXPECT_EQ("/data/misc_ce/0/supplemental",
+              create_data_misc_supplemental_path(nullptr, /*isCeData=*/true, 0));
+    EXPECT_EQ("/data/misc_ce/10/supplemental",
+              create_data_misc_supplemental_path(nullptr, true, 10));
+
+    EXPECT_EQ("/data/misc_ce/0/supplemental/com.foo",
+              create_data_misc_supplemental_package_path(nullptr, true, 0, "com.foo"));
+    EXPECT_EQ("/data/misc_ce/10/supplemental/com.foo",
+              create_data_misc_supplemental_package_path(nullptr, true, 10, "com.foo"));
+
+    EXPECT_EQ("/data/misc_ce/0/supplemental/com.foo/shared",
+              create_data_misc_supplemental_shared_path(nullptr, true, 0, "com.foo"));
+    EXPECT_EQ("/data/misc_ce/10/supplemental/com.foo/shared",
+              create_data_misc_supplemental_shared_path(nullptr, true, 10, "com.foo"));
+
+    // De data paths
+    EXPECT_EQ("/data/misc_de/0/supplemental",
+              create_data_misc_supplemental_path(nullptr, /*isCeData=*/false, 0));
+    EXPECT_EQ("/data/misc_de/10/supplemental",
+              create_data_misc_supplemental_path(nullptr, false, 10));
+
+    EXPECT_EQ("/data/misc_de/0/supplemental/com.foo",
+              create_data_misc_supplemental_package_path(nullptr, false, 0, "com.foo"));
+    EXPECT_EQ("/data/misc_de/10/supplemental/com.foo",
+              create_data_misc_supplemental_package_path(nullptr, false, 10, "com.foo"));
+
+    EXPECT_EQ("/data/misc_de/0/supplemental/com.foo/shared",
+              create_data_misc_supplemental_shared_path(nullptr, false, 0, "com.foo"));
+    EXPECT_EQ("/data/misc_de/10/supplemental/com.foo/shared",
+              create_data_misc_supplemental_shared_path(nullptr, false, 10, "com.foo"));
+}
+
 }  // namespace installd
 }  // namespace android
diff --git a/cmds/installd/utils.cpp b/cmds/installd/utils.cpp
index 8a00be9..3ce4b67 100644
--- a/cmds/installd/utils.cpp
+++ b/cmds/installd/utils.cpp
@@ -194,6 +194,43 @@
     return StringPrintf("%s/user_de/%u", data.c_str(), userid);
 }
 
+/**
+ * Create the path name where supplemental data for all apps will be stored.
+ * E.g. /data/misc_ce/0/supplemental
+ */
+std::string create_data_misc_supplemental_path(const char* uuid, bool isCeData, userid_t user) {
+    std::string data(create_data_path(uuid));
+    if (isCeData) {
+        return StringPrintf("%s/misc_ce/%d/supplemental", data.c_str(), user);
+    } else {
+        return StringPrintf("%s/misc_de/%d/supplemental", data.c_str(), user);
+    }
+}
+
+/**
+ * Create the path name where code data for all codes in a particular app will be stored.
+ * E.g. /data/misc_ce/0/supplemental/<app-name>
+ */
+std::string create_data_misc_supplemental_package_path(const char* volume_uuid, bool isCeData,
+                                                       userid_t user, const char* package_name) {
+    check_package_name(package_name);
+    return StringPrintf("%s/%s",
+                        create_data_misc_supplemental_path(volume_uuid, isCeData, user).c_str(),
+                        package_name);
+}
+
+/**
+ * Create the path name where shared code data for a particular app will be stored.
+ * E.g. /data/misc_ce/0/supplemental/<app-name>/shared
+ */
+std::string create_data_misc_supplemental_shared_path(const char* volume_uuid, bool isCeData,
+                                                      userid_t user, const char* package_name) {
+    return StringPrintf("%s/shared",
+                        create_data_misc_supplemental_package_path(volume_uuid, isCeData, user,
+                                                                   package_name)
+                                .c_str());
+}
+
 std::string create_data_misc_ce_rollback_base_path(const char* volume_uuid, userid_t user) {
     return StringPrintf("%s/misc_ce/%u/rollback", create_data_path(volume_uuid).c_str(), user);
 }
@@ -637,8 +674,12 @@
     return delete_dir_contents(temp_dir_path.c_str(), 1, exclusion_predicate, ignore_if_missing);
 }
 
-bool is_renamed_deleted_dir(std::string_view path) {
-    return path.ends_with(deletedSuffix);
+bool is_renamed_deleted_dir(const std::string& path) {
+    if (path.size() < deletedSuffix.size()) {
+        return false;
+    }
+    std::string_view pathSuffix{path.c_str() + path.size() - deletedSuffix.size()};
+    return pathSuffix == deletedSuffix;
 }
 
 int rename_delete_dir_contents_and_dir(const std::string& pathname, bool ignore_if_missing) {
diff --git a/cmds/installd/utils.h b/cmds/installd/utils.h
index ffde562..2db1623 100644
--- a/cmds/installd/utils.h
+++ b/cmds/installd/utils.h
@@ -60,6 +60,13 @@
 std::string create_data_user_ce_package_path_as_user_link(
         const char* volume_uuid, userid_t userid, const char* package_name);
 
+std::string create_data_misc_supplemental_path(const char* volume_uuid, bool isCeData,
+                                               userid_t userid);
+std::string create_data_misc_supplemental_package_path(const char* volume_uuid, bool isCeData,
+                                                       userid_t userid, const char* package_name);
+std::string create_data_misc_supplemental_shared_path(const char* volume_uuid, bool isCeData,
+                                                      userid_t userid, const char* package_name);
+
 std::string create_data_misc_ce_rollback_base_path(const char* volume_uuid, userid_t user);
 std::string create_data_misc_de_rollback_base_path(const char* volume_uuid, userid_t user);
 std::string create_data_misc_ce_rollback_path(const char* volume_uuid, userid_t user,
@@ -120,8 +127,8 @@
 int delete_dir_contents(const std::string& pathname, bool ignore_if_missing = false);
 int delete_dir_contents_and_dir(const std::string& pathname, bool ignore_if_missing = false);
 
-bool is_renamed_deleted_dir(std::string_view path);
-int rename_delete_dir_contents_and_dir(const std::string& pathname, bool ignore_if_missing = false);
+bool is_renamed_deleted_dir(const std::string& path);
+int rename_delete_dir_contents_and_dir(const std::string& pathname, bool ignore_if_missing = true);
 
 void cleanup_invalid_package_dirs_under_path(const std::string& pathname);
 
diff --git a/include/android/choreographer.h b/include/android/choreographer.h
index 98f0eec..63aa7ff 100644
--- a/include/android/choreographer.h
+++ b/include/android/choreographer.h
@@ -76,7 +76,7 @@
  * It's passed the frame data that should not outlive the callback, as well as the data pointer
  * provided by the application that registered a callback.
  */
-typedef void (*AChoreographer_extendedFrameCallback)(
+typedef void (*AChoreographer_vsyncCallback)(
         const AChoreographerFrameCallbackData* callbackData, void* data);
 
 /**
@@ -134,8 +134,8 @@
  * Posts a callback to run on the next frame. The data pointer provided will
  * be passed to the callback function when it's called.
  */
-void AChoreographer_postExtendedFrameCallback(AChoreographer* choreographer,
-                                        AChoreographer_extendedFrameCallback callback, void* data)
+void AChoreographer_postVsyncCallback(AChoreographer* choreographer,
+                                        AChoreographer_vsyncCallback callback, void* data)
         __INTRODUCED_IN(33);
 
 /**
@@ -215,7 +215,7 @@
 /**
  * The time in nanoseconds which the frame at given index is expected to be presented.
  */
-int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos(
+int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(
         const AChoreographerFrameCallbackData* data, size_t index) __INTRODUCED_IN(33);
 
 /**
diff --git a/include/android/input.h b/include/android/input.h
index e6ad943f..fb5e204 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -808,6 +808,33 @@
 };
 
 /**
+ * Constants that identify different gesture classification types.
+ */
+enum {
+    /**
+     * Classification constant: None.
+     *
+     * No additional information is available about the current motion event stream.
+     */
+    AMOTION_EVENT_CLASSIFICATION_NONE = 0,
+    /**
+     * Classification constant: Ambiguous gesture.
+     *
+     * The user's intent with respect to the current event stream is not yet determined.
+     * Gestural actions, such as scrolling, should be inhibited until the classification resolves
+     * to another value or the event stream ends.
+     */
+    AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE = 1,
+    /**
+     * Classification constant: Deep press.
+     *
+     * The current event stream represents the user intentionally pressing harder on the screen.
+     * This classification type should be used to accelerate the long press behaviour.
+     */
+    AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS = 2,
+};
+
+/**
  * Input source masks.
  *
  * Refer to the documentation on android.view.InputDevice for more details about input sources
@@ -1327,6 +1354,23 @@
         int32_t axis, size_t pointer_index, size_t history_index);
 
 /**
+ * Get the action button for the motion event. Returns a valid action button when the
+ * event is associated with a button press or button release action. For other actions
+ * the return value is undefined.
+ */
+int32_t AMotionEvent_getActionButton(const AInputEvent* motion_event);
+
+/**
+ * Returns the classification for the current gesture.
+ * The classification may change as more events become available for the same gesture.
+ *
+ * @see #AMOTION_EVENT_CLASSIFICATION_NONE
+ * @see #AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE
+ * @see #AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS
+*/
+int32_t AMotionEvent_getClassification(const AInputEvent* motion_event);
+
+/**
  * Creates a native AInputEvent* object that is a copy of the specified Java
  * android.view.MotionEvent. The result may be used with generic and MotionEvent-specific
  * AInputEvent_* functions. The object returned by this function must be disposed using
diff --git a/include/ftl/details/array_traits.h b/include/ftl/details/array_traits.h
index 16e63ec..5234c38 100644
--- a/include/ftl/details/array_traits.h
+++ b/include/ftl/details/array_traits.h
@@ -42,7 +42,7 @@
   using const_reverse_iterator = std::reverse_iterator<const_iterator>;
 
   template <typename... Args>
-  static pointer construct_at(const_iterator it, Args&&... args) {
+  static constexpr pointer construct_at(const_iterator it, Args&&... args) {
     void* const ptr = const_cast<void*>(static_cast<const void*>(it));
     if constexpr (std::is_constructible_v<value_type, Args...>) {
       // TODO: Replace with std::construct_at in C++20.
@@ -52,6 +52,42 @@
       return new (ptr) value_type{std::forward<Args>(args)...};
     }
   }
+
+  // TODO: Make constexpr in C++20.
+  template <typename... Args>
+  static reference replace_at(const_iterator it, Args&&... args) {
+    value_type element{std::forward<Args>(args)...};
+    return replace_at(it, std::move(element));
+  }
+
+  // TODO: Make constexpr in C++20.
+  static reference replace_at(const_iterator it, value_type&& value) {
+    std::destroy_at(it);
+    // This is only safe because exceptions are disabled.
+    return *construct_at(it, std::move(value));
+  }
+
+  // TODO: Make constexpr in C++20.
+  static void in_place_swap(reference a, reference b) {
+    value_type c{std::move(a)};
+    replace_at(&a, std::move(b));
+    replace_at(&b, std::move(c));
+  }
+
+  // TODO: Make constexpr in C++20.
+  static void in_place_swap_ranges(iterator first1, iterator last1, iterator first2) {
+    while (first1 != last1) {
+      in_place_swap(*first1++, *first2++);
+    }
+  }
+
+  // TODO: Replace with std::uninitialized_copy in C++20.
+  template <typename Iterator>
+  static void uninitialized_copy(Iterator first, Iterator last, const_iterator out) {
+    while (first != last) {
+      construct_at(out++, *first++);
+    }
+  }
 };
 
 // CRTP mixin to define iterator functions in terms of non-const Self::begin and Self::end.
@@ -101,33 +137,33 @@
 // TODO: Replace with operator<=> in C++20.
 template <template <typename, std::size_t> class Array>
 struct ArrayComparators {
-  template <typename T, std::size_t N, std::size_t M>
-  friend bool operator==(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+  template <typename T, typename U, std::size_t N, std::size_t M>
+  friend bool operator==(const Array<T, N>& lhs, const Array<U, M>& rhs) {
     return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
   }
 
-  template <typename T, std::size_t N, std::size_t M>
-  friend bool operator<(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+  template <typename T, typename U, std::size_t N, std::size_t M>
+  friend bool operator<(const Array<T, N>& lhs, const Array<U, M>& rhs) {
     return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
   }
 
-  template <typename T, std::size_t N, std::size_t M>
-  friend bool operator>(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+  template <typename T, typename U, std::size_t N, std::size_t M>
+  friend bool operator>(const Array<T, N>& lhs, const Array<U, M>& rhs) {
     return rhs < lhs;
   }
 
-  template <typename T, std::size_t N, std::size_t M>
-  friend bool operator!=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+  template <typename T, typename U, std::size_t N, std::size_t M>
+  friend bool operator!=(const Array<T, N>& lhs, const Array<U, M>& rhs) {
     return !(lhs == rhs);
   }
 
-  template <typename T, std::size_t N, std::size_t M>
-  friend bool operator>=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+  template <typename T, typename U, std::size_t N, std::size_t M>
+  friend bool operator>=(const Array<T, N>& lhs, const Array<U, M>& rhs) {
     return !(lhs < rhs);
   }
 
-  template <typename T, std::size_t N, std::size_t M>
-  friend bool operator<=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+  template <typename T, typename U, std::size_t N, std::size_t M>
+  friend bool operator<=(const Array<T, N>& lhs, const Array<U, M>& rhs) {
     return !(lhs > rhs);
   }
 };
diff --git a/include/ftl/small_map.h b/include/ftl/small_map.h
index 2effaa4..5217e76 100644
--- a/include/ftl/small_map.h
+++ b/include/ftl/small_map.h
@@ -65,6 +65,9 @@
 class SmallMap final {
   using Map = SmallVector<std::pair<const K, V>, N>;
 
+  template <typename, typename, std::size_t, typename>
+  friend class SmallMap;
+
  public:
   using key_type = K;
   using mapped_type = V;
@@ -100,6 +103,10 @@
     deduplicate();
   }
 
+  // Copies or moves key-value pairs from a convertible map.
+  template <typename Q, typename W, std::size_t M, typename E>
+  SmallMap(SmallMap<Q, W, M, E> other) : map_(std::move(other.map_)) {}
+
   size_type max_size() const { return map_.max_size(); }
   size_type size() const { return map_.size(); }
   bool empty() const { return map_.empty(); }
diff --git a/include/ftl/small_vector.h b/include/ftl/small_vector.h
index 03587e3..339726e 100644
--- a/include/ftl/small_vector.h
+++ b/include/ftl/small_vector.h
@@ -37,6 +37,9 @@
 // augmented by an unstable_erase operation that does not preserve order, and a replace operation
 // that destructively emplaces.
 //
+// Unlike std::vector, T does not require copy/move assignment, so may be an object with const data
+// members, or be const itself.
+//
 // SmallVector<T, 0> is a specialization that thinly wraps std::vector.
 //
 // Example usage:
@@ -105,10 +108,9 @@
   SmallVector(Arg&& arg, Args&&... args)
       : vector_(std::in_place_type<Static>, std::forward<Arg>(arg), std::forward<Args>(args)...) {}
 
-  // Copies at most N elements from a smaller convertible vector.
-  template <typename U, std::size_t M, typename = std::enable_if_t<M <= N>>
-  SmallVector(const SmallVector<U, M>& other)
-      : SmallVector(kIteratorRange, other.begin(), other.end()) {}
+  // Copies or moves elements from a smaller convertible vector.
+  template <typename U, std::size_t M, typename = std::enable_if_t<(M > 0)>>
+  SmallVector(SmallVector<U, M> other) : vector_(convert(std::move(other))) {}
 
   void swap(SmallVector& other) { vector_.swap(other.vector_); }
 
@@ -235,7 +237,30 @@
     }
   }
 
+  // Extracts the elements as std::vector.
+  std::vector<T> promote() && {
+    if (dynamic()) {
+      return std::get<Dynamic>(std::move(vector_)).promote();
+    } else {
+      return {std::make_move_iterator(begin()), std::make_move_iterator(end())};
+    }
+  }
+
  private:
+  template <typename, std::size_t>
+  friend class SmallVector;
+
+  template <typename U, std::size_t M>
+  static std::variant<Static, Dynamic> convert(SmallVector<U, M>&& other) {
+    using Other = SmallVector<U, M>;
+
+    if (other.dynamic()) {
+      return std::get<typename Other::Dynamic>(std::move(other.vector_));
+    } else {
+      return std::get<typename Other::Static>(std::move(other.vector_));
+    }
+  }
+
   template <auto InsertStatic, auto InsertDynamic, typename... Args>
   auto insert(Args&&... args) {
     if (Dynamic* const vector = std::get_if<Dynamic>(&vector_)) {
@@ -267,9 +292,10 @@
 // Partial specialization without static storage.
 template <typename T>
 class SmallVector<T, 0> final : details::ArrayTraits<T>,
+                                details::ArrayComparators<SmallVector>,
                                 details::ArrayIterators<SmallVector<T, 0>, T>,
                                 std::vector<T> {
-  using details::ArrayTraits<T>::construct_at;
+  using details::ArrayTraits<T>::replace_at;
 
   using Iter = details::ArrayIterators<SmallVector, T>;
   using Impl = std::vector<T>;
@@ -291,8 +317,30 @@
   FTL_ARRAY_TRAIT(T, const_iterator);
   FTL_ARRAY_TRAIT(T, const_reverse_iterator);
 
+  // See std::vector for underlying constructors.
   using Impl::Impl;
 
+  // Copies and moves a vector, respectively.
+  SmallVector(const SmallVector&) = default;
+  SmallVector(SmallVector&&) = default;
+
+  // Constructs elements in place. See StaticVector for underlying constructor.
+  template <typename U, std::size_t... Sizes, typename... Types>
+  SmallVector(InitializerList<U, std::index_sequence<Sizes...>, Types...>&& list)
+      : SmallVector(SmallVector<T, sizeof...(Sizes)>(std::move(list))) {}
+
+  // Copies or moves elements from a convertible vector.
+  template <typename U, std::size_t M>
+  SmallVector(SmallVector<U, M> other) : Impl(convert(std::move(other))) {}
+
+  SmallVector& operator=(SmallVector other) {
+    // Define copy/move assignment in terms of copy/move construction.
+    swap(other);
+    return *this;
+  }
+
+  void swap(SmallVector& other) { Impl::swap(other); }
+
   using Impl::empty;
   using Impl::max_size;
   using Impl::size;
@@ -324,10 +372,7 @@
 
   template <typename... Args>
   reference replace(const_iterator it, Args&&... args) {
-    value_type element{std::forward<Args>(args)...};
-    std::destroy_at(it);
-    // This is only safe because exceptions are disabled.
-    return *construct_at(it, std::move(element));
+    return replace_at(it, std::forward<Args>(args)...);
   }
 
   template <typename... Args>
@@ -353,7 +398,26 @@
     pop_back();
   }
 
-  void swap(SmallVector& other) { Impl::swap(other); }
+  std::vector<T> promote() && { return std::move(*this); }
+
+ private:
+  template <typename U, std::size_t M>
+  static Impl convert(SmallVector<U, M>&& other) {
+    if constexpr (std::is_constructible_v<Impl, std::vector<U>&&>) {
+      return std::move(other).promote();
+    } else {
+      SmallVector vector(other.size());
+
+      // Consistently with StaticVector, T only requires copy/move construction from U, rather than
+      // copy/move assignment.
+      auto it = vector.begin();
+      for (auto& element : other) {
+        vector.replace(it++, std::move(element));
+      }
+
+      return vector;
+    }
+  }
 };
 
 template <typename>
diff --git a/include/ftl/static_vector.h b/include/ftl/static_vector.h
index b7f8c29..eb83b85 100644
--- a/include/ftl/static_vector.h
+++ b/include/ftl/static_vector.h
@@ -39,6 +39,9 @@
 // adheres to standard containers, except the unstable_erase operation that does not preserve order,
 // and the replace operation that destructively emplaces.
 //
+// Unlike std::vector, T does not require copy/move assignment, so may be an object with const data
+// members, or be const itself.
+//
 // StaticVector<T, 1> is analogous to an iterable std::optional.
 // StaticVector<T, 0> is an error.
 //
@@ -78,7 +81,14 @@
                            details::ArrayComparators<StaticVector> {
   static_assert(N > 0);
 
+  // For constructor that moves from a smaller convertible vector.
+  template <typename, std::size_t>
+  friend class StaticVector;
+
   using details::ArrayTraits<T>::construct_at;
+  using details::ArrayTraits<T>::replace_at;
+  using details::ArrayTraits<T>::in_place_swap_ranges;
+  using details::ArrayTraits<T>::uninitialized_copy;
 
   using Iter = details::ArrayIterators<StaticVector, T>;
   friend Iter;
@@ -117,14 +127,18 @@
   StaticVector(StaticVector&& other) { swap<true>(other); }
 
   // Copies at most N elements from a smaller convertible vector.
-  template <typename U, std::size_t M, typename = std::enable_if_t<M <= N>>
+  template <typename U, std::size_t M>
   StaticVector(const StaticVector<U, M>& other)
-      : StaticVector(kIteratorRange, other.begin(), other.end()) {}
+      : StaticVector(kIteratorRange, other.begin(), other.end()) {
+    static_assert(N >= M, "Insufficient capacity");
+  }
 
-  // Copies at most N elements from an array.
+  // Copies at most N elements from a smaller convertible array.
   template <typename U, std::size_t M>
   explicit StaticVector(U (&array)[M])
-      : StaticVector(kIteratorRange, std::begin(array), std::end(array)) {}
+      : StaticVector(kIteratorRange, std::begin(array), std::end(array)) {
+    static_assert(N >= M, "Insufficient capacity");
+  }
 
   // Copies at most N elements from the range [first, last).
   //
@@ -139,7 +153,18 @@
   template <typename Iterator>
   StaticVector(IteratorRangeTag, Iterator first, Iterator last)
       : size_(std::min(max_size(), static_cast<size_type>(std::distance(first, last)))) {
-    std::uninitialized_copy(first, first + size_, begin());
+    uninitialized_copy(first, first + size_, begin());
+  }
+
+  // Moves at most N elements from a smaller convertible vector.
+  template <typename U, std::size_t M>
+  StaticVector(StaticVector<U, M>&& other) {
+    static_assert(N >= M, "Insufficient capacity");
+
+    // Same logic as swap<true>, though M need not be equal to N.
+    std::uninitialized_move(other.begin(), other.end(), begin());
+    std::destroy(other.begin(), other.end());
+    std::swap(size_, other.size_);
   }
 
   // Constructs at most N elements. The template arguments T and N are inferred using the
@@ -178,7 +203,9 @@
   template <typename U, std::size_t Size, std::size_t... Sizes, typename... Types>
   StaticVector(InitializerList<U, std::index_sequence<Size, Sizes...>, Types...>&& list)
       : StaticVector(std::index_sequence<0, 0, Size>{}, std::make_index_sequence<Size>{},
-                     std::index_sequence<Sizes...>{}, list.tuple) {}
+                     std::index_sequence<Sizes...>{}, list.tuple) {
+    static_assert(sizeof...(Sizes) < N, "Too many elements");
+  }
 
   ~StaticVector() { std::destroy(begin(), end()); }
 
@@ -238,10 +265,7 @@
   //
   template <typename... Args>
   reference replace(const_iterator it, Args&&... args) {
-    value_type element{std::forward<Args>(args)...};
-    std::destroy_at(it);
-    // This is only safe because exceptions are disabled.
-    return *construct_at(it, std::move(element));
+    return replace_at(it, std::forward<Args>(args)...);
   }
 
   // Appends an element, and returns an iterator to it. If the vector is full, the element is not
@@ -380,7 +404,7 @@
     }
 
     // Swap elements [0, min).
-    std::swap_ranges(begin(), begin() + min, other.begin());
+    in_place_swap_ranges(begin(), begin() + min, other.begin());
 
     // No elements to move if sizes are equal.
     if (min == max) return;
diff --git a/include/input/Input.h b/include/input/Input.h
index e421dee..2837add 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -275,23 +275,21 @@
 
 /**
  * Classifications of the current gesture, if available.
- *
- * The following values must be kept in sync with MotionEvent.java
  */
 enum class MotionClassification : uint8_t {
     /**
      * No classification is available.
      */
-    NONE = 0,
+    NONE = AMOTION_EVENT_CLASSIFICATION_NONE,
     /**
      * Too early to classify the current gesture. Need more events. Look for changes in the
      * upcoming motion events.
      */
-    AMBIGUOUS_GESTURE = 1,
+    AMBIGUOUS_GESTURE = AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE,
     /**
      * The current gesture likely represents a user intentionally exerting force on the touchscreen.
      */
-    DEEP_PRESS = 2,
+    DEEP_PRESS = AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS,
 };
 
 /**
diff --git a/libs/binder/PersistableBundle.cpp b/libs/binder/PersistableBundle.cpp
index 406fee0..1504715 100644
--- a/libs/binder/PersistableBundle.cpp
+++ b/libs/binder/PersistableBundle.cpp
@@ -27,13 +27,6 @@
 
 #include "ParcelValTypes.h"
 
-using android::BAD_TYPE;
-using android::BAD_VALUE;
-using android::NO_ERROR;
-using android::Parcel;
-using android::status_t;
-using android::UNEXPECTED_NULL;
-
 using android::binder::VAL_BOOLEAN;
 using android::binder::VAL_INTEGER;
 using android::binder::VAL_LONG;
diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp
index 1446802..38bde3a 100644
--- a/libs/binder/tests/parcel_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/Android.bp
@@ -26,13 +26,9 @@
     static_libs: [
         "libbase",
         "libbinder_random_parcel",
-        "libcgrouprc",
-        "libcgrouprc_format",
         "libcutils",
         "libhidlbase",
         "liblog",
-        "libprocessgroup",
-        "libjsoncpp",
         "libutils",
     ],
 
diff --git a/libs/ftl/small_map_test.cpp b/libs/ftl/small_map_test.cpp
index ee650e5..1740a2b 100644
--- a/libs/ftl/small_map_test.cpp
+++ b/libs/ftl/small_map_test.cpp
@@ -96,6 +96,33 @@
   }
 }
 
+TEST(SmallMap, Assign) {
+  {
+    // Same types; smaller capacity.
+    SmallMap map1 = ftl::init::map<char, std::string>('k', "kilo")('M', "mega")('G', "giga");
+    const SmallMap map2 = ftl::init::map('T', "tera"s)('P', "peta"s);
+
+    map1 = map2;
+    EXPECT_EQ(map1, map2);
+  }
+  {
+    // Convertible types; same capacity.
+    SmallMap map1 = ftl::init::map<char, std::string>('M', "mega")('G', "giga");
+    const SmallMap map2 = ftl::init::map('T', "tera")('P', "peta");
+
+    map1 = map2;
+    EXPECT_EQ(map1, map2);
+  }
+  {
+    // Convertible types; zero capacity.
+    SmallMap<char, std::string, 0> map1 = ftl::init::map('M', "mega")('G', "giga");
+    const SmallMap<char, std::string, 0> map2 = ftl::init::map('T', "tera")('P', "peta");
+
+    map1 = map2;
+    EXPECT_EQ(map1, map2);
+  }
+}
+
 TEST(SmallMap, UniqueKeys) {
   {
     // Duplicate mappings are discarded.
diff --git a/libs/ftl/small_vector_test.cpp b/libs/ftl/small_vector_test.cpp
index 4237496..b662a81 100644
--- a/libs/ftl/small_vector_test.cpp
+++ b/libs/ftl/small_vector_test.cpp
@@ -138,6 +138,116 @@
   }
 }
 
+TEST(SmallVector, Copy) {
+  {
+    // Same capacity.
+    const SmallVector vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 2> copy(vector);
+    EXPECT_EQ(copy, vector);
+
+    // The vector is assignable even if T is const.
+    const SmallVector<std::string, 2> other = {"tiramisu"s};
+    copy = other;
+    EXPECT_EQ(copy, other);
+  }
+  {
+    // From smaller capacity.
+    const SmallVector vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 3> copy(vector);
+    EXPECT_EQ(copy, vector);
+
+    // The vector is assignable even if T is const.
+    const SmallVector other = {"tiramisu"s};
+    copy = other;
+    EXPECT_EQ(copy, other);
+  }
+  {
+    // To zero capacity.
+    const SmallVector vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 0> copy(vector);
+    EXPECT_EQ(copy, vector);
+
+    // The vector is assignable even if T is const.
+    const SmallVector other = {"tiramisu"s};
+    copy = other;
+    EXPECT_EQ(copy, other);
+  }
+  {
+    // From/to zero capacity.
+    const SmallVector<std::string, 0> vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 0> copy(vector);
+    EXPECT_EQ(copy, vector);
+
+    // The vector is assignable even if T is const.
+    const SmallVector<std::string, 0> other = {"tiramisu"s};
+    copy = other;
+    EXPECT_EQ(copy, other);
+  }
+}
+
+TEST(SmallVector, Move) {
+  {
+    // Same capacity.
+    SmallVector vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 2> move(std::move(vector));
+    EXPECT_TRUE(vector.empty());
+    EXPECT_EQ(move, (SmallVector{"snow"s, "cone"s}));
+
+    // The vector is assignable even if T is const.
+    SmallVector<std::string, 2> other = {"tiramisu"s};
+    move = std::move(other);
+    EXPECT_TRUE(other.empty());
+    EXPECT_EQ(move, (SmallVector{"tiramisu"s}));
+  }
+  {
+    // From smaller capacity.
+    SmallVector vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 3> move(std::move(vector));
+    EXPECT_TRUE(vector.empty());
+    EXPECT_EQ(move, (SmallVector{"snow"s, "cone"s}));
+
+    // The vector is assignable even if T is const.
+    SmallVector other = {"tiramisu"s};
+    move = std::move(other);
+    EXPECT_TRUE(other.empty());
+    EXPECT_EQ(move, (SmallVector{"tiramisu"s}));
+  }
+  {
+    // To zero capacity.
+    SmallVector vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 0> move(std::move(vector));
+    EXPECT_TRUE(vector.empty());
+    EXPECT_EQ(move, (SmallVector{"snow"s, "cone"s}));
+
+    // The vector is assignable even if T is const.
+    SmallVector other = {"tiramisu"s};
+    move = std::move(other);
+    EXPECT_TRUE(other.empty());
+    EXPECT_EQ(move, (SmallVector{"tiramisu"s}));
+  }
+  {
+    // From/to zero capacity.
+    SmallVector<std::string, 0> vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 0> move(std::move(vector));
+    EXPECT_TRUE(vector.empty());
+    EXPECT_EQ(move, (SmallVector{"snow"s, "cone"s}));
+
+    // The vector is assignable even if T is const.
+    SmallVector<std::string, 0> other = {"tiramisu"s};
+    move = std::move(other);
+    EXPECT_TRUE(other.empty());
+    EXPECT_EQ(move, (SmallVector{"tiramisu"s}));
+  }
+}
+
 TEST(SmallVector, String) {
   SmallVector<char, 10> chars;
   char c = 'a';
@@ -366,21 +476,20 @@
   bool alive = true;
 };
 
-void swap(DestroyCounts& lhs, DestroyCounts& rhs) {
-  std::swap(lhs.alive, rhs.alive);
-}
-
 }  // namespace
 
 TEST(SmallVector, Destroy) {
   int live = 0;
   int dead = 0;
-
-  { SmallVector<DestroyCounts, 3> counts; }
+  {
+    // Empty.
+    SmallVector<DestroyCounts, 3> counts;
+  }
   EXPECT_EQ(0, live);
   EXPECT_EQ(0, dead);
 
   {
+    // Static.
     SmallVector<DestroyCounts, 3> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
@@ -393,6 +502,7 @@
 
   live = 0;
   {
+    // Dynamic.
     SmallVector<DestroyCounts, 3> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
@@ -406,12 +516,13 @@
 
   live = dead = 0;
   {
+    // Copy.
     SmallVector<DestroyCounts, 2> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
 
-    auto copy = counts;
+    const auto copy = counts;
     EXPECT_TRUE(copy.dynamic());
   }
   EXPECT_EQ(6, live);
@@ -419,12 +530,13 @@
 
   live = dead = 0;
   {
+    // Move.
     SmallVector<DestroyCounts, 2> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
 
-    auto move = std::move(counts);
+    const auto move = std::move(counts);
     EXPECT_TRUE(move.dynamic());
   }
   EXPECT_EQ(3, live);
@@ -432,6 +544,7 @@
 
   live = dead = 0;
   {
+    // Swap.
     SmallVector<DestroyCounts, 2> counts1;
     counts1.emplace_back(live, dead);
     counts1.emplace_back(live, dead);
@@ -448,7 +561,10 @@
 
     swap(counts1, counts2);
 
+    EXPECT_EQ(1u, counts1.size());
     EXPECT_FALSE(counts1.dynamic());
+
+    EXPECT_EQ(3u, counts2.size());
     EXPECT_TRUE(counts2.dynamic());
 
     EXPECT_EQ(0, live);
@@ -465,29 +581,82 @@
   int dead = 0;
 
   SmallVector<DestroyCounts, 2> counts;
-  counts.emplace_back(live, dead);
-  counts.emplace_back(live, dead);
+  {
+    // Static.
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
 
-  counts.clear();
+    counts.clear();
 
-  EXPECT_TRUE(counts.empty());
-  EXPECT_FALSE(counts.dynamic());
-
+    EXPECT_TRUE(counts.empty());
+    EXPECT_FALSE(counts.dynamic());
+  }
   EXPECT_EQ(2, live);
   EXPECT_EQ(0, dead);
 
   live = 0;
-  counts.emplace_back(live, dead);
-  counts.emplace_back(live, dead);
-  counts.emplace_back(live, dead);
+  {
+    // Dynamic.
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
 
-  counts.clear();
+    counts.clear();
 
-  EXPECT_TRUE(counts.empty());
-  EXPECT_TRUE(counts.dynamic());
-
+    EXPECT_TRUE(counts.empty());
+    EXPECT_TRUE(counts.dynamic());
+  }
   EXPECT_EQ(3, live);
   EXPECT_EQ(2, dead);
 }
 
+TEST(SmallVector, Promote) {
+  {
+    const std::vector vector = {"snow"s, "cone"s};
+    EXPECT_EQ(vector, SmallVector("snow"s, "cone"s).promote());
+    EXPECT_EQ(vector, (SmallVector<std::string, 0>({"snow"s, "cone"s}).promote()));
+  }
+
+  int live = 0;
+  int dead = 0;
+
+  std::vector<DestroyCounts> vector;
+  {
+    // Static.
+    SmallVector<DestroyCounts, 3> counts;
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
+
+    vector = std::move(counts).promote();
+
+    ASSERT_EQ(2u, vector.size());
+    EXPECT_TRUE(vector[0].alive);
+    EXPECT_TRUE(vector[1].alive);
+  }
+  EXPECT_EQ(0, live);
+  EXPECT_EQ(2, dead);
+
+  vector.clear();
+  live = dead = 0;
+  {
+    // Dynamic.
+    SmallVector<DestroyCounts, 2> counts;
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
+
+    EXPECT_EQ(2, dead);
+    dead = 0;
+
+    vector = std::move(counts).promote();
+
+    ASSERT_EQ(3u, vector.size());
+    EXPECT_TRUE(vector[0].alive);
+    EXPECT_TRUE(vector[1].alive);
+    EXPECT_TRUE(vector[2].alive);
+  }
+  EXPECT_EQ(0, live);
+  EXPECT_EQ(0, dead);
+}
+
 }  // namespace android::test
diff --git a/libs/ftl/static_vector_test.cpp b/libs/ftl/static_vector_test.cpp
index 2de3ad2..0e10a5d 100644
--- a/libs/ftl/static_vector_test.cpp
+++ b/libs/ftl/static_vector_test.cpp
@@ -144,6 +144,64 @@
   }
 }
 
+TEST(StaticVector, Copy) {
+  {
+    // Same capacity.
+    const StaticVector vector = {"snow"s, "cone"s};
+
+    StaticVector<const std::string, 2> copy(vector);
+    EXPECT_EQ(copy, vector);
+
+    // The vector is assignable even if T is const.
+    const StaticVector<std::string, 2> other = {"tiramisu"s};
+    copy = other;
+    EXPECT_EQ(copy, other);
+  }
+  {
+    // From smaller capacity.
+    const StaticVector vector = {"snow"s, "cone"s};
+
+    StaticVector<const std::string, 3> copy(vector);
+    EXPECT_EQ(copy, vector);
+
+    // The vector is assignable even if T is const.
+    const StaticVector other = {"tiramisu"s};
+    copy = other;
+    EXPECT_EQ(copy, other);
+  }
+}
+
+TEST(StaticVector, Move) {
+  {
+    // Same capacity.
+    StaticVector vector = {"snow"s, "cone"s};
+
+    StaticVector<const std::string, 2> move(std::move(vector));
+    EXPECT_TRUE(vector.empty());
+    EXPECT_EQ(move, (StaticVector{"snow"s, "cone"s}));
+
+    // The vector is assignable even if T is const.
+    StaticVector<std::string, 2> other = {"tiramisu"s};
+    move = std::move(other);
+    EXPECT_TRUE(other.empty());
+    EXPECT_EQ(move, (StaticVector{"tiramisu"s}));
+  }
+  {
+    // From smaller capacity.
+    StaticVector vector = {"snow"s, "cone"s};
+
+    StaticVector<const std::string, 3> move(std::move(vector));
+    EXPECT_TRUE(vector.empty());
+    EXPECT_EQ(move, (StaticVector{"snow"s, "cone"s}));
+
+    // The vector is assignable even if T is const.
+    StaticVector other = {"tiramisu"s};
+    move = std::move(other);
+    EXPECT_TRUE(other.empty());
+    EXPECT_EQ(move, (StaticVector{"tiramisu"s}));
+  }
+}
+
 TEST(StaticVector, String) {
   StaticVector<char, 10> chars;
   char c = 'a';
@@ -328,21 +386,20 @@
   bool alive = true;
 };
 
-void swap(DestroyCounts& lhs, DestroyCounts& rhs) {
-  std::swap(lhs.alive, rhs.alive);
-}
-
 }  // namespace
 
 TEST(StaticVector, Destroy) {
   int live = 0;
   int dead = 0;
-
-  { StaticVector<DestroyCounts, 5> counts; }
+  {
+    // Empty.
+    StaticVector<DestroyCounts, 5> counts;
+  }
   EXPECT_EQ(0, live);
   EXPECT_EQ(0, dead);
 
   {
+    // Non-empty.
     StaticVector<DestroyCounts, 5> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
@@ -353,30 +410,33 @@
 
   live = 0;
   {
+    // Copy.
     StaticVector<DestroyCounts, 5> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
 
-    auto copy = counts;
+    const auto copy = counts;
   }
   EXPECT_EQ(6, live);
   EXPECT_EQ(0, dead);
 
   live = 0;
   {
+    // Move.
     StaticVector<DestroyCounts, 5> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
 
-    auto move = std::move(counts);
+    const auto move = std::move(counts);
   }
   EXPECT_EQ(3, live);
   EXPECT_EQ(3, dead);
 
   live = dead = 0;
   {
+    // Swap.
     StaticVector<DestroyCounts, 5> counts1;
     counts1.emplace_back(live, dead);
     counts1.emplace_back(live, dead);
@@ -384,29 +444,34 @@
 
     StaticVector<DestroyCounts, 5> counts2;
     counts2.emplace_back(live, dead);
+    counts2.emplace_back(live, dead);
 
     swap(counts1, counts2);
 
+    EXPECT_EQ(2u, counts1.size());
+    EXPECT_EQ(3u, counts2.size());
+
     EXPECT_EQ(0, live);
-    EXPECT_EQ(2, dead);
+    EXPECT_EQ(7, dead);  // 3 moves per swap, plus 1 move.
 
     dead = 0;
   }
-  EXPECT_EQ(4, live);
+  EXPECT_EQ(5, live);
   EXPECT_EQ(0, dead);
 }
 
 TEST(StaticVector, Clear) {
   int live = 0;
   int dead = 0;
+  {
+    StaticVector<DestroyCounts, 5> counts;
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
 
-  StaticVector<DestroyCounts, 5> counts;
-  counts.emplace_back(live, dead);
-  counts.emplace_back(live, dead);
+    counts.clear();
 
-  counts.clear();
-
-  EXPECT_TRUE(counts.empty());
+    EXPECT_TRUE(counts.empty());
+  }
   EXPECT_EQ(2, live);
   EXPECT_EQ(0, dead);
 }
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index 3ce381b..a3eebdd 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -78,7 +78,7 @@
 struct FrameCallback {
     AChoreographer_frameCallback callback;
     AChoreographer_frameCallback64 callback64;
-    AChoreographer_extendedFrameCallback extendedCallback;
+    AChoreographer_vsyncCallback vsyncCallback;
     void* data;
     nsecs_t dueTime;
 
@@ -121,7 +121,7 @@
     explicit Choreographer(const sp<Looper>& looper) EXCLUDES(gChoreographers.lock);
     void postFrameCallbackDelayed(AChoreographer_frameCallback cb,
                                   AChoreographer_frameCallback64 cb64,
-                                  AChoreographer_extendedFrameCallback extendedCallback, void* data,
+                                  AChoreographer_vsyncCallback vsyncCallback, void* data,
                                   nsecs_t delay);
     void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data)
             EXCLUDES(gChoreographers.lock);
@@ -230,10 +230,10 @@
 
 void Choreographer::postFrameCallbackDelayed(AChoreographer_frameCallback cb,
                                              AChoreographer_frameCallback64 cb64,
-                                             AChoreographer_extendedFrameCallback extendedCallback,
-                                             void* data, nsecs_t delay) {
+                                             AChoreographer_vsyncCallback vsyncCallback, void* data,
+                                             nsecs_t delay) {
     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
-    FrameCallback callback{cb, cb64, extendedCallback, data, now + delay};
+    FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay};
     {
         std::lock_guard<std::mutex> _l{mLock};
         mFrameCallbacks.push(callback);
@@ -389,13 +389,13 @@
     }
     mLastVsyncEventData = vsyncEventData;
     for (const auto& cb : callbacks) {
-        if (cb.extendedCallback != nullptr) {
+        if (cb.vsyncCallback != nullptr) {
             const ChoreographerFrameCallbackDataImpl frameCallbackData =
                     createFrameCallbackData(timestamp);
             mInCallback = true;
-            cb.extendedCallback(reinterpret_cast<const AChoreographerFrameCallbackData*>(
-                                        &frameCallbackData),
-                                cb.data);
+            cb.vsyncCallback(reinterpret_cast<const AChoreographerFrameCallbackData*>(
+                                     &frameCallbackData),
+                             cb.data);
             mInCallback = false;
         } else if (cb.callback64 != nullptr) {
             cb.callback64(timestamp, cb.data);
@@ -522,10 +522,9 @@
                                                     void* data, uint32_t delayMillis) {
     return AChoreographer_postFrameCallbackDelayed64(choreographer, callback, data, delayMillis);
 }
-void AChoreographer_routePostExtendedFrameCallback(AChoreographer* choreographer,
-                                                   AChoreographer_extendedFrameCallback callback,
-                                                   void* data) {
-    return AChoreographer_postExtendedFrameCallback(choreographer, callback, data);
+void AChoreographer_routePostVsyncCallback(AChoreographer* choreographer,
+                                           AChoreographer_vsyncCallback callback, void* data) {
+    return AChoreographer_postVsyncCallback(choreographer, callback, data);
 }
 void AChoreographer_routeRegisterRefreshRateCallback(AChoreographer* choreographer,
                                                      AChoreographer_refreshRateCallback callback,
@@ -553,9 +552,10 @@
         const AChoreographerFrameCallbackData* data, size_t index) {
     return AChoreographerFrameCallbackData_getFrameTimelineVsyncId(data, index);
 }
-int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTimeNanos(
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentationTimeNanos(
         const AChoreographerFrameCallbackData* data, size_t index) {
-    return AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos(data, index);
+    return AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(data,
+                                                                                         index);
 }
 int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineDeadlineNanos(
         const AChoreographerFrameCallbackData* data, size_t index) {
@@ -589,9 +589,8 @@
     AChoreographer_to_Choreographer(choreographer)
             ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, ms2ns(delayMillis));
 }
-void AChoreographer_postExtendedFrameCallback(AChoreographer* choreographer,
-                                              AChoreographer_extendedFrameCallback callback,
-                                              void* data) {
+void AChoreographer_postVsyncCallback(AChoreographer* choreographer,
+                                      AChoreographer_vsyncCallback callback, void* data) {
     AChoreographer_to_Choreographer(choreographer)
             ->postFrameCallbackDelayed(nullptr, nullptr, callback, data, 0);
 }
@@ -650,7 +649,7 @@
     LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds");
     return frameCallbackData->vsyncEventData.frameTimelines[index].vsyncId;
 }
-int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos(
+int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(
         const AChoreographerFrameCallbackData* data, size_t index) {
     const ChoreographerFrameCallbackDataImpl* frameCallbackData =
             AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
diff --git a/libs/nativedisplay/include-private/private/android/choreographer.h b/libs/nativedisplay/include-private/private/android/choreographer.h
index d650c26..19be5bd 100644
--- a/libs/nativedisplay/include-private/private/android/choreographer.h
+++ b/libs/nativedisplay/include-private/private/android/choreographer.h
@@ -50,9 +50,8 @@
 void AChoreographer_routePostFrameCallbackDelayed64(AChoreographer* choreographer,
                                                     AChoreographer_frameCallback64 callback,
                                                     void* data, uint32_t delayMillis);
-void AChoreographer_routePostExtendedFrameCallback(AChoreographer* choreographer,
-                                                   AChoreographer_extendedFrameCallback callback,
-                                                   void* data);
+void AChoreographer_routePostVsyncCallback(AChoreographer* choreographer,
+                                           AChoreographer_vsyncCallback callback, void* data);
 void AChoreographer_routeRegisterRefreshRateCallback(AChoreographer* choreographer,
                                                      AChoreographer_refreshRateCallback callback,
                                                      void* data);
@@ -67,7 +66,7 @@
         const AChoreographerFrameCallbackData* data);
 AVsyncId AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(
         const AChoreographerFrameCallbackData* data, size_t index);
-int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTimeNanos(
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentationTimeNanos(
         const AChoreographerFrameCallbackData* data, size_t index);
 int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineDeadlineNanos(
         const AChoreographerFrameCallbackData* data, size_t index);
diff --git a/libs/nativedisplay/libnativedisplay.map.txt b/libs/nativedisplay/libnativedisplay.map.txt
index 6579313..2da10cd 100644
--- a/libs/nativedisplay/libnativedisplay.map.txt
+++ b/libs/nativedisplay/libnativedisplay.map.txt
@@ -7,12 +7,12 @@
     AChoreographer_postFrameCallbackDelayed64; # apex # introduced=30
     AChoreographer_registerRefreshRateCallback; # apex # introduced=30
     AChoreographer_unregisterRefreshRateCallback; # apex # introduced=30
-    AChoreographer_postExtendedFrameCallback; # apex # introduced=33
+    AChoreographer_postVsyncCallback; # apex # introduced=33
     AChoreographerFrameCallbackData_getFrameTimeNanos; # apex # introduced=33
     AChoreographerFrameCallbackData_getFrameTimelinesLength; # apex # introduced=33
     AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex; # apex # introduced=33
     AChoreographerFrameCallbackData_getFrameTimelineVsyncId; # apex # introduced=33
-    AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos; # apex # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos; # apex # introduced=33
     AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos; # apex # introduced=33
     AChoreographer_create; # apex # introduced=30
     AChoreographer_destroy; # apex # introduced=30
@@ -35,12 +35,12 @@
       android::AChoreographer_routePostFrameCallbackDelayed64*;
       android::AChoreographer_routeRegisterRefreshRateCallback*;
       android::AChoreographer_routeUnregisterRefreshRateCallback*;
-      android::AChoreographer_routePostExtendedFrameCallback*;
+      android::AChoreographer_routePostVsyncCallback*;
       android::AChoreographerFrameCallbackData_routeGetFrameTimeNanos*;
       android::AChoreographerFrameCallbackData_routeGetFrameTimelinesLength*;
       android::AChoreographerFrameCallbackData_routeGetPreferredFrameTimelineIndex*;
       android::AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId*;
-      android::AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTimeNanos*;
+      android::AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentationTimeNanos*;
       android::AChoreographerFrameCallbackData_routeGetFrameTimelineDeadlineNanos*;
       android::AChoreographer_signalRefreshRateCallbacks*;
       android::AChoreographer_getFrameInterval*;
diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index 2c51ccd..40ba5ad 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -54,6 +54,10 @@
     // dataspace, in non-linear space.
     mat4 colorTransform = mat4();
 
+    // If true, and colorTransform is non-identity, most client draw calls can
+    // ignore it. Some draws (e.g. screen decorations) may need it, though.
+    bool deviceHandlesColorTransform = false;
+
     // An additional orientation flag to be applied after clipping the output.
     // By way of example, this may be used for supporting fullscreen screenshot
     // capture of a device in landscape while the buffer is in portrait
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 763b82d..b467b35 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -792,7 +792,7 @@
 
     // setup color filter if necessary
     sk_sp<SkColorFilter> displayColorTransform;
-    if (display.colorTransform != mat4()) {
+    if (display.colorTransform != mat4() && !display.deviceHandlesColorTransform) {
         displayColorTransform = SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform));
     }
     const bool ctModifiesAlpha =
@@ -1107,11 +1107,37 @@
 
             if (imageTextureRef->colorType() == kAlpha_8_SkColorType) {
                 LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with A8");
-                float matrix[] = { 0, 0, 0, 0, 0,
-                                   0, 0, 0, 0, 0,
-                                   0, 0, 0, 0, 0,
-                                   0, 0, 0, -1, 1 };
-                paint.setColorFilter(SkColorFilters::Matrix(matrix));
+
+                // SysUI creates the alpha layer as a coverage layer, which is
+                // appropriate for the DPU. Use a color matrix to convert it to
+                // a mask.
+                // TODO (b/219525258): Handle input as a mask.
+                //
+                // The color matrix will convert A8 pixels with no alpha to
+                // black, as described by this vector. If the display handles
+                // the color transform, we need to invert it to find the color
+                // that will result in black after the DPU applies the transform.
+                SkV4 black{0.0f, 0.0f, 0.0f, 1.0f}; // r, g, b, a
+                if (display.colorTransform != mat4() && display.deviceHandlesColorTransform) {
+                    SkM44 colorSpaceMatrix = getSkM44(display.colorTransform);
+                    if (colorSpaceMatrix.invert(&colorSpaceMatrix)) {
+                        black = colorSpaceMatrix * black;
+                    } else {
+                        // We'll just have to use 0,0,0 as black, which should
+                        // be close to correct.
+                        ALOGI("Could not invert colorTransform!");
+                    }
+                }
+                SkColorMatrix colorMatrix(0, 0, 0, 0, black[0],
+                                          0, 0, 0, 0, black[1],
+                                          0, 0, 0, 0, black[2],
+                                          0, 0, 0, -1, 1);
+                if (display.colorTransform != mat4() && !display.deviceHandlesColorTransform) {
+                    // On the other hand, if the device doesn't handle it, we
+                    // have to apply it ourselves.
+                    colorMatrix.postConcat(toSkColorMatrix(display.colorTransform));
+                }
+                paint.setColorFilter(SkColorFilters::Matrix(colorMatrix));
             }
         } else {
             ATRACE_NAME("DrawColor");
@@ -1134,8 +1160,8 @@
             paint.setBlendMode(SkBlendMode::kSrc);
         }
 
-        // A color filter will have been set for an A8 buffer. Do not replace
-        // it with the displayColorTransform, which shouldn't affect A8.
+        // An A8 buffer will already have the proper color filter attached to
+        // its paint, including the displayColorTransform as needed.
         if (!paint.getColorFilter()) {
             paint.setColorFilter(displayColorTransform);
         }
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index e197150..add7a94 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -2627,6 +2627,7 @@
 
     const auto r8Buffer = allocateR8Buffer(2, 1);
     if (!r8Buffer) {
+        GTEST_SKIP() << "Test is only necessary on devices that support r8";
         return;
     }
     {
@@ -2677,6 +2678,144 @@
     expectBufferColor(Rect(0, 0, 1, 1), 0,   0, 0, 255);
     expectBufferColor(Rect(1, 0, 2, 1), 0, 255, 0, 255);
 }
+
+TEST_P(RenderEngineTest, r8_respects_color_transform) {
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    initializeRenderEngine();
+
+    const auto r8Buffer = allocateR8Buffer(2, 1);
+    if (!r8Buffer) {
+        GTEST_SKIP() << "Test is only necessary on devices that support r8";
+        return;
+    }
+    {
+        uint8_t* pixels;
+        r8Buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                    reinterpret_cast<void**>(&pixels));
+        pixels[0] = 0;
+        pixels[1] = 255;
+        r8Buffer->getBuffer()->unlock();
+    }
+
+    const auto rect = Rect(0, 0, 2, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .outputDataspace = ui::Dataspace::SRGB,
+            // Verify that the R8 layer respects the color transform when
+            // deviceHandlesColorTransform is false. This transform converts
+            // pure red to pure green. That will occur when the R8 buffer is
+            // 255. When the R8 buffer is 0, it will still change to black, as
+            // with r8_behaves_as_mask.
+            .colorTransform = mat4(0, 1, 0, 0,
+                                   0, 0, 0, 0,
+                                   0, 0, 1, 0,
+                                   0, 0, 0, 1),
+            .deviceHandlesColorTransform = false,
+    };
+
+    const auto redBuffer = allocateAndFillSourceBuffer(2, 1, ubyte4(255, 0, 0, 255));
+    const renderengine::LayerSettings redLayer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = redBuffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+    };
+    const renderengine::LayerSettings r8Layer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = r8Buffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{redLayer, r8Layer};
+    invokeDraw(display, layers);
+
+    expectBufferColor(Rect(0, 0, 1, 1), 0,   0, 0, 255);
+    expectBufferColor(Rect(1, 0, 2, 1), 0, 255, 0, 255);
+}
+
+TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) {
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    initializeRenderEngine();
+
+    const auto r8Buffer = allocateR8Buffer(2, 1);
+    if (!r8Buffer) {
+        GTEST_SKIP() << "Test is only necessary on devices that support r8";
+        return;
+    }
+    {
+        uint8_t* pixels;
+        r8Buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                    reinterpret_cast<void**>(&pixels));
+        pixels[0] = 0;
+        pixels[1] = 255;
+        r8Buffer->getBuffer()->unlock();
+    }
+
+    const auto rect = Rect(0, 0, 2, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .outputDataspace = ui::Dataspace::SRGB,
+            // If deviceHandlesColorTransform is true, pixels where the A8
+            // buffer is opaque are unaffected. If the colorTransform is
+            // invertible, pixels where the A8 buffer are transparent have the
+            // inverse applied to them so that the DPU will convert them back to
+            // black. Test with an arbitrary, invertible matrix.
+            .colorTransform = mat4(1, 0, 0, 2,
+                                   3, 1, 2, 5,
+                                   0, 5, 3, 0,
+                                   0, 1, 0, 2),
+            .deviceHandlesColorTransform = true,
+    };
+
+    const auto redBuffer = allocateAndFillSourceBuffer(2, 1, ubyte4(255, 0, 0, 255));
+    const renderengine::LayerSettings redLayer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = redBuffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+    };
+    const renderengine::LayerSettings r8Layer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = r8Buffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{redLayer, r8Layer};
+    invokeDraw(display, layers);
+
+    expectBufferColor(Rect(1, 0, 2, 1), 255, 0, 0, 255); // Still red.
+    expectBufferColor(Rect(0, 0, 1, 1), 0,  70, 0, 255);
+}
 } // namespace renderengine
 } // namespace android
 
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index a8e9bae..7d30d0c 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -335,12 +335,15 @@
     // button will likely wake the device.
     // TODO: Use the input device configuration to control this behavior more finely.
     uint32_t policyFlags = 0;
+    int32_t displayId = ADISPLAY_ID_NONE;
+    if (getDeviceContext().getAssociatedViewport()) {
+        displayId = getDeviceContext().getAssociatedViewport()->displayId;
+    }
 
     NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(),
-                          AINPUT_SOURCE_JOYSTICK, ADISPLAY_ID_NONE, policyFlags,
-                          AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState,
-                          MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
-                          &pointerProperties, &pointerCoords, 0, 0,
+                          AINPUT_SOURCE_JOYSTICK, displayId, policyFlags, AMOTION_EVENT_ACTION_MOVE,
+                          0, 0, metaState, buttonState, MotionClassification::NONE,
+                          AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords, 0, 0,
                           AMOTION_EVENT_INVALID_CURSOR_POSITION,
                           AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {});
     getListener().notifyMotion(&args);
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index f729ba9..c1934ff 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -550,18 +550,15 @@
 
 /**
  * Determine which DisplayViewport to use.
- * 1. If display port is specified, return the matching viewport. If matching viewport not
- * found, then return.
+ * 1. If a device has associated display, get the matching viewport.
  * 2. Always use the suggested viewport from WindowManagerService for pointers.
- * 3. If a device has associated display, get the matching viewport by either unique id or by
- * the display type (internal or external).
+ * 3. Get the matching viewport by either unique id in idc file or by the display type
+ * (internal or external).
  * 4. Otherwise, use a non-display viewport.
  */
 std::optional<DisplayViewport> TouchInputMapper::findViewport() {
     if (mParameters.hasAssociatedDisplay && mDeviceMode != DeviceMode::UNSCALED) {
-        const std::optional<uint8_t> displayPort = getDeviceContext().getAssociatedDisplayPort();
-        if (displayPort) {
-            // Find the viewport that contains the same port
+        if (getDeviceContext().getAssociatedViewport()) {
             return getDeviceContext().getAssociatedViewport();
         }
 
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 54cf15d..9f33d23 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -20,6 +20,7 @@
 #include <InputReader.h>
 #include <InputReaderBase.h>
 #include <InputReaderFactory.h>
+#include <JoystickInputMapper.h>
 #include <KeyboardInputMapper.h>
 #include <MultiTouchInputMapper.h>
 #include <PeripheralController.h>
@@ -8251,6 +8252,25 @@
     ASSERT_EQ(DISPLAY_ID, args.displayId);
 }
 
+TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayUniqueId) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareAxes(POSITION);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID);
+
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareVirtualDisplay(DISPLAY_ORIENTATION_0);
+
+    // Send a touch event
+    processPosition(mapper, 100, 100);
+    processSync(mapper);
+
+    NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(VIRTUAL_DISPLAY_ID, args.displayId);
+}
+
 TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) {
     // Setup for second display.
     std::shared_ptr<FakePointerController> fakePointerController =
@@ -9260,6 +9280,67 @@
     ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
+// --- JoystickInputMapperTest ---
+
+class JoystickInputMapperTest : public InputMapperTest {
+protected:
+    static const int32_t RAW_X_MIN;
+    static const int32_t RAW_X_MAX;
+    static const int32_t RAW_Y_MIN;
+    static const int32_t RAW_Y_MAX;
+
+    void SetUp() override {
+        InputMapperTest::SetUp(InputDeviceClass::JOYSTICK | InputDeviceClass::EXTERNAL);
+    }
+    void prepareAxes() {
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, RAW_X_MIN, RAW_X_MAX, 0, 0);
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0);
+    }
+
+    void processAxis(JoystickInputMapper& mapper, int32_t axis, int32_t value) {
+        process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, axis, value);
+    }
+
+    void processSync(JoystickInputMapper& mapper) {
+        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+    }
+
+    void prepareVirtualDisplay(int32_t orientation) {
+        setDisplayInfoAndReconfigure(VIRTUAL_DISPLAY_ID, VIRTUAL_DISPLAY_WIDTH,
+                                     VIRTUAL_DISPLAY_HEIGHT, orientation, VIRTUAL_DISPLAY_UNIQUE_ID,
+                                     NO_PORT, ViewportType::VIRTUAL);
+    }
+};
+
+const int32_t JoystickInputMapperTest::RAW_X_MIN = -32767;
+const int32_t JoystickInputMapperTest::RAW_X_MAX = 32767;
+const int32_t JoystickInputMapperTest::RAW_Y_MIN = -32767;
+const int32_t JoystickInputMapperTest::RAW_Y_MAX = 32767;
+
+TEST_F(JoystickInputMapperTest, Configure_AssignsDisplayUniqueId) {
+    prepareAxes();
+    JoystickInputMapper& mapper = addMapperAndConfigure<JoystickInputMapper>();
+
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID);
+
+    prepareVirtualDisplay(DISPLAY_ORIENTATION_0);
+
+    // Send an axis event
+    processAxis(mapper, ABS_X, 100);
+    processSync(mapper);
+
+    NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(VIRTUAL_DISPLAY_ID, args.displayId);
+
+    // Send another axis event
+    processAxis(mapper, ABS_Y, 100);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(VIRTUAL_DISPLAY_ID, args.displayId);
+}
+
 // --- PeripheralControllerTest ---
 
 class PeripheralControllerTest : public testing::Test {
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 65f9731..e036252 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -1079,9 +1079,9 @@
     clientCompositionDisplay.targetLuminanceNits = outputState.clientTargetWhitePointNits;
 
     // Compute the global color transform matrix.
-    if (!outputState.usesDeviceComposition && !getSkipColorTransform()) {
-        clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
-    }
+    clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
+    clientCompositionDisplay.deviceHandlesColorTransform =
+            outputState.usesDeviceComposition || getSkipColorTransform();
 
     // Generate the client composition requests for the layers on this output.
     std::vector<LayerFE*> clientCompositionLayersFE;
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index e72bc9f..cab4b8d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -3487,7 +3487,8 @@
                                             .maxLuminance = kDefaultMaxLuminance,
                                             .currentLuminanceNits = kDefaultMaxLuminance,
                                             .outputDataspace = kDefaultOutputDataspace,
-                                            .colorTransform = mat4(),
+                                            .colorTransform = kDefaultColorTransformMat,
+                                            .deviceHandlesColorTransform = true,
                                             .orientation = kDefaultOutputOrientationFlags,
                                             .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
@@ -3505,7 +3506,8 @@
                                             .maxLuminance = kDefaultMaxLuminance,
                                             .currentLuminanceNits = kDisplayLuminance,
                                             .outputDataspace = kDefaultOutputDataspace,
-                                            .colorTransform = mat4(),
+                                            .colorTransform = kDefaultColorTransformMat,
+                                            .deviceHandlesColorTransform = true,
                                             .orientation = kDefaultOutputOrientationFlags,
                                             .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
@@ -3522,7 +3524,8 @@
                                             .maxLuminance = kDefaultMaxLuminance,
                                             .currentLuminanceNits = kDefaultMaxLuminance,
                                             .outputDataspace = kDefaultOutputDataspace,
-                                            .colorTransform = mat4(),
+                                            .colorTransform = kDefaultColorTransformMat,
+                                            .deviceHandlesColorTransform = true,
                                             .orientation = kDefaultOutputOrientationFlags,
                                             .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
@@ -3540,6 +3543,7 @@
                                             .currentLuminanceNits = kDefaultMaxLuminance,
                                             .outputDataspace = kDefaultOutputDataspace,
                                             .colorTransform = kDefaultColorTransformMat,
+                                            .deviceHandlesColorTransform = false,
                                             .orientation = kDefaultOutputOrientationFlags,
                                             .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
@@ -3557,6 +3561,7 @@
                                             .currentLuminanceNits = kDefaultMaxLuminance,
                                             .outputDataspace = kDefaultOutputDataspace,
                                             .colorTransform = kDefaultColorTransformMat,
+                                            .deviceHandlesColorTransform = false,
                                             .orientation = kDefaultOutputOrientationFlags,
                                             .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
@@ -3574,7 +3579,8 @@
                                             .maxLuminance = kDefaultMaxLuminance,
                                             .currentLuminanceNits = kDefaultMaxLuminance,
                                             .outputDataspace = kDefaultOutputDataspace,
-                                            .colorTransform = mat4(),
+                                            .colorTransform = kDefaultColorTransformMat,
+                                            .deviceHandlesColorTransform = true,
                                             .orientation = kDefaultOutputOrientationFlags,
                                             .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index f542161..eef0052 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -141,7 +141,7 @@
     return mCompositionDisplay->getRenderSurface()->getPageFlipCount();
 }
 
-std::pair<gui::DisplayInfo, ui::Transform> DisplayDevice::getInputInfo() const {
+auto DisplayDevice::getInputInfo() const -> InputInfo {
     gui::DisplayInfo info;
     info.displayId = getLayerStack().id;
 
@@ -166,10 +166,13 @@
 
     info.logicalWidth = getLayerStackSpaceRect().width();
     info.logicalHeight = getLayerStackSpaceRect().height();
-    return {info, displayTransform};
+
+    return {.info = info,
+            .transform = displayTransform,
+            .receivesInput = receivesInput(),
+            .isSecure = isSecure()};
 }
 
-// ----------------------------------------------------------------------------
 void DisplayDevice::setPowerMode(hal::PowerMode mode) {
     mPowerMode = mode;
     getCompositionDisplay()->setCompositionEnabled(mPowerMode != hal::PowerMode::OFF);
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 3cae30f..bf6b31a 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -174,9 +174,14 @@
         return mDeviceProductInfo;
     }
 
-    // Get the DisplayInfo that will be sent to InputFlinger, and the display transform that should
-    // be applied to all the input windows on the display.
-    std::pair<gui::DisplayInfo, ui::Transform> getInputInfo() const;
+    struct InputInfo {
+        gui::DisplayInfo info;
+        ui::Transform transform;
+        bool receivesInput;
+        bool isSecure;
+    };
+
+    InputInfo getInputInfo() const;
 
     /* ------------------------------------------------------------------------
      * Display power mode management.
diff --git a/services/surfaceflinger/FlagManager.cpp b/services/surfaceflinger/FlagManager.cpp
index e09a192..bd3cf74 100644
--- a/services/surfaceflinger/FlagManager.cpp
+++ b/services/surfaceflinger/FlagManager.cpp
@@ -101,7 +101,6 @@
 }
 
 bool FlagManager::use_skia_tracing() const {
-    ALOGD("use_skia_tracing ?");
     std::optional<bool> sysPropVal =
             doParse<bool>(base::GetProperty(PROPERTY_SKIA_ATRACE_ENABLED, "").c_str());
     return getValue("SkiaTracingFeature__use_skia_tracing", sysPropVal, false);
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 86e96d7..81747d5 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -492,17 +492,22 @@
 
 void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate,
                                       nsecs_t& deadlineDelta) {
-    if (mPredictionState == PredictionState::Expired ||
-        mActuals.presentTime == Fence::SIGNAL_TIME_INVALID) {
+    if (mActuals.presentTime == Fence::SIGNAL_TIME_INVALID) {
         // Cannot do any classification for invalid present time.
-        // For prediction expired case, we do not know what happened here to classify this
-        // correctly. This could potentially be AppDeadlineMissed but that's assuming no app will
-        // request frames 120ms apart.
         mJankType = JankType::Unknown;
         deadlineDelta = -1;
         return;
     }
 
+    if (mPredictionState == PredictionState::Expired) {
+        // We classify prediction expired as AppDeadlineMissed as the
+        // TokenManager::kMaxTokens we store is large enough to account for a
+        // reasonable app, so prediction expire would mean a huge scheduling delay.
+        mJankType = JankType::AppDeadlineMissed;
+        deadlineDelta = -1;
+        return;
+    }
+
     if (mPredictionState == PredictionState::None) {
         // Cannot do jank classification on frames that don't have a token.
         return;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 3b9cfa6..eeeaac1 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -69,11 +69,6 @@
 using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
 using RefreshRate = RefreshRateConfigs::RefreshRate;
 
-bool RefreshRate::inPolicy(Fps minRefreshRate, Fps maxRefreshRate) const {
-    using fps_approx_ops::operator<=;
-    return minRefreshRate <= getFps() && getFps() <= maxRefreshRate;
-}
-
 std::string RefreshRate::toString() const {
     return base::StringPrintf("{id=%d, hwcId=%d, fps=%.2f, width=%d, height=%d group=%d}",
                               getModeId().value(), mode->getHwcId(), getFps().getValue(),
@@ -84,7 +79,7 @@
     return base::StringPrintf("default mode ID: %d, allowGroupSwitching = %d"
                               ", primary range: %s, app request range: %s",
                               defaultMode.value(), allowGroupSwitching,
-                              primaryRange.toString().c_str(), appRequestRange.toString().c_str());
+                              to_string(primaryRange).c_str(), to_string(appRequestRange).c_str());
 }
 
 std::pair<nsecs_t, nsecs_t> RefreshRateConfigs::getDisplayFrames(nsecs_t layerPeriod,
@@ -396,8 +391,9 @@
                 continue;
             }
 
-            bool inPrimaryRange = scores[i].refreshRate->inPolicy(policy->primaryRange.min,
-                                                                  policy->primaryRange.max);
+            const bool inPrimaryRange =
+                    policy->primaryRange.includes(scores[i].refreshRate->getFps());
+
             if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
                 !(layer.focused &&
                   (layer.vote == LayerVoteType::ExplicitDefault ||
@@ -746,7 +742,7 @@
         return false;
     }
     const RefreshRate& refreshRate = *iter->second;
-    if (!refreshRate.inPolicy(policy.primaryRange.min, policy.primaryRange.max)) {
+    if (!policy.primaryRange.includes(refreshRate.getFps())) {
         ALOGE("Default mode is not in the primary range.");
         return false;
     }
@@ -843,7 +839,7 @@
     ALOGV("constructAvailableRefreshRates: %s ", policy->toString().c_str());
 
     auto filterRefreshRates =
-            [&](Fps min, Fps max, const char* listName,
+            [&](FpsRange range, const char* rangeName,
                 std::vector<const RefreshRate*>* outRefreshRates) REQUIRES(mLock) {
                 getSortedRefreshRateListLocked(
                         [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
@@ -855,13 +851,13 @@
                                     mode->getDpiY() == defaultMode->getDpiY() &&
                                     (policy->allowGroupSwitching ||
                                      mode->getGroup() == defaultMode->getGroup()) &&
-                                    refreshRate.inPolicy(min, max);
+                                    range.includes(mode->getFps());
                         },
                         outRefreshRates);
 
-                LOG_ALWAYS_FATAL_IF(outRefreshRates->empty(),
-                                    "No matching modes for %s range: min=%s max=%s", listName,
-                                    to_string(min).c_str(), to_string(max).c_str());
+                LOG_ALWAYS_FATAL_IF(outRefreshRates->empty(), "No matching modes for %s range %s",
+                                    rangeName, to_string(range).c_str());
+
                 auto stringifyRefreshRates = [&]() -> std::string {
                     std::string str;
                     for (auto refreshRate : *outRefreshRates) {
@@ -869,13 +865,11 @@
                     }
                     return str;
                 };
-                ALOGV("%s refresh rates: %s", listName, stringifyRefreshRates().c_str());
+                ALOGV("%s refresh rates: %s", rangeName, stringifyRefreshRates().c_str());
             };
 
-    filterRefreshRates(policy->primaryRange.min, policy->primaryRange.max, "primary",
-                       &mPrimaryRefreshRates);
-    filterRefreshRates(policy->appRequestRange.min, policy->appRequestRange.max, "app request",
-                       &mAppRequestRefreshRates);
+    filterRefreshRates(policy->primaryRange, "primary", &mPrimaryRefreshRates);
+    filterRefreshRates(policy->appRequestRange, "app request", &mAppRequestRefreshRates);
 }
 
 Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index ade1787..f5b97c2 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -22,7 +22,6 @@
 #include <type_traits>
 #include <utility>
 
-#include <android-base/stringprintf.h>
 #include <gui/DisplayEventReceiver.h>
 
 #include <scheduler/Fps.h>
@@ -101,21 +100,6 @@
     using AllRefreshRatesMapType =
             std::unordered_map<DisplayModeId, std::unique_ptr<const RefreshRate>>;
 
-    struct FpsRange {
-        Fps min = Fps::fromValue(0.f);
-        Fps max = Fps::fromValue(std::numeric_limits<float>::max());
-
-        bool operator==(const FpsRange& other) const {
-            return isApproxEqual(min, other.min) && isApproxEqual(max, other.max);
-        }
-
-        bool operator!=(const FpsRange& other) const { return !(*this == other); }
-
-        std::string toString() const {
-            return base::StringPrintf("[%s %s]", to_string(min).c_str(), to_string(max).c_str());
-        }
-    };
-
     struct Policy {
     private:
         static constexpr int kAllowGroupSwitchingDefault = false;
@@ -140,24 +124,24 @@
 
         Policy() = default;
 
-        Policy(DisplayModeId defaultMode, const FpsRange& range)
+        Policy(DisplayModeId defaultMode, FpsRange range)
               : Policy(defaultMode, kAllowGroupSwitchingDefault, range, range) {}
 
-        Policy(DisplayModeId defaultMode, bool allowGroupSwitching, const FpsRange& range)
+        Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange range)
               : Policy(defaultMode, allowGroupSwitching, range, range) {}
 
-        Policy(DisplayModeId defaultMode, const FpsRange& primaryRange,
-               const FpsRange& appRequestRange)
+        Policy(DisplayModeId defaultMode, FpsRange primaryRange, FpsRange appRequestRange)
               : Policy(defaultMode, kAllowGroupSwitchingDefault, primaryRange, appRequestRange) {}
 
-        Policy(DisplayModeId defaultMode, bool allowGroupSwitching, const FpsRange& primaryRange,
-               const FpsRange& appRequestRange)
+        Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange primaryRange,
+               FpsRange appRequestRange)
               : defaultMode(defaultMode),
                 allowGroupSwitching(allowGroupSwitching),
                 primaryRange(primaryRange),
                 appRequestRange(appRequestRange) {}
 
         bool operator==(const Policy& other) const {
+            using namespace fps_approx_ops;
             return defaultMode == other.defaultMode && primaryRange == other.primaryRange &&
                     appRequestRange == other.appRequestRange &&
                     allowGroupSwitching == other.allowGroupSwitching;
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
index 639b3e5..bd4f409 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <cmath>
+#include <limits>
 #include <ostream>
 #include <string>
 #include <type_traits>
@@ -60,6 +61,13 @@
     nsecs_t mPeriod = 0;
 };
 
+struct FpsRange {
+    Fps min = Fps::fromValue(0.f);
+    Fps max = Fps::fromValue(std::numeric_limits<float>::max());
+
+    bool includes(Fps) const;
+};
+
 static_assert(std::is_trivially_copyable_v<Fps>);
 
 constexpr Fps operator""_Hz(unsigned long long frequency) {
@@ -111,8 +119,21 @@
     return !isApproxLess(lhs, rhs);
 }
 
+inline bool operator==(FpsRange lhs, FpsRange rhs) {
+    return isApproxEqual(lhs.min, rhs.min) && isApproxEqual(lhs.max, rhs.max);
+}
+
+inline bool operator!=(FpsRange lhs, FpsRange rhs) {
+    return !(lhs == rhs);
+}
+
 } // namespace fps_approx_ops
 
+inline bool FpsRange::includes(Fps fps) const {
+    using fps_approx_ops::operator<=;
+    return min <= fps && fps <= max;
+}
+
 struct FpsApproxEqual {
     bool operator()(Fps lhs, Fps rhs) const { return isApproxEqual(lhs, rhs); }
 };
@@ -125,4 +146,9 @@
     return stream << to_string(fps);
 }
 
+inline std::string to_string(FpsRange range) {
+    const auto [min, max] = range;
+    return base::StringPrintf("[%s, %s]", to_string(min).c_str(), to_string(max).c_str());
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 9716d8e..59c07b6 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -50,6 +50,7 @@
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
 #include <ftl/future.h>
+#include <ftl/small_map.h>
 #include <gui/BufferQueue.h>
 #include <gui/DebugEGLImageTracker.h>
 #include <gui/IProducerListener.h>
@@ -146,15 +147,16 @@
 
 #define MAIN_THREAD ACQUIRE(mStateLock) RELEASE(mStateLock)
 
+// Note: The parentheses around `expr` are needed to deduce an lvalue or rvalue reference.
 #define ON_MAIN_THREAD(expr)                                       \
-    [&] {                                                          \
+    [&]() -> decltype(auto) {                                      \
         LOG_FATAL_IF(std::this_thread::get_id() != mMainThreadId); \
         UnnecessaryLock lock(mStateLock);                          \
         return (expr);                                             \
     }()
 
 #define MAIN_THREAD_GUARD(expr)                                    \
-    [&] {                                                          \
+    [&]() -> decltype(auto) {                                      \
         LOG_FATAL_IF(std::this_thread::get_id() != mMainThreadId); \
         MainThreadScopedGuard lock(SF_MAIN_THREAD);                \
         return (expr);                                             \
@@ -1344,9 +1346,8 @@
     auto future = mScheduler->schedule([=]() MAIN_THREAD {
         ATRACE_CALL();
         if (mPowerAdvisor.isUsingExpensiveRendering()) {
-            const auto& displays = ON_MAIN_THREAD(mDisplays);
-            for (const auto& [_, display] : displays) {
-                const static constexpr auto kDisable = false;
+            for (const auto& [_, display] : mDisplays) {
+                constexpr bool kDisable = false;
                 mPowerAdvisor.setExpensiveRenderingExpected(display->getId(), kDisable);
             }
         }
@@ -3235,9 +3236,7 @@
         return;
     }
 
-    const auto& displays = ON_MAIN_THREAD(mDisplays);
-
-    for (const auto& [_, display] : displays) {
+    for (const auto& [_, display] : ON_MAIN_THREAD(mDisplays)) {
         if (const auto brightness = display->getStagedBrightness(); brightness) {
             if (!needsComposite) {
                 const status_t error =
@@ -3258,60 +3257,48 @@
 
 void SurfaceFlinger::buildWindowInfos(std::vector<WindowInfo>& outWindowInfos,
                                       std::vector<DisplayInfo>& outDisplayInfos) {
-    struct Details {
-        Details(bool receivesInput, bool isSecure, const ui::Transform& transform,
-                const DisplayInfo& info)
-              : receivesInput(receivesInput),
-                isSecure(isSecure),
-                transform(std::move(transform)),
-                info(std::move(info)) {}
-        bool receivesInput;
-        bool isSecure;
-        ui::Transform transform;
-        DisplayInfo info;
-    };
-    std::unordered_map<uint32_t /*layerStackId*/, Details> inputDisplayDetails;
+    ftl::SmallMap<ui::LayerStack, DisplayDevice::InputInfo, 4> displayInputInfos;
+
     for (const auto& [_, display] : ON_MAIN_THREAD(mDisplays)) {
-        const uint32_t layerStackId = display->getLayerStack().id;
-        const auto& [info, transform] = display->getInputInfo();
-        const auto& [it, emplaced] =
-                inputDisplayDetails.try_emplace(layerStackId, display->receivesInput(),
-                                                display->isSecure(), transform, info);
+        const auto layerStack = display->getLayerStack();
+        const auto info = display->getInputInfo();
+
+        const auto [it, emplaced] = displayInputInfos.try_emplace(layerStack, info);
         if (emplaced) {
             continue;
         }
 
-        // There is more than one display for the layerStack. In this case, the first display that
-        // is configured to receive input takes precedence.
-        auto& details = it->second;
-        if (details.receivesInput) {
+        // If the layer stack is mirrored on multiple displays, the first display that is configured
+        // to receive input takes precedence.
+        auto& otherInfo = it->second;
+        if (otherInfo.receivesInput) {
             ALOGW_IF(display->receivesInput(),
                      "Multiple displays claim to accept input for the same layer stack: %u",
-                     layerStackId);
-            continue;
+                     layerStack.id);
+        } else {
+            otherInfo = info;
         }
-        details.receivesInput = display->receivesInput();
-        details.isSecure = display->isSecure();
-        details.transform = std::move(transform);
-        details.info = std::move(info);
     }
 
+    static size_t sNumWindowInfos = 0;
+    outWindowInfos.reserve(sNumWindowInfos);
+    sNumWindowInfos = 0;
+
     mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
         if (!layer->needsInputInfo()) return;
 
-        const uint32_t layerStackId = layer->getLayerStack().id;
-        const auto it = inputDisplayDetails.find(layerStackId);
-        if (it == inputDisplayDetails.end()) {
-            // Do not create WindowInfos for windows on displays that cannot receive input.
-            return;
+        // Do not create WindowInfos for windows on displays that cannot receive input.
+        if (const auto opt = displayInputInfos.get(layer->getLayerStack())) {
+            const auto& info = opt->get();
+            outWindowInfos.push_back(layer->fillInputInfo(info.transform, info.isSecure));
         }
-
-        const auto& details = it->second;
-        outWindowInfos.push_back(layer->fillInputInfo(details.transform, details.isSecure));
     });
 
-    for (const auto& [_, details] : inputDisplayDetails) {
-        outDisplayInfos.push_back(std::move(details.info));
+    sNumWindowInfos = outWindowInfos.size();
+
+    outDisplayInfos.reserve(displayInputInfos.size());
+    for (const auto& [_, info] : displayInputInfos) {
+        outDisplayInfos.push_back(info.info);
     }
 }
 
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index 23cd993..30b9d8f 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-#include "WindowInfosListenerInvoker.h"
+#include <ftl/small_vector.h>
 #include <gui/ISurfaceComposer.h>
-#include <unordered_set>
+
 #include "SurfaceFlinger.h"
+#include "WindowInfosListenerInvoker.h"
 
 namespace android {
 
@@ -41,18 +42,17 @@
       : mFlinger(flinger),
         mWindowInfosReportedListener(sp<WindowInfosReportedListener>::make(*this)) {}
 
-void WindowInfosListenerInvoker::addWindowInfosListener(
-        const sp<IWindowInfosListener>& windowInfosListener) {
-    sp<IBinder> asBinder = IInterface::asBinder(windowInfosListener);
-
+void WindowInfosListenerInvoker::addWindowInfosListener(sp<IWindowInfosListener> listener) {
+    sp<IBinder> asBinder = IInterface::asBinder(listener);
     asBinder->linkToDeath(this);
+
     std::scoped_lock lock(mListenersMutex);
-    mWindowInfosListeners.emplace(asBinder, windowInfosListener);
+    mWindowInfosListeners.try_emplace(asBinder, std::move(listener));
 }
 
 void WindowInfosListenerInvoker::removeWindowInfosListener(
-        const sp<IWindowInfosListener>& windowInfosListener) {
-    sp<IBinder> asBinder = IInterface::asBinder(windowInfosListener);
+        const sp<IWindowInfosListener>& listener) {
+    sp<IBinder> asBinder = IInterface::asBinder(listener);
 
     std::scoped_lock lock(mListenersMutex);
     asBinder->unlinkToDeath(this);
@@ -67,12 +67,11 @@
 void WindowInfosListenerInvoker::windowInfosChanged(const std::vector<WindowInfo>& windowInfos,
                                                     const std::vector<DisplayInfo>& displayInfos,
                                                     bool shouldSync) {
-    std::unordered_set<sp<IWindowInfosListener>, SpHash<IWindowInfosListener>> windowInfosListeners;
-
+    ftl::SmallVector<const sp<IWindowInfosListener>, kStaticCapacity> windowInfosListeners;
     {
         std::scoped_lock lock(mListenersMutex);
         for (const auto& [_, listener] : mWindowInfosListeners) {
-            windowInfosListeners.insert(listener);
+            windowInfosListeners.push_back(listener);
         }
     }
 
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h
index 2eabf48..d8d8d0f 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.h
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.h
@@ -20,10 +20,8 @@
 #include <android/gui/IWindowInfosListener.h>
 #include <android/gui/IWindowInfosReportedListener.h>
 #include <binder/IBinder.h>
+#include <ftl/small_map.h>
 #include <utils/Mutex.h>
-#include <unordered_map>
-
-#include "WpHash.h"
 
 namespace android {
 
@@ -33,7 +31,7 @@
 public:
     explicit WindowInfosListenerInvoker(SurfaceFlinger&);
 
-    void addWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener);
+    void addWindowInfosListener(sp<gui::IWindowInfosListener>);
     void removeWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener);
 
     void windowInfosChanged(const std::vector<gui::WindowInfo>&,
@@ -48,8 +46,11 @@
 
     SurfaceFlinger& mFlinger;
     std::mutex mListenersMutex;
-    std::unordered_map<wp<IBinder>, const sp<gui::IWindowInfosListener>, WpHash>
+
+    static constexpr size_t kStaticCapacity = 3;
+    ftl::SmallMap<wp<IBinder>, const sp<gui::IWindowInfosListener>, kStaticCapacity>
             mWindowInfosListeners GUARDED_BY(mListenersMutex);
+
     sp<gui::IWindowInfosReportedListener> mWindowInfosReportedListener;
     std::atomic<size_t> mCallbacksPending{0};
 };
diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp
index d2f6f71..b0b6bf1 100644
--- a/services/surfaceflinger/fuzzer/Android.bp
+++ b/services/surfaceflinger/fuzzer/Android.bp
@@ -111,3 +111,19 @@
         "surfaceflinger_scheduler_fuzzer.cpp",
     ],
 }
+
+cc_fuzz {
+    name: "surfaceflinger_layer_fuzzer",
+    defaults: [
+        "surfaceflinger_fuzz_defaults",
+    ],
+    header_libs: [
+        "libgui_headers",
+    ],
+    static_libs: [
+        "librenderengine",
+    ],
+    srcs: [
+        "surfaceflinger_layer_fuzzer.cpp",
+    ],
+}
diff --git a/services/surfaceflinger/fuzzer/README.md b/services/surfaceflinger/fuzzer/README.md
index 6231ca5..78a7596 100644
--- a/services/surfaceflinger/fuzzer/README.md
+++ b/services/surfaceflinger/fuzzer/README.md
@@ -3,6 +3,7 @@
 + [SurfaceFlinger](#SurfaceFlinger)
 + [DisplayHardware](#DisplayHardware)
 + [Scheduler](#Scheduler)
++ [Layer](#Layer)
 
 # <a name="SurfaceFlinger"></a> Fuzzer for SurfaceFlinger
 
@@ -70,3 +71,25 @@
   $ adb sync data
   $ adb shell /data/fuzz/arm64/surfaceflinger_scheduler_fuzzer/surfaceflinger_scheduler_fuzzer
 ```
+
+# <a name="Layer"></a> Fuzzer for Layer
+
+Layer supports the following parameters:
+1. Display Connection Types (parameter name: `fakeDisplay`)
+2. State Sets (parameter name: `traverseInZOrder`)
+3. State Subsets (parameter name: `prepareCompositionState`)
+4. Disconnect modes (parameter name: `disconnect`)
+5. Data Spaces (parameter name: `setDataspace`)
+
+You can find the possible values in the fuzzer's source code.
+
+#### Steps to run
+1. Build the fuzzer
+```
+  $ mm -j$(nproc) surfaceflinger_layer_fuzzer
+```
+2. Run on device
+```
+  $ adb sync data
+  $ adb shell /data/fuzz/arm64/surfaceflinger_layer_fuzzer/surfaceflinger_layer_fuzzer
+```
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
index 30a6fbd..afc1abd 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
@@ -29,35 +29,6 @@
         LatchUnsignaledConfig::Disabled,
 };
 
-static constexpr ui::PixelFormat kPixelFormats[] = {ui::PixelFormat::RGBA_8888,
-                                                    ui::PixelFormat::RGBX_8888,
-                                                    ui::PixelFormat::RGB_888,
-                                                    ui::PixelFormat::RGB_565,
-                                                    ui::PixelFormat::BGRA_8888,
-                                                    ui::PixelFormat::YCBCR_422_SP,
-                                                    ui::PixelFormat::YCRCB_420_SP,
-                                                    ui::PixelFormat::YCBCR_422_I,
-                                                    ui::PixelFormat::RGBA_FP16,
-                                                    ui::PixelFormat::RAW16,
-                                                    ui::PixelFormat::BLOB,
-                                                    ui::PixelFormat::IMPLEMENTATION_DEFINED,
-                                                    ui::PixelFormat::YCBCR_420_888,
-                                                    ui::PixelFormat::RAW_OPAQUE,
-                                                    ui::PixelFormat::RAW10,
-                                                    ui::PixelFormat::RAW12,
-                                                    ui::PixelFormat::RGBA_1010102,
-                                                    ui::PixelFormat::Y8,
-                                                    ui::PixelFormat::Y16,
-                                                    ui::PixelFormat::YV12,
-                                                    ui::PixelFormat::DEPTH_16,
-                                                    ui::PixelFormat::DEPTH_24,
-                                                    ui::PixelFormat::DEPTH_24_STENCIL_8,
-                                                    ui::PixelFormat::DEPTH_32F,
-                                                    ui::PixelFormat::DEPTH_32F_STENCIL_8,
-                                                    ui::PixelFormat::STENCIL_8,
-                                                    ui::PixelFormat::YCBCR_P010,
-                                                    ui::PixelFormat::HSV_888};
-
 static constexpr ui::Rotation kRotations[] = {ui::Rotation::Rotation0, ui::Rotation::Rotation90,
                                               ui::Rotation::Rotation180, ui::Rotation::Rotation270};
 
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 0a458c2..2fcf856 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -123,6 +123,35 @@
                                                 ui::ColorMode::BT2100_HLG,
                                                 ui::ColorMode::DISPLAY_BT2020};
 
+static constexpr ui::PixelFormat kPixelFormats[] = {ui::PixelFormat::RGBA_8888,
+                                                    ui::PixelFormat::RGBX_8888,
+                                                    ui::PixelFormat::RGB_888,
+                                                    ui::PixelFormat::RGB_565,
+                                                    ui::PixelFormat::BGRA_8888,
+                                                    ui::PixelFormat::YCBCR_422_SP,
+                                                    ui::PixelFormat::YCRCB_420_SP,
+                                                    ui::PixelFormat::YCBCR_422_I,
+                                                    ui::PixelFormat::RGBA_FP16,
+                                                    ui::PixelFormat::RAW16,
+                                                    ui::PixelFormat::BLOB,
+                                                    ui::PixelFormat::IMPLEMENTATION_DEFINED,
+                                                    ui::PixelFormat::YCBCR_420_888,
+                                                    ui::PixelFormat::RAW_OPAQUE,
+                                                    ui::PixelFormat::RAW10,
+                                                    ui::PixelFormat::RAW12,
+                                                    ui::PixelFormat::RGBA_1010102,
+                                                    ui::PixelFormat::Y8,
+                                                    ui::PixelFormat::Y16,
+                                                    ui::PixelFormat::YV12,
+                                                    ui::PixelFormat::DEPTH_16,
+                                                    ui::PixelFormat::DEPTH_24,
+                                                    ui::PixelFormat::DEPTH_24_STENCIL_8,
+                                                    ui::PixelFormat::DEPTH_32F,
+                                                    ui::PixelFormat::DEPTH_32F_STENCIL_8,
+                                                    ui::PixelFormat::STENCIL_8,
+                                                    ui::PixelFormat::YCBCR_P010,
+                                                    ui::PixelFormat::HSV_888};
+
 FloatRect getFuzzedFloatRect(FuzzedDataProvider *fdp) {
     return FloatRect(fdp->ConsumeFloatingPoint<float>() /*left*/,
                      fdp->ConsumeFloatingPoint<float>() /*right*/,
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
new file mode 100644
index 0000000..46d52dd
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#include <BufferStateLayer.h>
+#include <Client.h>
+#include <DisplayDevice.h>
+#include <EffectLayer.h>
+#include <LayerRejecter.h>
+#include <LayerRenderArea.h>
+#include <MonitoredProducer.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <gui/IProducerListener.h>
+#include <gui/LayerDebugInfo.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/WindowInfo.h>
+#include <renderengine/mock/FakeExternalTexture.h>
+#include <ui/DisplayStatInfo.h>
+
+#include <FuzzableDataspaces.h>
+#include <surfaceflinger_fuzzers_utils.h>
+
+namespace android::fuzzer {
+using namespace renderengine;
+
+constexpr uint16_t kRandomStringLength = 256;
+
+class LayerFuzzer {
+public:
+    LayerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
+    void init();
+    void invokeBufferStateLayer();
+    void invokeEffectLayer();
+    LayerCreationArgs createLayerCreationArgs(TestableSurfaceFlinger* flinger, sp<Client> client);
+    Rect getFuzzedRect();
+    FrameTimelineInfo getFuzzedFrameTimelineInfo();
+
+private:
+    FuzzedDataProvider mFdp;
+};
+
+Rect LayerFuzzer::getFuzzedRect() {
+    return Rect(mFdp.ConsumeIntegral<int32_t>() /*left*/, mFdp.ConsumeIntegral<int32_t>() /*top*/,
+                mFdp.ConsumeIntegral<int32_t>() /*right*/,
+                mFdp.ConsumeIntegral<int32_t>() /*bottom*/);
+}
+
+FrameTimelineInfo LayerFuzzer::getFuzzedFrameTimelineInfo() {
+    return FrameTimelineInfo{.vsyncId = mFdp.ConsumeIntegral<int64_t>(),
+                             .inputEventId = mFdp.ConsumeIntegral<int32_t>()};
+}
+
+LayerCreationArgs LayerFuzzer::createLayerCreationArgs(TestableSurfaceFlinger* flinger,
+                                                       sp<Client> client) {
+    flinger->setupScheduler(std::make_unique<android::mock::VsyncController>(),
+                            std::make_unique<android::mock::VSyncTracker>(),
+                            std::make_unique<android::mock::EventThread>(),
+                            std::make_unique<android::mock::EventThread>());
+
+    return LayerCreationArgs(flinger->flinger(), client,
+                             mFdp.ConsumeRandomLengthString(kRandomStringLength) /*name*/,
+                             mFdp.ConsumeIntegral<uint32_t>() /*flags*/, {} /*metadata*/);
+}
+
+void LayerFuzzer::invokeEffectLayer() {
+    TestableSurfaceFlinger flinger;
+    sp<Client> client = sp<Client>::make(flinger.flinger());
+    const LayerCreationArgs layerCreationArgs = createLayerCreationArgs(&flinger, client);
+    sp<EffectLayer> effectLayer = sp<EffectLayer>::make(layerCreationArgs);
+
+    effectLayer->setColor({(mFdp.ConsumeFloatingPointInRange<float>(0, 255) /*x*/,
+                            mFdp.ConsumeFloatingPointInRange<float>(0, 255) /*y*/,
+                            mFdp.ConsumeFloatingPointInRange<float>(0, 255) /*z*/)});
+    effectLayer->setDataspace(mFdp.PickValueInArray(kDataspaces));
+    sp<EffectLayer> parent = sp<EffectLayer>::make(layerCreationArgs);
+    effectLayer->setChildrenDrawingParent(parent);
+
+    const FrameTimelineInfo frameInfo = getFuzzedFrameTimelineInfo();
+    const int64_t postTime = mFdp.ConsumeIntegral<int64_t>();
+    effectLayer->setFrameTimelineVsyncForBufferTransaction(frameInfo, postTime);
+    effectLayer->setFrameTimelineVsyncForBufferlessTransaction(frameInfo, postTime);
+    auto surfaceFrame = effectLayer->createSurfaceFrameForTransaction(frameInfo, postTime);
+    auto surfaceFrame1 =
+            effectLayer->createSurfaceFrameForBuffer(frameInfo, postTime,
+                                                     mFdp.ConsumeRandomLengthString(
+                                                             kRandomStringLength) /*bufferName*/);
+    effectLayer->addSurfaceFramePresentedForBuffer(surfaceFrame,
+                                                   mFdp.ConsumeIntegral<int64_t>() /*acquireTime*/,
+                                                   mFdp.ConsumeIntegral<int64_t>() /*currentTime*/);
+    effectLayer->addSurfaceFrameDroppedForBuffer(surfaceFrame1);
+
+    parent.clear();
+    client.clear();
+    effectLayer.clear();
+}
+
+void LayerFuzzer::invokeBufferStateLayer() {
+    TestableSurfaceFlinger flinger;
+    sp<Client> client = sp<Client>::make(flinger.flinger());
+    sp<BufferStateLayer> layer =
+            sp<BufferStateLayer>::make(createLayerCreationArgs(&flinger, client));
+    sp<Fence> fence = sp<Fence>::make();
+    const std::shared_ptr<FenceTime> fenceTime = std::make_shared<FenceTime>(fence);
+
+    const CompositorTiming compositor = {mFdp.ConsumeIntegral<int64_t>(),
+                                         mFdp.ConsumeIntegral<int64_t>(),
+                                         mFdp.ConsumeIntegral<int64_t>()};
+    std::packaged_task<renderengine::RenderEngineResult()> renderResult([&] {
+        return renderengine::RenderEngineResult{mFdp.ConsumeIntegral<int32_t>(),
+                                                base::unique_fd(fence->get())};
+    });
+    layer->onLayerDisplayed(renderResult.get_future());
+    layer->releasePendingBuffer(mFdp.ConsumeIntegral<int64_t>());
+    layer->finalizeFrameEventHistory(fenceTime, compositor);
+    layer->onPostComposition(nullptr, fenceTime, fenceTime, compositor);
+    layer->isBufferDue(mFdp.ConsumeIntegral<int64_t>());
+
+    layer->setTransform(mFdp.ConsumeIntegral<uint32_t>());
+    layer->setTransformToDisplayInverse(mFdp.ConsumeBool());
+    layer->setCrop(getFuzzedRect());
+
+    layer->setHdrMetadata(getFuzzedHdrMetadata(&mFdp));
+    layer->setDataspace(mFdp.PickValueInArray(kDataspaces));
+    if (mFdp.ConsumeBool()) {
+        layer->setSurfaceDamageRegion(Region());
+        layer->setTransparentRegionHint(Region());
+    } else {
+        layer->setSurfaceDamageRegion(Region(getFuzzedRect()));
+        layer->setTransparentRegionHint(Region(getFuzzedRect()));
+    }
+    layer->setApi(mFdp.ConsumeIntegral<int32_t>());
+
+    native_handle_t* testHandle = native_handle_create(0, 1);
+    const bool ownsHandle = mFdp.ConsumeBool();
+    sp<NativeHandle> nativeHandle = sp<NativeHandle>::make(testHandle, ownsHandle);
+    layer->setSidebandStream(nativeHandle);
+    layer->addFrameEvent(fence, mFdp.ConsumeIntegral<int64_t>() /*postedTime*/,
+                         mFdp.ConsumeIntegral<int64_t>() /*requestedTime*/);
+    layer->computeSourceBounds(getFuzzedFloatRect(&mFdp));
+
+    layer->fenceHasSignaled();
+    layer->framePresentTimeIsCurrent(mFdp.ConsumeIntegral<int64_t>());
+    layer->onPreComposition(mFdp.ConsumeIntegral<int64_t>());
+    const std::vector<sp<CallbackHandle>> callbacks;
+    layer->setTransactionCompletedListeners(callbacks);
+
+    std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
+            renderengine::mock::FakeExternalTexture>(mFdp.ConsumeIntegral<uint32_t>(),
+                                                     mFdp.ConsumeIntegral<uint32_t>(),
+                                                     mFdp.ConsumeIntegral<uint64_t>(),
+                                                     static_cast<android::PixelFormat>(
+                                                             mFdp.PickValueInArray(kPixelFormats)),
+                                                     mFdp.ConsumeIntegral<uint64_t>());
+    layer->setBuffer(texture, {} /*bufferData*/, mFdp.ConsumeIntegral<nsecs_t>() /*postTime*/,
+                     mFdp.ConsumeIntegral<nsecs_t>() /*desiredTime*/,
+                     mFdp.ConsumeBool() /*isAutoTimestamp*/,
+                     {mFdp.ConsumeIntegral<nsecs_t>()} /*dequeue*/, {} /*info*/);
+
+    LayerRenderArea layerArea(*(flinger.flinger()), layer, getFuzzedRect(),
+                              {mFdp.ConsumeIntegral<int32_t>(),
+                               mFdp.ConsumeIntegral<int32_t>()} /*reqSize*/,
+                              mFdp.PickValueInArray(kDataspaces), mFdp.ConsumeBool(),
+                              getFuzzedRect(), mFdp.ConsumeBool());
+    layerArea.render([]() {} /*drawLayers*/);
+
+    if (!ownsHandle) {
+        native_handle_close(testHandle);
+        native_handle_delete(testHandle);
+    }
+    nativeHandle.clear();
+    fence.clear();
+    client.clear();
+    layer.clear();
+}
+
+void LayerFuzzer::init() {
+    invokeBufferStateLayer();
+    invokeEffectLayer();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    LayerFuzzer layerFuzzer(data, size);
+    layerFuzzer.init();
+    return 0;
+}
+
+} // namespace android::fuzzer
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index efcc386..ceddf27 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -26,6 +26,7 @@
     defaults: ["surfaceflinger_defaults"],
     test_suites: ["device-tests"],
     srcs: [
+        "BootDisplayMode_test.cpp",
         "BufferGenerator.cpp",
         "CommonTypes_test.cpp",
         "Credentials_test.cpp",
diff --git a/services/surfaceflinger/tests/BootDisplayMode_test.cpp b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
new file mode 100644
index 0000000..abdb16d
--- /dev/null
+++ b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <thread>
+
+#include <gtest/gtest.h>
+
+#include <gui/SurfaceComposerClient.h>
+#include <private/gui/ComposerService.h>
+#include <chrono>
+
+namespace android {
+
+TEST(BootDisplayModeTest, setBootDisplayMode) {
+    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    auto displayToken = SurfaceComposerClient::getInternalDisplayToken();
+    bool bootModeSupport = false;
+    ASSERT_NO_FATAL_FAILURE(sf->getBootDisplayModeSupport(&bootModeSupport));
+    if (bootModeSupport) {
+        ASSERT_EQ(NO_ERROR, sf->setBootDisplayMode(displayToken, 0));
+    }
+}
+
+TEST(BootDisplayModeTest, clearBootDisplayMode) {
+    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    auto displayToken = SurfaceComposerClient::getInternalDisplayToken();
+    bool bootModeSupport = false;
+    ASSERT_NO_FATAL_FAILURE(sf->getBootDisplayModeSupport(&bootModeSupport));
+    if (bootModeSupport) {
+        ASSERT_EQ(NO_ERROR, sf->clearBootDisplayMode(displayToken));
+    }
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/FpsTest.cpp b/services/surfaceflinger/tests/unittests/FpsTest.cpp
index 88b74d2..2193c9d 100644
--- a/services/surfaceflinger/tests/unittests/FpsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FpsTest.cpp
@@ -68,4 +68,14 @@
     EXPECT_EQ(31, (30.5_Hz).getIntValue());
 }
 
+TEST(FpsTest, range) {
+    const auto fps = Fps::fromPeriodNsecs(16'666'665);
+
+    EXPECT_TRUE((FpsRange{60.000004_Hz, 60.000004_Hz}.includes(fps)));
+    EXPECT_TRUE((FpsRange{59_Hz, 60.1_Hz}.includes(fps)));
+    EXPECT_FALSE((FpsRange{75_Hz, 90_Hz}.includes(fps)));
+    EXPECT_FALSE((FpsRange{60.0011_Hz, 90_Hz}.includes(fps)));
+    EXPECT_FALSE((FpsRange{50_Hz, 59.998_Hz}.includes(fps)));
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 397c619..834a560 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -1279,7 +1279,7 @@
     validateTraceEvent(actualSurfaceFrameEnd2, protoPresentedSurfaceFrameActualEnd);
 }
 
-TEST_F(FrameTimelineTest, traceSurfaceFrame_predictionExpiredDoesNotTraceExpectedTimeline) {
+TEST_F(FrameTimelineTest, traceSurfaceFrame_predictionExpiredIsAppMissedDeadline) {
     auto tracingSession = getTracingSessionForTest();
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
 
@@ -1312,7 +1312,7 @@
             createProtoActualSurfaceFrameStart(traceCookie + 1, surfaceFrameToken,
                                                displayFrameToken, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_UNSPECIFIED, false,
-                                               false, FrameTimelineEvent::JANK_UNKNOWN,
+                                               false, FrameTimelineEvent::JANK_APP_DEADLINE_MISSED,
                                                FrameTimelineEvent::PREDICTION_EXPIRED, true);
     auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 4efcc05..9143d61 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -834,17 +834,6 @@
     EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 }
 
-TEST_F(RefreshRateConfigsTest, testInPolicy) {
-    const auto refreshRate =
-            asRefreshRate(createDisplayMode(kModeId60, Fps::fromPeriodNsecs(16'666'665)));
-
-    EXPECT_TRUE(refreshRate.inPolicy(60.000004_Hz, 60.000004_Hz));
-    EXPECT_TRUE(refreshRate.inPolicy(59_Hz, 60.1_Hz));
-    EXPECT_FALSE(refreshRate.inPolicy(75_Hz, 90_Hz));
-    EXPECT_FALSE(refreshRate.inPolicy(60.0011_Hz, 90_Hz));
-    EXPECT_FALSE(refreshRate.inPolicy(50_Hz, 59.998_Hz));
-}
-
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_75HzContent) {
     TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);