Merge "Use vibrator_service cmd for dumpstate" into sc-dev
diff --git a/Android.bp b/Android.bp
index 298c01a..dec6716 100644
--- a/Android.bp
+++ b/Android.bp
@@ -85,4 +85,10 @@
 cc_library_headers{
     name: "libandroid_headers_private",
     export_include_dirs: ["include/private"],
-}
\ No newline at end of file
+}
+
+filegroup {
+    name: "deviceproductinfoconstants_aidl",
+    srcs: ["aidl/android/hardware/display/IDeviceProductInfoConstants.aidl"],
+    path: "aidl",
+}
diff --git a/aidl/android/hardware/display/IDeviceProductInfoConstants.aidl b/aidl/android/hardware/display/IDeviceProductInfoConstants.aidl
new file mode 100644
index 0000000..7cc272a
--- /dev/null
+++ b/aidl/android/hardware/display/IDeviceProductInfoConstants.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+/** @hide */
+interface IDeviceProductInfoConstants {
+    /** The device connection to the display sink is unknown. */
+    const int CONNECTION_TO_SINK_UNKNOWN = 0;
+
+    /** The device is built-in in the display sink. */
+    const int CONNECTION_TO_SINK_BUILT_IN = 1;
+
+    /** The device is directly connected to the display sink. */
+    const int CONNECTION_TO_SINK_DIRECT = 2;
+
+    /** The device is transitively connected to the display sink. */
+    const int CONNECTION_TO_SINK_TRANSITIVE = 3;
+}
\ No newline at end of file
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index c7c3a34..006e532 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -37,12 +37,18 @@
     chmod 0666 /sys/kernel/tracing/events/sched/sched_process_exit/enable
     chmod 0666 /sys/kernel/debug/tracing/events/sched/sched_waking/enable
     chmod 0666 /sys/kernel/tracing/events/sched/sched_waking/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/sched/sched_wakeup_new/enable
+    chmod 0666 /sys/kernel/tracing/events/sched/sched_wakeup_new/enable
     chmod 0666 /sys/kernel/debug/tracing/events/cgroup/enable
     chmod 0666 /sys/kernel/tracing/events/cgroup/enable
     chmod 0666 /sys/kernel/debug/tracing/events/power/cpu_frequency/enable
     chmod 0666 /sys/kernel/tracing/events/power/cpu_frequency/enable
     chmod 0666 /sys/kernel/debug/tracing/events/power/cpu_idle/enable
     chmod 0666 /sys/kernel/tracing/events/power/cpu_idle/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/power/clock_enable/enable
+    chmod 0666 /sys/kernel/tracing/events/power/clock_enable/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/power/clock_disable/enable
+    chmod 0666 /sys/kernel/tracing/events/power/clock_disable/enable
     chmod 0666 /sys/kernel/debug/tracing/events/power/clock_set_rate/enable
     chmod 0666 /sys/kernel/tracing/events/power/clock_set_rate/enable
     chmod 0666 /sys/kernel/debug/tracing/events/power/cpu_frequency_limits/enable
@@ -79,6 +85,8 @@
     chmod 0666 /sys/kernel/tracing/events/binder/binder_locked/enable
     chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_unlock/enable
     chmod 0666 /sys/kernel/tracing/events/binder/binder_unlock/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_set_priority/enable
+    chmod 0666 /sys/kernel/tracing/events/binder/binder_set_priority/enable
     chmod 0666 /sys/kernel/debug/tracing/events/i2c/enable
     chmod 0666 /sys/kernel/tracing/events/i2c/enable
     chmod 0666 /sys/kernel/debug/tracing/events/i2c/i2c_read/enable
@@ -125,6 +133,8 @@
     chmod 0666 /sys/kernel/tracing/events/lowmemorykiller/lowmemory_kill/enable
     chmod 0666 /sys/kernel/debug/tracing/events/oom/oom_score_adj_update/enable
     chmod 0666 /sys/kernel/tracing/events/oom/oom_score_adj_update/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/oom/mark_victim/enable
+    chmod 0666 /sys/kernel/tracing/events/oom/mark_victim/enable
     chmod 0666 /sys/kernel/debug/tracing/events/task/task_rename/enable
     chmod 0666 /sys/kernel/tracing/events/task/task_rename/enable
     chmod 0666 /sys/kernel/debug/tracing/events/task/task_newtask/enable
@@ -159,6 +169,12 @@
     chmod 0666 /sys/kernel/tracing/events/ipi/ipi_exit/enable
     chmod 0666 /sys/kernel/debug/tracing/events/ipi/ipi_raise/enable
     chmod 0666 /sys/kernel/tracing/events/ipi/ipi_raise/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/clk/clk_enable/enable
+    chmod 0666 /sys/kernel/tracing/events/clk/clk_disable/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/clk/clk_disable/enable
+    chmod 0666 /sys/kernel/tracing/events/clk/clk_enable/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/clk/clk_set_rate/enable
+    chmod 0666 /sys/kernel/tracing/events/clk/clk_set_rate/enable
 
     # disk
     chmod 0666 /sys/kernel/tracing/events/f2fs/f2fs_get_data_block/enable
diff --git a/cmds/atrace/atrace_userdebug.rc b/cmds/atrace/atrace_userdebug.rc
index 6c86c21..9186514 100644
--- a/cmds/atrace/atrace_userdebug.rc
+++ b/cmds/atrace/atrace_userdebug.rc
@@ -18,8 +18,3 @@
     chmod 0666 /sys/kernel/tracing/events/filemap/enable
     chmod 0666 /sys/kernel/debug/tracing/events/filemap/enable
 
-    # irq
-    chmod 0666 /sys/kernel/tracing/events/irq/enable
-    chmod 0666 /sys/kernel/debug/tracing/events/irq/enable
-    chmod 0666 /sys/kernel/tracing/events/ipi/enable
-    chmod 0666 /sys/kernel/debug/tracing/events/ipi/enable
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 23986dd..31930ff 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -1208,10 +1208,6 @@
 
 static void DumpPacketStats() {
     DumpFile("NETWORK DEV INFO", "/proc/net/dev");
-    DumpFile("QTAGUID NETWORK INTERFACES INFO", "/proc/net/xt_qtaguid/iface_stat_all");
-    DumpFile("QTAGUID NETWORK INTERFACES INFO (xt)", "/proc/net/xt_qtaguid/iface_stat_fmt");
-    DumpFile("QTAGUID CTRL INFO", "/proc/net/xt_qtaguid/ctrl");
-    DumpFile("QTAGUID STATS INFO", "/proc/net/xt_qtaguid/stats");
 }
 
 static void DumpIpAddrAndRules() {
@@ -1657,8 +1653,6 @@
     for_each_tid(show_wchan, "BLOCKED PROCESS WAIT-CHANNELS");
     for_each_pid(show_showtime, "PROCESS TIMES (pid cmd user system iowait+percentage)");
 
-    /* Dump Bluetooth HCI logs */
-    ds.AddDir("/data/misc/bluetooth/logs", true);
     /* Dump Nfc NCI logs */
     ds.AddDir("/data/misc/nfc/logs", true);
 
@@ -1744,6 +1738,9 @@
 
     RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunDumpsysNormal);
 
+    /* Dump Bluetooth HCI logs after getting bluetooth_manager dumpsys */
+    ds.AddDir("/data/misc/bluetooth/logs", true);
+
     if (ds.dump_pool_) {
         WAIT_TASK_WITH_CONSENT_CHECK(DUMP_CHECKINS_TASK, ds.dump_pool_);
     } else {
@@ -2060,7 +2057,7 @@
 }
 
 Dumpstate::RunStatus Dumpstate::DumpTraces(const char** path) {
-    const std::string temp_file_pattern = "/data/anr/dumptrace_XXXXXX";
+    const std::string temp_file_pattern = ds.bugreport_internal_dir_ + "/dumptrace_XXXXXX";
     const size_t buf_size = temp_file_pattern.length() + 1;
     std::unique_ptr<char[]> file_name_buf(new char[buf_size]);
     memcpy(file_name_buf.get(), temp_file_pattern.c_str(), buf_size);
@@ -3069,6 +3066,9 @@
     android::os::UnlinkAndLogOnError(tmp_path_);
     android::os::UnlinkAndLogOnError(screenshot_path_);
     android::os::UnlinkAndLogOnError(path_);
+    if (dump_traces_path != nullptr) {
+        android::os::UnlinkAndLogOnError(dump_traces_path);
+    }
 }
 
 void Dumpstate::EnableParallelRunIfNeeded() {
diff --git a/cmds/idlcli/Android.bp b/cmds/idlcli/Android.bp
index c537fed..1ebdc47 100644
--- a/cmds/idlcli/Android.bp
+++ b/cmds/idlcli/Android.bp
@@ -53,6 +53,8 @@
         "vibrator/CommandGetCompositionDelayMax.cpp",
         "vibrator/CommandGetCompositionSizeMax.cpp",
         "vibrator/CommandGetPrimitiveDuration.cpp",
+        "vibrator/CommandGetQFactor.cpp",
+        "vibrator/CommandGetResonantFrequency.cpp",
         "vibrator/CommandGetSupportedAlwaysOnEffects.cpp",
         "vibrator/CommandGetSupportedEffects.cpp",
         "vibrator/CommandGetSupportedPrimitives.cpp",
diff --git a/cmds/idlcli/CommandVibrator.cpp b/cmds/idlcli/CommandVibrator.cpp
index a7a70c3..81bdbe2 100644
--- a/cmds/idlcli/CommandVibrator.cpp
+++ b/cmds/idlcli/CommandVibrator.cpp
@@ -22,7 +22,7 @@
 class IdlCli;
 
 class CommandVibrator : public CommandWithSubcommands<CommandVibrator> {
-    std::string getDescription() const override { return "Invoke Vibrator HIDL APIs."; }
+    std::string getDescription() const override { return "Invoke Vibrator IDL APIs."; }
 
     std::string getUsageSummary() const override { return "<api> [arguments]"; }
 
diff --git a/cmds/idlcli/IdlCli.h b/cmds/idlcli/IdlCli.h
index dd84304..24a40d9 100644
--- a/cmds/idlcli/IdlCli.h
+++ b/cmds/idlcli/IdlCli.h
@@ -25,14 +25,47 @@
 class IdlCli : public CommandWithSubcommands<IdlCli> {
     std::string getDescription() const override { return "Invoke IDL APIs."; }
 
-    std::string getUsageSummary() const override { return "<idl> [arguments]"; }
+    std::string getUsageSummary() const override { return "<idl> [options] [arguments]"; }
 
     UsageDetails getUsageDetails() const override {
         UsageDetails details{
+                {"-n <name>", {"Get named service, rather than default."}},
                 {"<idl>", CommandRegistry<IdlCli>::List()},
         };
         return details;
     }
+
+    Status doArgs(Args &args) override {
+        while (args.get<std::string>().value_or("").find("-") == 0) {
+            auto opt = *args.pop<std::string>();
+            if (opt == "--") {
+                break;
+            } else if (opt == "-n") {
+                if (auto name = args.pop<decltype(mName)>()) {
+                    mName = *name;
+                } else {
+                    std::cerr << "Missing Value for Name!" << std::endl;
+                    return USAGE;
+                }
+            } else {
+                std::cerr << "Invalid Option '" << opt << "'!" << std::endl;
+                return USAGE;
+            }
+        }
+        return CommandWithSubcommands::doArgs(args);
+    }
+
+    IdlCli() {}
+
+    std::string mName;
+
+public:
+    static IdlCli &Get() {
+        static IdlCli instance;
+        return instance;
+    }
+
+    auto getName() { return mName; }
 };
 
 } // namespace idlcli
diff --git a/cmds/idlcli/main.cpp b/cmds/idlcli/main.cpp
index 9ed9d82..308f294 100644
--- a/cmds/idlcli/main.cpp
+++ b/cmds/idlcli/main.cpp
@@ -19,5 +19,5 @@
 
 int main(const int argc, const char* const argv[]) {
     using namespace ::android::idlcli;
-    return IdlCli{}.main(Args{argc, argv});
+    return IdlCli::Get().main(Args{argc, argv});
 }
diff --git a/cmds/idlcli/utils.h b/cmds/idlcli/utils.h
index b874455..262f2e5 100644
--- a/cmds/idlcli/utils.h
+++ b/cmds/idlcli/utils.h
@@ -249,7 +249,7 @@
 
 template <typename T>
 class CommandWithSubcommands : public Command {
-private:
+protected:
     Status doArgs(Args &args) override {
         mCommand = CommandRegistry<T>::Create(*args.get());
         if (!mCommand) {
diff --git a/cmds/idlcli/vibrator.h b/cmds/idlcli/vibrator.h
index 6c30a9e..dfbb886 100644
--- a/cmds/idlcli/vibrator.h
+++ b/cmds/idlcli/vibrator.h
@@ -13,24 +13,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef FRAMEWORK_NATIVE_CMDS_IDLCLI_VIBRATOR_H_
-#define FRAMEWORK_NATIVE_CMDS_IDLCLI_VIBRATOR_H_
+#pragma once
 
 #include <future>
 
 #include <aidl/android/hardware/vibrator/BnVibratorCallback.h>
 #include <aidl/android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibratorManager.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
 #include <android/hardware/vibrator/1.3/IVibrator.h>
 
+#include "IdlCli.h"
 #include "utils.h"
 
-#include "log/log.h"
-
 namespace android {
 
 using hardware::Return;
+using idlcli::IdlCli;
 
 static constexpr int NUM_TRIES = 2;
 
@@ -47,20 +47,34 @@
 }
 
 template <typename I>
-inline auto getService() {
-    return I::getService();
+inline auto getService(std::string name) {
+    const auto instance = std::string() + I::descriptor + "/" + name;
+    auto vibBinder = ndk::SpAIBinder(AServiceManager_getService(instance.c_str()));
+    return I::fromBinder(vibBinder);
 }
 
 template <>
-inline auto getService<aidl::android::hardware::vibrator::IVibrator>() {
-    const auto instance =
-            std::string() + aidl::android::hardware::vibrator::IVibrator::descriptor + "/default";
-    auto vibBinder = ndk::SpAIBinder(AServiceManager_getService(instance.c_str()));
-    return aidl::android::hardware::vibrator::IVibrator::fromBinder(vibBinder);
+inline auto getService<android::hardware::vibrator::V1_0::IVibrator>(std::string name) {
+    return android::hardware::vibrator::V1_0::IVibrator::getService(name);
+}
+
+template <>
+inline auto getService<android::hardware::vibrator::V1_1::IVibrator>(std::string name) {
+    return android::hardware::vibrator::V1_1::IVibrator::getService(name);
+}
+
+template <>
+inline auto getService<android::hardware::vibrator::V1_2::IVibrator>(std::string name) {
+    return android::hardware::vibrator::V1_2::IVibrator::getService(name);
+}
+
+template <>
+inline auto getService<android::hardware::vibrator::V1_3::IVibrator>(std::string name) {
+    return android::hardware::vibrator::V1_3::IVibrator::getService(name);
 }
 
 template <typename I>
-using shared_ptr = std::result_of_t<decltype(getService<I>)&()>;
+using shared_ptr = std::result_of_t<decltype(getService<I>)&(std::string)>;
 
 template <typename I>
 class HalWrapper {
@@ -68,7 +82,8 @@
     static std::unique_ptr<HalWrapper> Create() {
         // Assume that if getService returns a nullptr, HAL is not available on the
         // device.
-        auto hal = getService<I>();
+        const auto name = IdlCli::Get().getName();
+        auto hal = getService<I>(name.empty() ? "default" : name);
         return hal ? std::unique_ptr<HalWrapper>(new HalWrapper(std::move(hal))) : nullptr;
     }
 
@@ -121,5 +136,3 @@
 } // namespace idlcli
 
 } // namespace android
-
-#endif // FRAMEWORK_NATIVE_CMDS_IDLCLI_VIBRATOR_H_
diff --git a/cmds/idlcli/vibrator/CommandGetQFactor.cpp b/cmds/idlcli/vibrator/CommandGetQFactor.cpp
new file mode 100644
index 0000000..a2681e9
--- /dev/null
+++ b/cmds/idlcli/vibrator/CommandGetQFactor.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils.h"
+#include "vibrator.h"
+
+namespace android {
+namespace idlcli {
+
+class CommandVibrator;
+
+namespace vibrator {
+
+class CommandGetQFactor : public Command {
+    std::string getDescription() const override { return "Retrieves vibrator Q factor."; }
+
+    std::string getUsageSummary() const override { return ""; }
+
+    UsageDetails getUsageDetails() const override {
+        UsageDetails details{};
+        return details;
+    }
+
+    Status doArgs(Args &args) override {
+        if (!args.empty()) {
+            std::cerr << "Unexpected Arguments!" << std::endl;
+            return USAGE;
+        }
+        return OK;
+    }
+
+    Status doMain(Args && /*args*/) override {
+        std::string statusStr;
+        float qFactor;
+        Status ret;
+
+        if (auto hal = getHal<aidl::IVibrator>()) {
+            auto status = hal->call(&aidl::IVibrator::getQFactor, &qFactor);
+            statusStr = status.getDescription();
+            ret = status.isOk() ? OK : ERROR;
+        } else {
+            return UNAVAILABLE;
+        }
+
+        std::cout << "Status: " << statusStr << std::endl;
+        std::cout << "Q Factor: " << qFactor << std::endl;
+
+        return ret;
+    }
+};
+
+static const auto Command =
+    CommandRegistry<CommandVibrator>::Register<CommandGetQFactor>("getQFactor");
+
+}  // namespace vibrator
+}  // namespace idlcli
+}  // namespace android
diff --git a/cmds/idlcli/vibrator/CommandGetResonantFrequency.cpp b/cmds/idlcli/vibrator/CommandGetResonantFrequency.cpp
new file mode 100644
index 0000000..81a6391
--- /dev/null
+++ b/cmds/idlcli/vibrator/CommandGetResonantFrequency.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils.h"
+#include "vibrator.h"
+
+namespace android {
+namespace idlcli {
+
+class CommandVibrator;
+
+namespace vibrator {
+
+class CommandGetResonantFrequency : public Command {
+    std::string getDescription() const override {
+        return "Retrieves vibrator resonant frequency in Hz.";
+    }
+
+    std::string getUsageSummary() const override { return ""; }
+
+    UsageDetails getUsageDetails() const override {
+        UsageDetails details{};
+        return details;
+    }
+
+    Status doArgs(Args &args) override {
+        if (!args.empty()) {
+            std::cerr << "Unexpected Arguments!" << std::endl;
+            return USAGE;
+        }
+        return OK;
+    }
+
+    Status doMain(Args && /*args*/) override {
+        std::string statusStr;
+        float resonantFrequencyHz;
+        Status ret;
+
+        if (auto hal = getHal<aidl::IVibrator>()) {
+            auto status = hal->call(&aidl::IVibrator::getResonantFrequency, &resonantFrequencyHz);
+            statusStr = status.getDescription();
+            ret = status.isOk() ? OK : ERROR;
+        } else {
+            return UNAVAILABLE;
+        }
+
+        std::cout << "Status: " << statusStr << std::endl;
+        std::cout << "Resonant Frequency: " << resonantFrequencyHz << " Hz" << std::endl;
+
+        return ret;
+    }
+};
+
+static const auto Command = CommandRegistry<CommandVibrator>::Register<CommandGetResonantFrequency>(
+    "getResonantFrequency");
+
+}  // namespace vibrator
+}  // namespace idlcli
+}  // namespace android
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index 4dfd1d0..0cf50a3 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -2112,8 +2112,9 @@
     {
         struct stat s;
         if (stat(b_path.c_str(), &s) != 0) {
-            // Silently ignore for now. The service calling this isn't smart enough to understand
-            // lack of artifacts at the moment.
+            // Ignore for now. The service calling this isn't smart enough to
+            // understand lack of artifacts at the moment.
+            LOG(VERBOSE) << "A/B artifact " << b_path << " does not exist!";
             return false;
         }
         if (!S_ISREG(s.st_mode)) {
diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp
index 443821c..cefcf64 100644
--- a/cmds/installd/otapreopt.cpp
+++ b/cmds/installd/otapreopt.cpp
@@ -175,8 +175,10 @@
 private:
 
     bool ReadSystemProperties() {
+        // TODO This file does not have a stable format. It should be read by
+        // code shared by init and otapreopt. See b/181182967#comment80
         static constexpr const char* kPropertyFiles[] = {
-                "/default.prop", "/system/build.prop"
+                "/system/build.prop"
         };
 
         for (size_t i = 0; i < arraysize(kPropertyFiles); ++i) {
@@ -193,28 +195,38 @@
         //   export NAME VALUE
         // For simplicity, don't respect string quotation. The values we are interested in can be
         // encoded without them.
+        // init.environ.rc and etc/classpath have the same format for
+        // environment variable exports and can be matched by the same regex.
+        // TODO Just like with the system-properties above we really should have
+        // common code between init and otapreopt to deal with reading these
+        // things. See b/181182967
+        static constexpr const char* kEnvironmentVariableSources[] = {
+                "/init.environ.rc", "/etc/classpath"
+        };
+
         std::regex export_regex("\\s*export\\s+(\\S+)\\s+(\\S+)");
-        bool parse_result = ParseFile("/init.environ.rc", [&](const std::string& line) {
-            std::smatch export_match;
-            if (!std::regex_match(line, export_match, export_regex)) {
+        for (const char* env_vars_file : kEnvironmentVariableSources) {
+            bool parse_result = ParseFile(env_vars_file, [&](const std::string& line) {
+                std::smatch export_match;
+                if (!std::regex_match(line, export_match, export_regex)) {
+                    return true;
+                }
+
+                if (export_match.size() != 3) {
+                    return true;
+                }
+
+                std::string name = export_match[1].str();
+                std::string value = export_match[2].str();
+
+                system_properties_.SetProperty(name, value);
+
                 return true;
+            });
+            if (!parse_result) {
+                return false;
             }
-
-            if (export_match.size() != 3) {
-                return true;
-            }
-
-            std::string name = export_match[1].str();
-            std::string value = export_match[2].str();
-
-            system_properties_.SetProperty(name, value);
-
-            return true;
-        });
-        if (!parse_result) {
-            return false;
         }
-
         if (system_properties_.GetProperty(kAndroidDataPathPropertyName) == nullptr) {
             return false;
         }
@@ -473,24 +485,29 @@
     // Run dexopt with the parameters of parameters_.
     // TODO(calin): embed the profile name in the parameters.
     int Dexopt() {
-        std::string dummy;
-        return dexopt(parameters_.apk_path,
-                      parameters_.uid,
-                      parameters_.pkgName,
-                      parameters_.instruction_set,
-                      parameters_.dexopt_needed,
-                      parameters_.oat_dir,
-                      parameters_.dexopt_flags,
-                      parameters_.compiler_filter,
-                      parameters_.volume_uuid,
-                      parameters_.shared_libraries,
-                      parameters_.se_info,
-                      parameters_.downgrade,
-                      parameters_.target_sdk_version,
-                      parameters_.profile_name,
-                      parameters_.dex_metadata_path,
-                      parameters_.compilation_reason,
-                      &dummy);
+        std::string error;
+        int res = dexopt(parameters_.apk_path,
+                         parameters_.uid,
+                         parameters_.pkgName,
+                         parameters_.instruction_set,
+                         parameters_.dexopt_needed,
+                         parameters_.oat_dir,
+                         parameters_.dexopt_flags,
+                         parameters_.compiler_filter,
+                         parameters_.volume_uuid,
+                         parameters_.shared_libraries,
+                         parameters_.se_info,
+                         parameters_.downgrade,
+                         parameters_.target_sdk_version,
+                         parameters_.profile_name,
+                         parameters_.dex_metadata_path,
+                         parameters_.compilation_reason,
+                         &error);
+        if (res != 0) {
+            LOG(ERROR) << "During preopt of " << parameters_.apk_path << " got result " << res
+                       << " error: " << error;
+        }
+        return res;
     }
 
     int RunPreopt() {
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index 72c03bf..fb07840 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -28,6 +28,7 @@
 #include <libdm/dm.h>
 #include <selinux/android.h>
 
+#include <apex_file_repository.h>
 #include <apexd.h>
 
 #include "installd_constants.h"
@@ -64,11 +65,14 @@
     // system/apex/apexd/apexd.cpp.
     //
     // Only scan the APEX directory under /system, /system_ext and /vendor (within the chroot dir).
-    std::vector<const char*> apex_dirs{apex::kApexPackageSystemDir, apex::kApexPackageSystemExtDir,
+    std::vector<std::string> apex_dirs{apex::kApexPackageSystemDir, apex::kApexPackageSystemExtDir,
                                        apex::kApexPackageVendorDir};
+    // Initialize ApexFileRepository used internally in ScanPackagesDirAndActivate.
+    // This is a quick fix to fix apex activation in otapreopt_chroot.
+    apex::ApexFileRepository::GetInstance().AddPreInstalledApex(apex_dirs);
     for (const auto& dir : apex_dirs) {
         // Cast call to void to suppress warn_unused_result.
-        static_cast<void>(apex::ScanPackagesDirAndActivate(dir));
+        static_cast<void>(apex::ScanPackagesDirAndActivate(dir.c_str()));
     }
     return apex::GetActivePackages();
 }
diff --git a/cmds/surfacereplayer/proto/Android.bp b/cmds/surfacereplayer/proto/Android.bp
index dae976e..23b54ee 100644
--- a/cmds/surfacereplayer/proto/Android.bp
+++ b/cmds/surfacereplayer/proto/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/cmds/surfacereplayer/replayer/Replayer.cpp b/cmds/surfacereplayer/replayer/Replayer.cpp
index 58d6582..3c53c02 100644
--- a/cmds/surfacereplayer/replayer/Replayer.cpp
+++ b/cmds/surfacereplayer/replayer/Replayer.cpp
@@ -28,7 +28,6 @@
 #include <gui/Surface.h>
 #include <private/gui/ComposerService.h>
 
-#include <ui/DisplayInfo.h>
 #include <utils/Log.h>
 #include <utils/String8.h>
 #include <utils/Trace.h>
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 83b6aa0..9d88ca6 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -1,7 +1,16 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
 prebuilt_etc {
     name: "android.hardware.biometrics.face.xml",
     product_specific: true,
     sub_dir: "permissions",
     src: "android.hardware.biometrics.face.xml",
     filename_from_src: true,
-}
\ No newline at end of file
+}
diff --git a/include/android/choreographer.h b/include/android/choreographer.h
index 8039bb0..cc5420e 100644
--- a/include/android/choreographer.h
+++ b/include/android/choreographer.h
@@ -127,6 +127,10 @@
  * to listen directly to DisplayManager.DisplayListener#onDisplayChanged events
  * instead.
  *
+ * As of API level 31, this api is guaranteed to have a consistent view with DisplayManager;
+ * Display#getRefreshRate is guaranteed to not return a stale refresh rate when invoked from this
+ * callback.
+ *
  * Available since API level 30.
  */
 void AChoreographer_registerRefreshRateCallback(AChoreographer* choreographer,
diff --git a/include/android/imagedecoder.h b/include/android/imagedecoder.h
index cac67d4..ee1eee2 100644
--- a/include/android/imagedecoder.h
+++ b/include/android/imagedecoder.h
@@ -367,6 +367,10 @@
  * the first frame (e.g. before calling {@link AImageDecoder_advanceFrame} or
  * after calling {@link AImageDecoder_rewind}).
  *
+ * It is strongly recommended to use setTargetSize only for downscaling, as it
+ * is often more efficient to scale-up when rendering than up-front due to
+ * reduced overall memory.
+ *
  * Available since API level 30.
  *
  * @param width Width of the output (prior to cropping).
diff --git a/include/android/input.h b/include/android/input.h
index b70d424..7973487 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -859,7 +859,7 @@
     /** HDMI */
     AINPUT_SOURCE_HDMI = 0x02000000 | AINPUT_SOURCE_CLASS_BUTTON,
     /** sensor */
-    AINPUT_SOURCE_SENSOR = 0x04000000 | AINPUT_SOURCE_UNKNOWN,
+    AINPUT_SOURCE_SENSOR = 0x04000000 | AINPUT_SOURCE_CLASS_NONE,
     /** rotary encoder */
     AINPUT_SOURCE_ROTARY_ENCODER = 0x00400000 | AINPUT_SOURCE_CLASS_NONE,
 
diff --git a/include/input/Flags.h b/include/input/Flags.h
index 072dd18..b12a9ed 100644
--- a/include/input/Flags.h
+++ b/include/input/Flags.h
@@ -84,7 +84,7 @@
 template <typename F>
 constexpr std::optional<std::string_view> flag_name(F flag) {
     using U = std::underlying_type_t<F>;
-    auto idx = __builtin_ctzl(static_cast<U>(flag));
+    auto idx = static_cast<size_t>(__builtin_ctzl(static_cast<U>(flag)));
     return details::flag_names<F>[idx];
 }
 
@@ -280,4 +280,4 @@
 } // namespace flag_operators
 } // namespace android
 
-#endif // __UI_INPUT_FLAGS_H
\ No newline at end of file
+#endif // __UI_INPUT_FLAGS_H
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 2bd7bd2..2deb99d 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -100,6 +100,13 @@
     SPECIAL_TRIGGER = 3,
 };
 
+enum class InputDeviceLightType : int32_t {
+    SINGLE = 0,
+    PLAYER_ID = 1,
+    RGB = 2,
+    MULTI_COLOR = 3,
+};
+
 struct InputDeviceSensorInfo {
     explicit InputDeviceSensorInfo(std::string name, std::string vendor, int32_t version,
                                    InputDeviceSensorType type, InputDeviceSensorAccuracy accuracy,
@@ -156,6 +163,20 @@
     int32_t id;
 };
 
+struct InputDeviceLightInfo {
+    explicit InputDeviceLightInfo(std::string name, int32_t id, InputDeviceLightType type,
+                                  int32_t ordinal)
+          : name(name), id(id), type(type), ordinal(ordinal) {}
+    // Name string of the light.
+    std::string name;
+    // Light id
+    int32_t id;
+    // Type of the light.
+    InputDeviceLightType type;
+    // Ordinal of the light
+    int32_t ordinal;
+};
+
 /*
  * Describes the characteristics and capabilities of an input device.
  */
@@ -198,6 +219,7 @@
             float min, float max, float flat, float fuzz, float resolution);
     void addMotionRange(const MotionRange& range);
     void addSensorInfo(const InputDeviceSensorInfo& info);
+    void addLightInfo(const InputDeviceLightInfo& info);
 
     inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; }
     inline int32_t getKeyboardType() const { return mKeyboardType; }
@@ -230,6 +252,10 @@
 
     const std::vector<InputDeviceSensorType> getSensorTypes();
 
+    const std::vector<int32_t> getLightIds();
+
+    const InputDeviceLightInfo* getLightInfo(int32_t id);
+
 private:
     int32_t mId;
     int32_t mGeneration;
@@ -248,6 +274,8 @@
 
     std::vector<MotionRange> mMotionRanges;
     std::unordered_map<InputDeviceSensorType, InputDeviceSensorInfo> mSensors;
+    /* Map from light ID to light info */
+    std::unordered_map<int32_t, InputDeviceLightInfo> mLights;
 };
 
 /* Types of input device configuration files. */
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 11b714f..ba9ae20 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -33,6 +33,7 @@
 #include <unordered_map>
 
 #include <android-base/chrono_utils.h>
+#include <android-base/unique_fd.h>
 
 #include <binder/IBinder.h>
 #include <binder/Parcelable.h>
@@ -44,7 +45,6 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
-#include <android-base/unique_fd.h>
 
 namespace android {
 class Parcel;
@@ -76,10 +76,15 @@
         uint32_t seq;
     } header;
 
-    // Body *must* be 8 byte aligned.
     // For keys and motions, rely on the fact that std::array takes up exactly as much space
     // as the underlying data. This is not guaranteed by C++, but it simplifies the conversions.
     static_assert(sizeof(std::array<uint8_t, 32>) == 32);
+
+    // For bool values, rely on the fact that they take up exactly one byte. This is not guaranteed
+    // by C++ and is implementation-dependent, but it simplifies the conversions.
+    static_assert(sizeof(bool) == 1);
+
+    // Body *must* be 8 byte aligned.
     union Body {
         struct Key {
             int32_t eventId;
@@ -154,8 +159,8 @@
         } motion;
 
         struct Finished {
-            uint32_t empty1;
-            uint32_t handled; // actually a bool, but we must maintain 8-byte alignment
+            bool handled;
+            uint8_t empty[7];
             nsecs_t consumeTime; // The time when the event was consumed by the receiving end
 
             inline size_t size() const { return sizeof(Finished); }
@@ -163,16 +168,18 @@
 
         struct Focus {
             int32_t eventId;
-            // The following two fields take up 4 bytes total
-            uint16_t hasFocus;    // actually a bool
-            uint16_t inTouchMode; // actually a bool, but we must maintain 8-byte alignment
+            // The following 3 fields take up 4 bytes total
+            bool hasFocus;
+            bool inTouchMode;
+            uint8_t empty[2];
 
             inline size_t size() const { return sizeof(Focus); }
         } focus;
 
         struct Capture {
             int32_t eventId;
-            uint32_t pointerCaptureEnabled; // actually a bool, but we maintain 8-byte alignment
+            bool pointerCaptureEnabled;
+            uint8_t empty[3];
 
             inline size_t size() const { return sizeof(Capture); }
         } capture;
@@ -181,21 +188,6 @@
     bool isValid(size_t actualSize) const;
     size_t size() const;
     void getSanitizedCopy(InputMessage* msg) const;
-
-    static const char* typeToString(Type type) {
-        switch (type) {
-            case Type::KEY:
-                return "KEY";
-            case Type::MOTION:
-                return "MOTION";
-            case Type::FINISHED:
-                return "FINISHED";
-            case Type::FOCUS:
-                return "FOCUS";
-            case Type::CAPTURE:
-                return "CAPTURE";
-        }
-    }
 };
 
 /*
diff --git a/include/input/TouchVideoFrame.h b/include/input/TouchVideoFrame.h
index 4fa2f86..eda628e 100644
--- a/include/input/TouchVideoFrame.h
+++ b/include/input/TouchVideoFrame.h
@@ -57,7 +57,7 @@
 
     /**
      * Rotate the video frame.
-     * The rotation value is an enum from ui/DisplayInfo.h
+     * The rotation value is an enum from ui/Rotation.h
      */
     void rotate(int32_t orientation);
 
diff --git a/include/ui/DisplayInfo.h b/include/ui/DisplayInfo.h
deleted file mode 120000
index 9a195ea..0000000
--- a/include/ui/DisplayInfo.h
+++ /dev/null
@@ -1 +0,0 @@
-../../libs/ui/include/ui/DisplayInfo.h
\ No newline at end of file
diff --git a/include/ui/StaticDisplayInfo.h b/include/ui/StaticDisplayInfo.h
new file mode 120000
index 0000000..c58aae3
--- /dev/null
+++ b/include/ui/StaticDisplayInfo.h
@@ -0,0 +1 @@
+../../libs/ui/include/ui/StaticDisplayInfo.h
\ No newline at end of file
diff --git a/libs/attestation/Android.bp b/libs/attestation/Android.bp
index b85aecd..ea3c341 100644
--- a/libs/attestation/Android.bp
+++ b/libs/attestation/Android.bp
@@ -11,6 +11,15 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
 cc_library_static {
     name: "libattestation",
     cflags: [
@@ -28,4 +37,4 @@
         "liblog",
         "libcrypto",
     ],
-}
\ No newline at end of file
+}
diff --git a/libs/attestation/tests/Android.bp b/libs/attestation/tests/Android.bp
index 6ce5ea1..8dac0ce 100644
--- a/libs/attestation/tests/Android.bp
+++ b/libs/attestation/tests/Android.bp
@@ -12,6 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
 cc_test {
     name: "libattestation_tests",
     test_suites: ["device-tests"],
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index e9d866b..6df04f0 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -216,6 +216,11 @@
         "performance*",
         "portability*",
     ],
+
+    pgo: {
+        sampling: true,
+        profile_file: "libbinder/libbinder.profdata",
+    },
 }
 
 // AIDL interface between libbinder and framework.jar
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 2e90142..1fbaa13 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -22,10 +22,6 @@
       "name": "binderParcelTest"
     },
     {
-      "name": "binderParcelTest",
-      "host": true
-    },
-    {
       "name": "binderLibTest"
     },
     {
diff --git a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
index a7a6563..c20d9f6 100644
--- a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
+++ b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
@@ -113,5 +113,14 @@
      * Returns the debug flag for the given package.
      * Unknown packages will cause the call to fail.
      */
-     boolean isPackageDebuggable(in String packageName);
+    boolean isPackageDebuggable(in String packageName);
+
+    /**
+     * Check whether the given feature name and version is one of the available
+     * features as returned by {@link PackageManager#getSystemAvailableFeatures()}. Since
+     * features are defined to always be backwards compatible, this returns true
+     * if the available feature version is greater than or equal to the
+     * requested version.
+     */
+    boolean hasSystemFeature(in String featureName, in int version);
 }
diff --git a/libs/binder/ndk/include_cpp/android/binder_to_string.h b/libs/binder/ndk/include_cpp/android/binder_to_string.h
index 5842925..ef71a81 100644
--- a/libs/binder/ndk/include_cpp/android/binder_to_string.h
+++ b/libs/binder/ndk/include_cpp/android/binder_to_string.h
@@ -136,8 +136,7 @@
     template <typename _U>
     static std::enable_if_t<
 #ifdef HAS_NDK_INTERFACE
-            std::is_base_of_v<::ndk::ICInterface, _U> || std::is_same_v<::ndk::SpAIBinder, _U> ||
-                    std::is_same_v<::ndk::ScopedFileDescriptor, _U> ||
+            std::is_base_of_v<::ndk::ICInterface, _U> ||
                     std::is_same_v<::ndk::AParcelableHolder, _U>
 #else
             std::is_base_of_v<IInterface, _U> || std::is_same_v<IBinder, _U> ||
@@ -168,13 +167,19 @@
         return std::to_string(t);
     } else if constexpr (std::is_same_v<std::string, _T>) {
         return t;
+#ifdef HAS_NDK_INTERFACE
+    } else if constexpr (std::is_same_v<::ndk::SpAIBinder, _T>) {
+        return (t.get() == nullptr) ? "(null)" : "";
+    } else if constexpr (std::is_same_v<::ndk::ScopedFileDescriptor, _T>) {
+        return (t.get() == -1) ? "(null)" : "";
+#endif
 #ifdef HAS_STRING16
     } else if constexpr (std::is_same_v<String16, _T>) {
         std::stringstream out;
         out << t;
         return out.str();
 #endif
-    } else if constexpr (details::IsPointerLike<_T>::value) {
+    } else if constexpr (details::IsPointerLike<_T>::value || std::is_pointer_v<_T>) {
         if (!t) return "(null)";
         std::stringstream out;
         out << ToString(*t);
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index 4580751..5df0012 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -96,6 +96,22 @@
 bool AServiceManager_isDeclared(const char* instance) __INTRODUCED_IN(31);
 
 /**
+ * Returns all declared instances for a particular interface.
+ *
+ * For instance, if 'android.foo.IFoo/foo' is declared, and 'android.foo.IFoo' is
+ * passed here, then ["foo"] would be returned.
+ *
+ * See also AServiceManager_isDeclared.
+ *
+ * \param interface interface, e.g. 'android.foo.IFoo'
+ * \param context to pass to callback
+ * \param callback taking instance (e.g. 'foo') and context
+ */
+void AServiceManager_forEachDeclaredInstance(const char* interface, void* context,
+                                             void (*callback)(const char*, void*))
+        __INTRODUCED_IN(31);
+
+/**
  * Prevent lazy services without client from shutting down their process
  *
  * \param persist 'true' if the process should not exit.
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index cef0bf3..8d08275 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -118,6 +118,7 @@
     AIBinder_getCallingSid; # apex
     AIBinder_setRequestingSid; # apex
     AServiceManager_isDeclared; # apex llndk
+    AServiceManager_forEachDeclaredInstance; # apex llndk
     AServiceManager_registerLazyService; # llndk
     AServiceManager_waitForService; # apex llndk
     AServiceManager_forceLazyServicesPersist; # llndk
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index cb0987e..1ccd0d2 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -19,6 +19,7 @@
 #include "ibinder_internal.h"
 #include "status_internal.h"
 
+#include <android-base/logging.h>
 #include <binder/IServiceManager.h>
 #include <binder/LazyServiceRegistrar.h>
 
@@ -28,6 +29,7 @@
 using ::android::sp;
 using ::android::status_t;
 using ::android::String16;
+using ::android::String8;
 
 binder_exception_t AServiceManager_addService(AIBinder* binder, const char* instance) {
     if (binder == nullptr || instance == nullptr) {
@@ -92,6 +94,17 @@
     sp<IServiceManager> sm = defaultServiceManager();
     return sm->isDeclared(String16(instance));
 }
+void AServiceManager_forEachDeclaredInstance(const char* interface, void* context,
+                                             void (*callback)(const char*, void*)) {
+    CHECK(interface != nullptr);
+    // context may be nullptr
+    CHECK(callback != nullptr);
+
+    sp<IServiceManager> sm = defaultServiceManager();
+    for (const String16& instance : sm->getDeclaredInstances(String16(interface))) {
+        callback(String8(instance).c_str(), context);
+    }
+}
 void AServiceManager_forceLazyServicesPersist(bool persist) {
     auto serviceRegistrar = android::binder::LazyServiceRegistrar::getInstance();
     serviceRegistrar.forcePersist(persist);
@@ -110,4 +123,4 @@
 void AServiceManager_reRegister() {
     auto serviceRegistrar = android::binder::LazyServiceRegistrar::getInstance();
     serviceRegistrar.reRegister();
-}
\ No newline at end of file
+}
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index de1a48d..6a88401 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -270,6 +270,25 @@
     EXPECT_EQ(2, out);
 }
 
+void defaultInstanceCounter(const char* instance, void* context) {
+    if (strcmp(instance, "default") == 0) {
+        ++*(size_t*)(context);
+    }
+}
+
+TEST(NdkBinder, GetDeclaredInstances) {
+    bool hasLight = AServiceManager_isDeclared("android.hardware.light.ILights/default");
+
+    size_t count;
+    AServiceManager_forEachDeclaredInstance("android.hardware.light.ILights", &count,
+                                            defaultInstanceCounter);
+
+    // At the time of writing this test, there is no good interface guaranteed
+    // to be on all devices. Cuttlefish has light, so this will generally test
+    // things.
+    EXPECT_EQ(count, hasLight ? 1 : 0);
+}
+
 TEST(NdkBinder, GetLazyService) {
     // Not declared in the vintf manifest
     ASSERT_FALSE(AServiceManager_isDeclared(kLazyBinderNdkUnitTestService));
diff --git a/libs/binder/rust/tests/serialization.rs b/libs/binder/rust/tests/serialization.rs
index 2bf3d03..f1b068e 100644
--- a/libs/binder/rust/tests/serialization.rs
+++ b/libs/binder/rust/tests/serialization.rs
@@ -40,6 +40,41 @@
     include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
 }
 
+macro_rules! assert_eq {
+    ($left:expr, $right:expr $(,)?) => {
+        match (&$left, &$right) {
+            (left, right) => {
+                if *left != *right {
+                    eprintln!(
+                        "assertion failed: `{:?}` == `{:?}`, {}:{}:{}",
+                        &*left,
+                        &*right,
+                        file!(),
+                        line!(),
+                        column!()
+                    );
+                    return Err(StatusCode::FAILED_TRANSACTION);
+                }
+            }
+        }
+    };
+}
+
+macro_rules! assert {
+    ($expr:expr) => {
+        if !$expr {
+            eprintln!(
+                "assertion failed: `{:?}`, {}:{}:{}",
+                $expr,
+                file!(),
+                line!(),
+                column!()
+            );
+            return Err(StatusCode::FAILED_TRANSACTION);
+        }
+    };
+}
+
 static SERVICE_ONCE: Once = Once::new();
 static mut SERVICE: Option<SpIBinder> = None;
 
@@ -282,7 +317,7 @@
             ))?;
         }
         bindings::Transaction_TEST_FAIL => {
-            return Err(StatusCode::FAILED_TRANSACTION)
+            assert!(false);
         }
         _ => return Err(StatusCode::UNKNOWN_TRANSACTION),
     }
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index 5bccaca..97626be 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
 cc_test {
     name: "ftl_test",
     test_suites: ["device-tests"],
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index debd664..ff059d7 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -143,7 +143,16 @@
 
     aidl: {
         export_aidl_headers: true,
-    }
+    },
+
+    pgo: {
+        sampling: true,
+        profile_file: "libgui/libgui.profdata",
+    },
+
+    lto: {
+        thin: true,
+    },
 }
 
 // Used by media codec services exclusively as a static lib for
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 762746c..989abd9 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -17,15 +17,10 @@
 // tag as surfaceflinger
 #define LOG_TAG "SurfaceFlinger"
 
-#include <stdint.h>
-#include <sys/types.h>
-
 #include <android/gui/ITransactionTraceListener.h>
-
-#include <binder/Parcel.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
-
+#include <binder/Parcel.h>
 #include <gui/IDisplayEventConnection.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/IRegionSamplingListener.h>
@@ -33,15 +28,15 @@
 #include <gui/ISurfaceComposerClient.h>
 #include <gui/LayerDebugInfo.h>
 #include <gui/LayerState.h>
-
+#include <stdint.h>
+#include <sys/types.h>
 #include <system/graphics.h>
-
-#include <ui/DisplayInfo.h>
 #include <ui/DisplayMode.h>
 #include <ui/DisplayStatInfo.h>
 #include <ui/DisplayState.h>
+#include <ui/DynamicDisplayInfo.h>
 #include <ui/HdrCapabilities.h>
-
+#include <ui/StaticDisplayInfo.h>
 #include <utils/Log.h>
 
 // ---------------------------------------------------------------------------
@@ -323,32 +318,26 @@
         return result;
     }
 
-    status_t getDisplayInfo(const sp<IBinder>& display, DisplayInfo* info) override {
+    status_t getStaticDisplayInfo(const sp<IBinder>& display,
+                                  ui::StaticDisplayInfo* info) override {
         Parcel data, reply;
         data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
         data.writeStrongBinder(display);
-        remote()->transact(BnSurfaceComposer::GET_DISPLAY_INFO, data, &reply);
+        remote()->transact(BnSurfaceComposer::GET_STATIC_DISPLAY_INFO, data, &reply);
         const status_t result = reply.readInt32();
         if (result != NO_ERROR) return result;
         return reply.read(*info);
     }
 
-    status_t getDisplayModes(const sp<IBinder>& display, Vector<ui::DisplayMode>* modes) override {
+    status_t getDynamicDisplayInfo(const sp<IBinder>& display,
+                                   ui::DynamicDisplayInfo* info) override {
         Parcel data, reply;
         data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
         data.writeStrongBinder(display);
-        remote()->transact(BnSurfaceComposer::GET_DISPLAY_MODES, data, &reply);
+        remote()->transact(BnSurfaceComposer::GET_DYNAMIC_DISPLAY_INFO, data, &reply);
         const status_t result = reply.readInt32();
-        if (result == NO_ERROR) {
-            const size_t numModes = reply.readUint32();
-            modes->clear();
-            modes->resize(numModes);
-            for (size_t i = 0; i < numModes; i++) {
-                memcpy(&(modes->editItemAt(i)), reply.readInplace(sizeof(ui::DisplayMode)),
-                       sizeof(ui::DisplayMode));
-            }
-        }
-        return result;
+        if (result != NO_ERROR) return result;
+        return reply.read(*info);
     }
 
     status_t getDisplayStats(const sp<IBinder>& display, DisplayStatInfo* stats) override {
@@ -365,44 +354,6 @@
         return result;
     }
 
-    int getActiveDisplayModeId(const sp<IBinder>& display) override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        data.writeStrongBinder(display);
-        remote()->transact(BnSurfaceComposer::GET_ACTIVE_DISPLAY_MODE, data, &reply);
-        return reply.readInt32();
-    }
-
-    status_t getDisplayColorModes(const sp<IBinder>& display,
-                                  Vector<ColorMode>* outColorModes) override {
-        Parcel data, reply;
-        status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        if (result != NO_ERROR) {
-            ALOGE("getDisplayColorModes failed to writeInterfaceToken: %d", result);
-            return result;
-        }
-        result = data.writeStrongBinder(display);
-        if (result != NO_ERROR) {
-            ALOGE("getDisplayColorModes failed to writeStrongBinder: %d", result);
-            return result;
-        }
-        result = remote()->transact(BnSurfaceComposer::GET_DISPLAY_COLOR_MODES, data, &reply);
-        if (result != NO_ERROR) {
-            ALOGE("getDisplayColorModes failed to transact: %d", result);
-            return result;
-        }
-        result = static_cast<status_t>(reply.readInt32());
-        if (result == NO_ERROR) {
-            size_t numModes = reply.readUint32();
-            outColorModes->clear();
-            outColorModes->resize(numModes);
-            for (size_t i = 0; i < numModes; ++i) {
-                outColorModes->replaceAt(static_cast<ColorMode>(reply.readInt32()), i);
-            }
-        }
-        return result;
-    }
-
     status_t getDisplayNativePrimaries(const sp<IBinder>& display,
                                        ui::DisplayPrimaries& primaries) override {
         Parcel data, reply;
@@ -429,26 +380,6 @@
         return result;
     }
 
-    ColorMode getActiveColorMode(const sp<IBinder>& display) override {
-        Parcel data, reply;
-        status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        if (result != NO_ERROR) {
-            ALOGE("getActiveColorMode failed to writeInterfaceToken: %d", result);
-            return static_cast<ColorMode>(result);
-        }
-        result = data.writeStrongBinder(display);
-        if (result != NO_ERROR) {
-            ALOGE("getActiveColorMode failed to writeStrongBinder: %d", result);
-            return static_cast<ColorMode>(result);
-        }
-        result = remote()->transact(BnSurfaceComposer::GET_ACTIVE_COLOR_MODE, data, &reply);
-        if (result != NO_ERROR) {
-            ALOGE("getActiveColorMode failed to transact: %d", result);
-            return static_cast<ColorMode>(result);
-        }
-        return static_cast<ColorMode>(reply.readInt32());
-    }
-
     status_t setActiveColorMode(const sp<IBinder>& display, ColorMode colorMode) override {
         Parcel data, reply;
         status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
@@ -474,24 +405,6 @@
         return static_cast<status_t>(reply.readInt32());
     }
 
-    status_t getAutoLowLatencyModeSupport(const sp<IBinder>& display,
-                                          bool* outSupport) const override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        status_t result = data.writeStrongBinder(display);
-        if (result != NO_ERROR) {
-            ALOGE("getAutoLowLatencyModeSupport failed to writeStrongBinder: %d", result);
-            return result;
-        }
-        result = remote()->transact(BnSurfaceComposer::GET_AUTO_LOW_LATENCY_MODE_SUPPORT, data,
-                                    &reply);
-        if (result != NO_ERROR) {
-            ALOGE("getAutoLowLatencyModeSupport failed to transact: %d", result);
-            return result;
-        }
-        return reply.readBool(outSupport);
-    }
-
     void setAutoLowLatencyMode(const sp<IBinder>& display, bool on) override {
         Parcel data, reply;
         status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
@@ -517,23 +430,6 @@
         }
     }
 
-    status_t getGameContentTypeSupport(const sp<IBinder>& display,
-                                       bool* outSupport) const override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        status_t result = data.writeStrongBinder(display);
-        if (result != NO_ERROR) {
-            ALOGE("getGameContentTypeSupport failed to writeStrongBinder: %d", result);
-            return result;
-        }
-        result = remote()->transact(BnSurfaceComposer::GET_GAME_CONTENT_TYPE_SUPPORT, data, &reply);
-        if (result != NO_ERROR) {
-            ALOGE("getGameContentTypeSupport failed to transact: %d", result);
-            return result;
-        }
-        return reply.readBool(outSupport);
-    }
-
     void setGameContentType(const sp<IBinder>& display, bool on) override {
         Parcel data, reply;
         status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
@@ -580,28 +476,6 @@
         return reply.readInt32();
     }
 
-    status_t getHdrCapabilities(const sp<IBinder>& display,
-                                HdrCapabilities* outCapabilities) const override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        status_t result = data.writeStrongBinder(display);
-        if (result != NO_ERROR) {
-            ALOGE("getHdrCapabilities failed to writeStrongBinder: %d", result);
-            return result;
-        }
-        result = remote()->transact(BnSurfaceComposer::GET_HDR_CAPABILITIES,
-                data, &reply);
-        if (result != NO_ERROR) {
-            ALOGE("getHdrCapabilities failed to transact: %d", result);
-            return result;
-        }
-        result = reply.readInt32();
-        if (result == NO_ERROR) {
-            result = reply.read(*outCapabilities);
-        }
-        return result;
-    }
-
     status_t enableVSyncInjections(bool enable) override {
         Parcel data, reply;
         status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
@@ -881,9 +755,36 @@
         return error;
     }
 
-    status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, size_t defaultMode,
-                                        bool allowGroupSwitching, float primaryRefreshRateMin,
-                                        float primaryRefreshRateMax, float appRequestRefreshRateMin,
+    virtual status_t addFpsListener(int32_t taskId, const sp<gui::IFpsListener>& listener) {
+        Parcel data, reply;
+        SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor());
+        SAFE_PARCEL(data.writeInt32, taskId);
+        SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener));
+        const status_t error =
+                remote()->transact(BnSurfaceComposer::ADD_FPS_LISTENER, data, &reply);
+        if (error != OK) {
+            ALOGE("addFpsListener: Failed to transact");
+        }
+        return error;
+    }
+
+    virtual status_t removeFpsListener(const sp<gui::IFpsListener>& listener) {
+        Parcel data, reply;
+        SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor());
+        SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener));
+
+        const status_t error =
+                remote()->transact(BnSurfaceComposer::REMOVE_FPS_LISTENER, data, &reply);
+        if (error != OK) {
+            ALOGE("removeFpsListener: Failed to transact");
+        }
+        return error;
+    }
+
+    status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                        ui::DisplayModeId defaultMode, bool allowGroupSwitching,
+                                        float primaryRefreshRateMin, float primaryRefreshRateMax,
+                                        float appRequestRefreshRateMin,
                                         float appRequestRefreshRateMax) override {
         Parcel data, reply;
         status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
@@ -938,7 +839,8 @@
         return reply.readInt32();
     }
 
-    status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, size_t* outDefaultMode,
+    status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                        ui::DisplayModeId* outDefaultMode,
                                         bool* outAllowGroupSwitching,
                                         float* outPrimaryRefreshRateMin,
                                         float* outPrimaryRefreshRateMax,
@@ -966,17 +868,16 @@
             ALOGE("getDesiredDisplayModeSpecs failed to transact: %d", result);
             return result;
         }
-        int32_t defaultMode;
-        result = reply.readInt32(&defaultMode);
+
+        result = reply.readInt32(outDefaultMode);
         if (result != NO_ERROR) {
             ALOGE("getDesiredDisplayModeSpecs failed to read defaultMode: %d", result);
             return result;
         }
-        if (defaultMode < 0) {
-            ALOGE("%s: defaultMode must be non-negative but it was %d", __func__, defaultMode);
+        if (*outDefaultMode < 0) {
+            ALOGE("%s: defaultMode must be non-negative but it was %d", __func__, *outDefaultMode);
             return BAD_VALUE;
         }
-        *outDefaultMode = static_cast<size_t>(defaultMode);
 
         result = reply.readBool(outAllowGroupSwitching);
         if (result != NO_ERROR) {
@@ -1436,28 +1337,24 @@
             }
             return NO_ERROR;
         }
-        case GET_DISPLAY_INFO: {
+        case GET_STATIC_DISPLAY_INFO: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            DisplayInfo info;
+            ui::StaticDisplayInfo info;
             const sp<IBinder> display = data.readStrongBinder();
-            const status_t result = getDisplayInfo(display, &info);
-            reply->writeInt32(result);
+            const status_t result = getStaticDisplayInfo(display, &info);
+            SAFE_PARCEL(reply->writeInt32, result);
             if (result != NO_ERROR) return result;
-            return reply->write(info);
+            SAFE_PARCEL(reply->write, info);
+            return NO_ERROR;
         }
-        case GET_DISPLAY_MODES: {
+        case GET_DYNAMIC_DISPLAY_INFO: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            Vector<ui::DisplayMode> modes;
+            ui::DynamicDisplayInfo info;
             const sp<IBinder> display = data.readStrongBinder();
-            const status_t result = getDisplayModes(display, &modes);
-            reply->writeInt32(result);
-            if (result == NO_ERROR) {
-                reply->writeUint32(static_cast<uint32_t>(modes.size()));
-                for (size_t i = 0; i < modes.size(); i++) {
-                    memcpy(reply->writeInplace(sizeof(ui::DisplayMode)), &modes[i],
-                           sizeof(ui::DisplayMode));
-                }
-            }
+            const status_t result = getDynamicDisplayInfo(display, &info);
+            SAFE_PARCEL(reply->writeInt32, result);
+            if (result != NO_ERROR) return result;
+            SAFE_PARCEL(reply->write, info);
             return NO_ERROR;
         }
         case GET_DISPLAY_STATS: {
@@ -1472,32 +1369,6 @@
             }
             return NO_ERROR;
         }
-        case GET_ACTIVE_DISPLAY_MODE: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> display = data.readStrongBinder();
-            int id = getActiveDisplayModeId(display);
-            reply->writeInt32(id);
-            return NO_ERROR;
-        }
-        case GET_DISPLAY_COLOR_MODES: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            Vector<ColorMode> colorModes;
-            sp<IBinder> display = nullptr;
-            status_t result = data.readStrongBinder(&display);
-            if (result != NO_ERROR) {
-                ALOGE("getDisplayColorModes failed to readStrongBinder: %d", result);
-                return result;
-            }
-            result = getDisplayColorModes(display, &colorModes);
-            reply->writeInt32(result);
-            if (result == NO_ERROR) {
-                reply->writeUint32(static_cast<uint32_t>(colorModes.size()));
-                for (size_t i = 0; i < colorModes.size(); ++i) {
-                    reply->writeInt32(static_cast<int32_t>(colorModes[i]));
-                }
-            }
-            return NO_ERROR;
-        }
         case GET_DISPLAY_NATIVE_PRIMARIES: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             ui::DisplayPrimaries primaries;
@@ -1518,18 +1389,6 @@
 
             return NO_ERROR;
         }
-        case GET_ACTIVE_COLOR_MODE: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> display = nullptr;
-            status_t result = data.readStrongBinder(&display);
-            if (result != NO_ERROR) {
-                ALOGE("getActiveColorMode failed to readStrongBinder: %d", result);
-                return result;
-            }
-            ColorMode colorMode = getActiveColorMode(display);
-            result = reply->writeInt32(static_cast<int32_t>(colorMode));
-            return result;
-        }
         case SET_ACTIVE_COLOR_MODE: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IBinder> display = nullptr;
@@ -1549,23 +1408,6 @@
             result = reply->writeInt32(result);
             return result;
         }
-
-        case GET_AUTO_LOW_LATENCY_MODE_SUPPORT: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> display = nullptr;
-            status_t result = data.readStrongBinder(&display);
-            if (result != NO_ERROR) {
-                ALOGE("getAutoLowLatencyModeSupport failed to readStrongBinder: %d", result);
-                return result;
-            }
-            bool supported = false;
-            result = getAutoLowLatencyModeSupport(display, &supported);
-            if (result == NO_ERROR) {
-                result = reply->writeBool(supported);
-            }
-            return result;
-        }
-
         case SET_AUTO_LOW_LATENCY_MODE: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IBinder> display = nullptr;
@@ -1583,23 +1425,6 @@
             setAutoLowLatencyMode(display, setAllm);
             return result;
         }
-
-        case GET_GAME_CONTENT_TYPE_SUPPORT: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> display = nullptr;
-            status_t result = data.readStrongBinder(&display);
-            if (result != NO_ERROR) {
-                ALOGE("getGameContentTypeSupport failed to readStrongBinder: %d", result);
-                return result;
-            }
-            bool supported = false;
-            result = getGameContentTypeSupport(display, &supported);
-            if (result == NO_ERROR) {
-                result = reply->writeBool(supported);
-            }
-            return result;
-        }
-
         case SET_GAME_CONTENT_TYPE: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IBinder> display = nullptr;
@@ -1617,7 +1442,6 @@
             setGameContentType(display, setGameContentTypeOn);
             return result;
         }
-
         case CLEAR_ANIMATION_FRAME_STATS: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             status_t result = clearAnimationFrameStats();
@@ -1639,23 +1463,6 @@
             setPowerMode(display, mode);
             return NO_ERROR;
         }
-        case GET_HDR_CAPABILITIES: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> display = nullptr;
-            status_t result = data.readStrongBinder(&display);
-            if (result != NO_ERROR) {
-                ALOGE("getHdrCapabilities failed to readStrongBinder: %d",
-                        result);
-                return result;
-            }
-            HdrCapabilities capabilities;
-            result = getHdrCapabilities(display, &capabilities);
-            reply->writeInt32(result);
-            if (result == NO_ERROR) {
-                reply->write(capabilities);
-            }
-            return NO_ERROR;
-        }
         case ENABLE_VSYNC_INJECTIONS: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             bool enable = false;
@@ -1859,10 +1666,36 @@
             }
             return removeRegionSamplingListener(listener);
         }
+        case ADD_FPS_LISTENER: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            int32_t taskId;
+            status_t result = data.readInt32(&taskId);
+            if (result != NO_ERROR) {
+                ALOGE("addFpsListener: Failed to read layer handle");
+                return result;
+            }
+            sp<gui::IFpsListener> listener;
+            result = data.readNullableStrongBinder(&listener);
+            if (result != NO_ERROR) {
+                ALOGE("addFpsListener: Failed to read listener");
+                return result;
+            }
+            return addFpsListener(taskId, listener);
+        }
+        case REMOVE_FPS_LISTENER: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            sp<gui::IFpsListener> listener;
+            status_t result = data.readNullableStrongBinder(&listener);
+            if (result != NO_ERROR) {
+                ALOGE("removeFpsListener: Failed to read listener");
+                return result;
+            }
+            return removeFpsListener(listener);
+        }
         case SET_DESIRED_DISPLAY_MODE_SPECS: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IBinder> displayToken = data.readStrongBinder();
-            int32_t defaultMode;
+            ui::DisplayModeId defaultMode;
             status_t result = data.readInt32(&defaultMode);
             if (result != NO_ERROR) {
                 ALOGE("setDesiredDisplayModeSpecs: failed to read defaultMode: %d", result);
@@ -1906,10 +1739,9 @@
                       result);
                 return result;
             }
-            result = setDesiredDisplayModeSpecs(displayToken, static_cast<size_t>(defaultMode),
-                                                allowGroupSwitching, primaryRefreshRateMin,
-                                                primaryRefreshRateMax, appRequestRefreshRateMin,
-                                                appRequestRefreshRateMax);
+            result = setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching,
+                                                primaryRefreshRateMin, primaryRefreshRateMax,
+                                                appRequestRefreshRateMin, appRequestRefreshRateMax);
             if (result != NO_ERROR) {
                 ALOGE("setDesiredDisplayModeSpecs: failed to call setDesiredDisplayModeSpecs: "
                       "%d",
@@ -1922,7 +1754,7 @@
         case GET_DESIRED_DISPLAY_MODE_SPECS: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IBinder> displayToken = data.readStrongBinder();
-            size_t defaultMode;
+            ui::DisplayModeId defaultMode;
             bool allowGroupSwitching;
             float primaryRefreshRateMin;
             float primaryRefreshRateMax;
@@ -1941,7 +1773,7 @@
                 return result;
             }
 
-            result = reply->writeInt32(static_cast<int32_t>(defaultMode));
+            result = reply->writeInt32(defaultMode);
             if (result != NO_ERROR) {
                 ALOGE("getDesiredDisplayModeSpecs: failed to write defaultMode: %d", result);
                 return result;
diff --git a/libs/gui/LayerDebugInfo.cpp b/libs/gui/LayerDebugInfo.cpp
index cdde9a2..e707684 100644
--- a/libs/gui/LayerDebugInfo.cpp
+++ b/libs/gui/LayerDebugInfo.cpp
@@ -61,6 +61,7 @@
     RETURN_ON_ERROR(parcel->writeBool(mRefreshPending));
     RETURN_ON_ERROR(parcel->writeBool(mIsOpaque));
     RETURN_ON_ERROR(parcel->writeBool(mContentDirty));
+    RETURN_ON_ERROR(parcel->write(mStretchEffect));
     return NO_ERROR;
 }
 
@@ -105,6 +106,7 @@
     RETURN_ON_ERROR(parcel->readBool(&mRefreshPending));
     RETURN_ON_ERROR(parcel->readBool(&mIsOpaque));
     RETURN_ON_ERROR(parcel->readBool(&mContentDirty));
+    RETURN_ON_ERROR(parcel->read(mStretchEffect));
     return NO_ERROR;
 }
 
@@ -115,6 +117,12 @@
     info.mTransparentRegion.dump(result, "TransparentRegion");
     info.mVisibleRegion.dump(result, "VisibleRegion");
     info.mSurfaceDamageRegion.dump(result, "SurfaceDamageRegion");
+    if (info.mStretchEffect.hasEffect()) {
+        const auto& se = info.mStretchEffect;
+        StringAppendF(&result, "  StretchEffect area=[%f, %f, %f, %f] vec=(%f, %f) maxAmount=%f\n",
+                      se.area.left, se.area.top, se.area.right, se.area.bottom, se.vectorX,
+                      se.vectorY, se.maxAmount);
+    }
 
     StringAppendF(&result, "      layerStack=%4d, z=%9d, pos=(%g,%g), size=(%4d,%4d), ",
                   info.mLayerStack, info.mZ, static_cast<double>(info.mX),
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index f053372..288bf92 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -548,6 +548,10 @@
         what |= eAutoRefreshChanged;
         autoRefresh = other.autoRefresh;
     }
+    if (other.what & eStretchChanged) {
+        what |= eStretchChanged;
+        stretchEffect = other.stretchEffect;
+    }
     if ((other.what & what) != other.what) {
         ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
               "other.what=0x%" PRIu64 " what=0x%" PRIu64,
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 07fc069..6de3e97 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -34,9 +34,9 @@
 #include <utils/NativeHandle.h>
 
 #include <ui/DisplayStatInfo.h>
+#include <ui/DynamicDisplayInfo.h>
 #include <ui/Fence.h>
 #include <ui/GraphicBuffer.h>
-#include <ui/HdrCapabilities.h>
 #include <ui/Region.h>
 
 #include <gui/BufferItem.h>
@@ -48,7 +48,6 @@
 
 namespace android {
 
-using ui::ColorMode;
 using ui::Dataspace;
 
 namespace {
@@ -361,15 +360,12 @@
         return NAME_NOT_FOUND;
     }
 
-    HdrCapabilities hdrCapabilities;
-    status_t err =
-        composerService()->getHdrCapabilities(display, &hdrCapabilities);
-
-    if (err)
+    ui::DynamicDisplayInfo info;
+    if (status_t err = composerService()->getDynamicDisplayInfo(display, &info); err != NO_ERROR) {
         return err;
+    }
 
-    *supported = !hdrCapabilities.getSupportedHdrTypes().empty();
-
+    *supported = !info.hdrCapabilities.getSupportedHdrTypes().empty();
     return NO_ERROR;
 }
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index d41da9b..5e8ab92 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -40,6 +40,7 @@
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 #include <ui/DisplayMode.h>
+#include <ui/DynamicDisplayInfo.h>
 
 #ifndef NO_INPUT
 #include <input/InputWindow.h>
@@ -1843,39 +1844,35 @@
     return ComposerService::getComposerService()->getDisplayState(display, state);
 }
 
-status_t SurfaceComposerClient::getDisplayInfo(const sp<IBinder>& display, DisplayInfo* info) {
-    return ComposerService::getComposerService()->getDisplayInfo(display, info);
+status_t SurfaceComposerClient::getStaticDisplayInfo(const sp<IBinder>& display,
+                                                     ui::StaticDisplayInfo* info) {
+    return ComposerService::getComposerService()->getStaticDisplayInfo(display, info);
 }
 
-status_t SurfaceComposerClient::getDisplayModes(const sp<IBinder>& display,
-                                                Vector<ui::DisplayMode>* modes) {
-    return ComposerService::getComposerService()->getDisplayModes(display, modes);
+status_t SurfaceComposerClient::getDynamicDisplayInfo(const sp<IBinder>& display,
+                                                      ui::DynamicDisplayInfo* info) {
+    return ComposerService::getComposerService()->getDynamicDisplayInfo(display, info);
 }
 
 status_t SurfaceComposerClient::getActiveDisplayMode(const sp<IBinder>& display,
                                                      ui::DisplayMode* mode) {
-    Vector<ui::DisplayMode> modes;
-    status_t result = getDisplayModes(display, &modes);
+    ui::DynamicDisplayInfo info;
+    status_t result = getDynamicDisplayInfo(display, &info);
     if (result != NO_ERROR) {
         return result;
     }
 
-    int activeId = getActiveDisplayModeId(display);
-    if (activeId < 0) {
-        ALOGE("No active mode found");
-        return NAME_NOT_FOUND;
+    if (const auto activeMode = info.getActiveDisplayMode()) {
+        *mode = *activeMode;
+        return NO_ERROR;
     }
 
-    *mode = modes[static_cast<size_t>(activeId)];
-    return NO_ERROR;
-}
-
-int SurfaceComposerClient::getActiveDisplayModeId(const sp<IBinder>& display) {
-    return ComposerService::getComposerService()->getActiveDisplayModeId(display);
+    ALOGE("Active display mode not found.");
+    return NAME_NOT_FOUND;
 }
 
 status_t SurfaceComposerClient::setDesiredDisplayModeSpecs(
-        const sp<IBinder>& displayToken, size_t defaultMode, bool allowGroupSwitching,
+        const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching,
         float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin,
         float appRequestRefreshRateMax) {
     return ComposerService::getComposerService()
@@ -1884,51 +1881,33 @@
                                          appRequestRefreshRateMin, appRequestRefreshRateMax);
 }
 
-status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(
-        const sp<IBinder>& displayToken, size_t* outDefaultMode, bool* outAllowGroupSwitching,
-        float* outPrimaryRefreshRateMin, float* outPrimaryRefreshRateMax,
-        float* outAppRequestRefreshRateMin, float* outAppRequestRefreshRateMax) {
+status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                                           ui::DisplayModeId* outDefaultMode,
+                                                           bool* outAllowGroupSwitching,
+                                                           float* outPrimaryRefreshRateMin,
+                                                           float* outPrimaryRefreshRateMax,
+                                                           float* outAppRequestRefreshRateMin,
+                                                           float* outAppRequestRefreshRateMax) {
     return ComposerService::getComposerService()
             ->getDesiredDisplayModeSpecs(displayToken, outDefaultMode, outAllowGroupSwitching,
                                          outPrimaryRefreshRateMin, outPrimaryRefreshRateMax,
                                          outAppRequestRefreshRateMin, outAppRequestRefreshRateMax);
 }
 
-status_t SurfaceComposerClient::getDisplayColorModes(const sp<IBinder>& display,
-        Vector<ColorMode>* outColorModes) {
-    return ComposerService::getComposerService()->getDisplayColorModes(display, outColorModes);
-}
-
 status_t SurfaceComposerClient::getDisplayNativePrimaries(const sp<IBinder>& display,
         ui::DisplayPrimaries& outPrimaries) {
     return ComposerService::getComposerService()->getDisplayNativePrimaries(display, outPrimaries);
 }
 
-ColorMode SurfaceComposerClient::getActiveColorMode(const sp<IBinder>& display) {
-    return ComposerService::getComposerService()->getActiveColorMode(display);
-}
-
 status_t SurfaceComposerClient::setActiveColorMode(const sp<IBinder>& display,
         ColorMode colorMode) {
     return ComposerService::getComposerService()->setActiveColorMode(display, colorMode);
 }
 
-bool SurfaceComposerClient::getAutoLowLatencyModeSupport(const sp<IBinder>& display) {
-    bool supported = false;
-    ComposerService::getComposerService()->getAutoLowLatencyModeSupport(display, &supported);
-    return supported;
-}
-
 void SurfaceComposerClient::setAutoLowLatencyMode(const sp<IBinder>& display, bool on) {
     ComposerService::getComposerService()->setAutoLowLatencyMode(display, on);
 }
 
-bool SurfaceComposerClient::getGameContentTypeSupport(const sp<IBinder>& display) {
-    bool supported = false;
-    ComposerService::getComposerService()->getGameContentTypeSupport(display, &supported);
-    return supported;
-}
-
 void SurfaceComposerClient::setGameContentType(const sp<IBinder>& display, bool on) {
     ComposerService::getComposerService()->setGameContentType(display, on);
 }
@@ -1960,12 +1939,6 @@
     return ComposerService::getComposerService()->getAnimationFrameStats(outStats);
 }
 
-status_t SurfaceComposerClient::getHdrCapabilities(const sp<IBinder>& display,
-        HdrCapabilities* outCapabilities) {
-    return ComposerService::getComposerService()->getHdrCapabilities(display,
-            outCapabilities);
-}
-
 status_t SurfaceComposerClient::getDisplayedContentSamplingAttributes(const sp<IBinder>& display,
                                                                       ui::PixelFormat* outFormat,
                                                                       ui::Dataspace* outDataspace,
@@ -2009,6 +1982,15 @@
     return ComposerService::getComposerService()->removeRegionSamplingListener(listener);
 }
 
+status_t SurfaceComposerClient::addFpsListener(int32_t taskId,
+                                               const sp<gui::IFpsListener>& listener) {
+    return ComposerService::getComposerService()->addFpsListener(taskId, listener);
+}
+
+status_t SurfaceComposerClient::removeFpsListener(const sp<gui::IFpsListener>& listener) {
+    return ComposerService::getComposerService()->removeFpsListener(listener);
+}
+
 bool SurfaceComposerClient::getDisplayBrightnessSupport(const sp<IBinder>& displayToken) {
     bool support = false;
     ComposerService::getComposerService()->getDisplayBrightnessSupport(displayToken, &support);
diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp
index e842382..1dcfe2e 100644
--- a/libs/gui/SurfaceControl.cpp
+++ b/libs/gui/SurfaceControl.cpp
@@ -30,9 +30,9 @@
 
 #include <binder/IPCThreadState.h>
 
-#include <ui/DisplayInfo.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/Rect.h>
+#include <ui/StaticDisplayInfo.h>
 
 #include <gui/BufferQueueCore.h>
 #include <gui/ISurfaceComposer.h>
diff --git a/libs/gui/aidl/android/gui/IFpsListener.aidl b/libs/gui/aidl/android/gui/IFpsListener.aidl
new file mode 100644
index 0000000..63d333c
--- /dev/null
+++ b/libs/gui/aidl/android/gui/IFpsListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+/** @hide */
+oneway interface IFpsListener {
+
+    // Reports the most recent recorded fps for the tree rooted at this layer
+    void onFpsReported(float fps);
+}
\ No newline at end of file
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 292838e..88cfe4b 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -16,30 +16,26 @@
 
 #pragma once
 
-#include <stdint.h>
-#include <sys/types.h>
-
-#include <binder/IBinder.h>
-#include <binder/IInterface.h>
-
+#include <android/gui/IFpsListener.h>
 #include <android/gui/IScreenCaptureListener.h>
 #include <android/gui/ITransactionTraceListener.h>
+#include <binder/IBinder.h>
+#include <binder/IInterface.h>
 #include <gui/FrameTimelineInfo.h>
 #include <gui/ITransactionCompletedListener.h>
-
 #include <input/Flags.h>
-
 #include <math/vec4.h>
-
+#include <stdint.h>
+#include <sys/types.h>
 #include <ui/ConfigStoreTypes.h>
 #include <ui/DisplayId.h>
+#include <ui/DisplayMode.h>
 #include <ui/DisplayedFrameStats.h>
 #include <ui/FrameStats.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/GraphicTypes.h>
 #include <ui/PixelFormat.h>
 #include <ui/Rotation.h>
-
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
@@ -54,7 +50,6 @@
 struct client_cache_t;
 struct ComposerState;
 struct DisplayCaptureArgs;
-struct DisplayInfo;
 struct DisplayStatInfo;
 struct DisplayState;
 struct InputWindowCommands;
@@ -74,6 +69,8 @@
 
 struct DisplayMode;
 struct DisplayState;
+struct DynamicDisplayInfo;
+struct StaticDisplayInfo;
 
 } // namespace ui
 
@@ -202,56 +199,32 @@
     virtual status_t getDisplayState(const sp<IBinder>& display, ui::DisplayState*) = 0;
 
     /**
-     * Get immutable information about given physical display.
+     * Gets immutable information about given physical display.
      */
-    virtual status_t getDisplayInfo(const sp<IBinder>& display, DisplayInfo*) = 0;
+    virtual status_t getStaticDisplayInfo(const sp<IBinder>& display, ui::StaticDisplayInfo*) = 0;
 
     /**
-     * Get modes supported by given physical display.
+     * Gets dynamic information about given physical display.
      */
-    virtual status_t getDisplayModes(const sp<IBinder>& display, Vector<ui::DisplayMode>*) = 0;
+    virtual status_t getDynamicDisplayInfo(const sp<IBinder>& display, ui::DynamicDisplayInfo*) = 0;
 
-    /**
-     * Get the index into modes returned by getDisplayModes,
-     * corresponding to the active mode.
-     */
-    virtual int getActiveDisplayModeId(const sp<IBinder>& display) = 0;
-
-    virtual status_t getDisplayColorModes(const sp<IBinder>& display,
-            Vector<ui::ColorMode>* outColorModes) = 0;
     virtual status_t getDisplayNativePrimaries(const sp<IBinder>& display,
             ui::DisplayPrimaries& primaries) = 0;
-    virtual ui::ColorMode getActiveColorMode(const sp<IBinder>& display) = 0;
     virtual status_t setActiveColorMode(const sp<IBinder>& display,
             ui::ColorMode colorMode) = 0;
 
     /**
-     * Returns true if the connected display reports support for HDMI 2.1 Auto
-     * Low Latency Mode.
-     * For more information, see the HDMI 2.1 specification.
-     */
-    virtual status_t getAutoLowLatencyModeSupport(const sp<IBinder>& display,
-                                                  bool* outSupport) const = 0;
-
-    /**
      * Switches Auto Low Latency Mode on/off on the connected display, if it is
-     * available. This should only be called if #getAutoLowLatencyMode returns
-     * true.
+     * available. This should only be called if the display supports Auto Low
+     * Latency Mode as reported in #getDynamicDisplayInfo.
      * For more information, see the HDMI 2.1 specification.
      */
     virtual void setAutoLowLatencyMode(const sp<IBinder>& display, bool on) = 0;
 
     /**
-     * Returns true if the connected display reports support for Game Content Type.
-     * For more information, see the HDMI 1.4 specification.
-     */
-    virtual status_t getGameContentTypeSupport(const sp<IBinder>& display,
-                                               bool* outSupport) const = 0;
-
-    /**
      * This will start sending infoframes to the connected display with
-     * ContentType=Game (if on=true). This will switch the disply to Game mode.
-     * This should only be called if #getGameContentTypeSupport returns true.
+     * ContentType=Game (if on=true). This should only be called if the display
+     * Game Content Type as reported in #getDynamicDisplayInfo.
      * For more information, see the HDMI 1.4 specification.
      */
     virtual void setGameContentType(const sp<IBinder>& display, bool on) = 0;
@@ -296,13 +269,6 @@
      */
     virtual status_t getAnimationFrameStats(FrameStats* outStats) const = 0;
 
-    /* Gets the supported HDR capabilities of the given display.
-     *
-     * Requires the ACCESS_SURFACE_FLINGER permission.
-     */
-    virtual status_t getHdrCapabilities(const sp<IBinder>& display,
-            HdrCapabilities* outCapabilities) const = 0;
-
     virtual status_t enableVSyncInjections(bool enable) = 0;
 
     virtual status_t injectVSync(nsecs_t when) = 0;
@@ -383,6 +349,22 @@
      */
     virtual status_t removeRegionSamplingListener(const sp<IRegionSamplingListener>& listener) = 0;
 
+    /* Registers a listener that streams fps updates from SurfaceFlinger.
+     *
+     * The listener will stream fps updates for the layer tree rooted at the layer denoted by the
+     * task ID, i.e., the layer must have the task ID as part of its layer metadata with key
+     * METADATA_TASK_ID. If there is no such layer, then no fps is expected to be reported.
+     *
+     * Multiple listeners may be supported.
+     *
+     * Requires the READ_FRAME_BUFFER permission.
+     */
+    virtual status_t addFpsListener(int32_t taskId, const sp<gui::IFpsListener>& listener) = 0;
+    /*
+     * Removes a listener that was streaming fps updates from SurfaceFlinger.
+     */
+    virtual status_t removeFpsListener(const sp<gui::IFpsListener>& listener) = 0;
+
     /* Sets the refresh rate boundaries for the display.
      *
      * The primary refresh rate range represents display manager's general guidance on the display
@@ -397,20 +379,21 @@
      *
      * defaultMode is used to narrow the list of display modes SurfaceFlinger will consider
      * switching between. Only modes with a mode group and resolution matching defaultMode
-     * will be considered for switching. The defaultMode index corresponds to the list of modes
-     * returned from getDisplayModes().
+     * will be considered for switching. The defaultMode corresponds to an ID of mode in the list
+     * of supported modes returned from getDynamicDisplayInfo().
      */
-    virtual status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, size_t defaultMode,
-                                                bool allowGroupSwitching,
-                                                float primaryRefreshRateMin,
-                                                float primaryRefreshRateMax,
-                                                float appRequestRefreshRateMin,
-                                                float appRequestRefreshRateMax) = 0;
+    virtual status_t setDesiredDisplayModeSpecs(
+            const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode,
+            bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax,
+            float appRequestRefreshRateMin, float appRequestRefreshRateMax) = 0;
 
-    virtual status_t getDesiredDisplayModeSpecs(
-            const sp<IBinder>& displayToken, size_t* outDefaultMode, bool* outAllowGroupSwitching,
-            float* outPrimaryRefreshRateMin, float* outPrimaryRefreshRateMax,
-            float* outAppRequestRefreshRateMin, float* outAppRequestRefreshRateMax) = 0;
+    virtual status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                                ui::DisplayModeId* outDefaultMode,
+                                                bool* outAllowGroupSwitching,
+                                                float* outPrimaryRefreshRateMin,
+                                                float* outPrimaryRefreshRateMax,
+                                                float* outAppRequestRefreshRateMin,
+                                                float* outAppRequestRefreshRateMax) = 0;
     /*
      * Gets whether brightness operations are supported on a display.
      *
@@ -534,7 +517,7 @@
         // Java by ActivityManagerService.
         BOOT_FINISHED = IBinder::FIRST_CALL_TRANSACTION,
         CREATE_CONNECTION,
-        GET_DISPLAY_INFO,
+        GET_STATIC_DISPLAY_INFO,
         CREATE_DISPLAY_EVENT_CONNECTION,
         CREATE_DISPLAY,
         DESTROY_DISPLAY,
@@ -542,8 +525,8 @@
         SET_TRANSACTION_STATE,
         AUTHENTICATE_SURFACE,
         GET_SUPPORTED_FRAME_TIMESTAMPS,
-        GET_DISPLAY_MODES,
-        GET_ACTIVE_DISPLAY_MODE,
+        GET_DISPLAY_MODES,       // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
+        GET_ACTIVE_DISPLAY_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
         GET_DISPLAY_STATE,
         CAPTURE_DISPLAY,
         CAPTURE_LAYERS,
@@ -551,9 +534,9 @@
         GET_ANIMATION_FRAME_STATS,
         SET_POWER_MODE,
         GET_DISPLAY_STATS,
-        GET_HDR_CAPABILITIES,
-        GET_DISPLAY_COLOR_MODES,
-        GET_ACTIVE_COLOR_MODE,
+        GET_HDR_CAPABILITIES,    // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
+        GET_DISPLAY_COLOR_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
+        GET_ACTIVE_COLOR_MODE,   // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
         SET_ACTIVE_COLOR_MODE,
         ENABLE_VSYNC_INJECTIONS,
         INJECT_VSYNC,
@@ -576,9 +559,9 @@
         CAPTURE_DISPLAY_BY_ID,
         NOTIFY_POWER_BOOST,
         SET_GLOBAL_SHADOW_SETTINGS,
-        GET_AUTO_LOW_LATENCY_MODE_SUPPORT,
+        GET_AUTO_LOW_LATENCY_MODE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
         SET_AUTO_LOW_LATENCY_MODE,
-        GET_GAME_CONTENT_TYPE_SUPPORT,
+        GET_GAME_CONTENT_TYPE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
         SET_GAME_CONTENT_TYPE,
         SET_FRAME_RATE,
         ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN,
@@ -586,6 +569,9 @@
         ADD_TRANSACTION_TRACE_LISTENER,
         GET_GPU_CONTEXT_PRIORITY,
         GET_EXTRA_BUFFER_COUNT,
+        GET_DYNAMIC_DISPLAY_INFO,
+        ADD_FPS_LISTENER,
+        REMOVE_FPS_LISTENER,
         // Always append new enum to the end.
     };
 
diff --git a/libs/gui/include/gui/LayerDebugInfo.h b/libs/gui/include/gui/LayerDebugInfo.h
index 66a7b4d..8b7d32c 100644
--- a/libs/gui/include/gui/LayerDebugInfo.h
+++ b/libs/gui/include/gui/LayerDebugInfo.h
@@ -20,6 +20,7 @@
 
 #include <ui/PixelFormat.h>
 #include <ui/Region.h>
+#include <ui/StretchEffect.h>
 
 #include <string>
 #include <math/vec4.h>
@@ -66,6 +67,7 @@
     bool mRefreshPending = false;
     bool mIsOpaque = false;
     bool mContentDirty = false;
+    StretchEffect mStretchEffect = {};
 };
 
 std::string to_string(const LayerDebugInfo& info);
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 8de7574..de88943 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -112,58 +112,42 @@
     static status_t getDisplayState(const sp<IBinder>& display, ui::DisplayState*);
 
     // Get immutable information about given physical display.
-    static status_t getDisplayInfo(const sp<IBinder>& display, DisplayInfo*);
+    static status_t getStaticDisplayInfo(const sp<IBinder>& display, ui::StaticDisplayInfo*);
 
-    // Get modes supported by given physical display.
-    static status_t getDisplayModes(const sp<IBinder>& display, Vector<ui::DisplayMode>*);
+    // Get dynamic information about given physical display.
+    static status_t getDynamicDisplayInfo(const sp<IBinder>& display, ui::DynamicDisplayInfo*);
 
-    // Get the ID of the active DisplayMode, as getDisplayModes index.
-    static int getActiveDisplayModeId(const sp<IBinder>& display);
-
-    // Shorthand for getDisplayModes element at getActiveDisplayModeId index.
+    // Shorthand for the active display mode from getDynamicDisplayInfo().
+    // TODO(b/180391891): Update clients to use getDynamicDisplayInfo and remove this function.
     static status_t getActiveDisplayMode(const sp<IBinder>& display, ui::DisplayMode*);
 
     // Sets the refresh rate boundaries for the display.
-    static status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, size_t defaultMode,
-                                               bool allowGroupSwitching,
-                                               float primaryRefreshRateMin,
-                                               float primaryRefreshRateMax,
-                                               float appRequestRefreshRateMin,
-                                               float appRequestRefreshRateMax);
+    static status_t setDesiredDisplayModeSpecs(
+            const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode,
+            bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax,
+            float appRequestRefreshRateMin, float appRequestRefreshRateMax);
     // Gets the refresh rate boundaries for the display.
     static status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
-                                               size_t* outDefaultMode, bool* outAllowGroupSwitching,
+                                               ui::DisplayModeId* outDefaultMode,
+                                               bool* outAllowGroupSwitching,
                                                float* outPrimaryRefreshRateMin,
                                                float* outPrimaryRefreshRateMax,
                                                float* outAppRequestRefreshRateMin,
                                                float* outAppRequestRefreshRateMax);
 
-    // Gets the list of supported color modes for the given display
-    static status_t getDisplayColorModes(const sp<IBinder>& display,
-            Vector<ui::ColorMode>* outColorModes);
-
     // Get the coordinates of the display's native color primaries
     static status_t getDisplayNativePrimaries(const sp<IBinder>& display,
             ui::DisplayPrimaries& outPrimaries);
 
-    // Gets the active color mode for the given display
-    static ui::ColorMode getActiveColorMode(const sp<IBinder>& display);
-
     // Sets the active color mode for the given display
     static status_t setActiveColorMode(const sp<IBinder>& display,
             ui::ColorMode colorMode);
 
-    // Reports whether the connected display supports Auto Low Latency Mode
-    static bool getAutoLowLatencyModeSupport(const sp<IBinder>& display);
-
     // Switches on/off Auto Low Latency Mode on the connected display. This should only be
     // called if the connected display supports Auto Low Latency Mode as reported by
     // #getAutoLowLatencyModeSupport
     static void setAutoLowLatencyMode(const sp<IBinder>& display, bool on);
 
-    // Reports whether the connected display supports Game content type
-    static bool getGameContentTypeSupport(const sp<IBinder>& display);
-
     // Turns Game mode on/off on the connected display. This should only be called
     // if the display supports Game content type, as reported by #getGameContentTypeSupport
     static void setGameContentType(const sp<IBinder>& display, bool on);
@@ -587,9 +571,6 @@
     static status_t clearAnimationFrameStats();
     static status_t getAnimationFrameStats(FrameStats* outStats);
 
-    static status_t getHdrCapabilities(const sp<IBinder>& display,
-            HdrCapabilities* outCapabilities);
-
     static void setDisplayProjection(const sp<IBinder>& token, ui::Rotation orientation,
                                      const Rect& layerStackRect, const Rect& displayRect);
 
@@ -608,6 +589,8 @@
                                               const sp<IBinder>& stopLayerHandle,
                                               const sp<IRegionSamplingListener>& listener);
     static status_t removeRegionSamplingListener(const sp<IRegionSamplingListener>& listener);
+    static status_t addFpsListener(int32_t taskId, const sp<gui::IFpsListener>& listener);
+    static status_t removeFpsListener(const sp<gui::IFpsListener>& listener);
 
 private:
     virtual void onFirstRef();
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 43909ac..e8fb71d 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -32,6 +32,7 @@
 #include <inttypes.h>
 #include <private/gui/ComposerService.h>
 #include <ui/BufferQueueDefs.h>
+#include <ui/DisplayMode.h>
 #include <ui/Rect.h>
 #include <utils/String8.h>
 
@@ -734,10 +735,11 @@
     }
 
     void setPowerMode(const sp<IBinder>& /*display*/, int /*mode*/) override {}
-    status_t getDisplayInfo(const sp<IBinder>& /*display*/, DisplayInfo*) override {
+    status_t getStaticDisplayInfo(const sp<IBinder>& /*display*/, ui::StaticDisplayInfo*) override {
         return NO_ERROR;
     }
-    status_t getDisplayModes(const sp<IBinder>& /*display*/, Vector<ui::DisplayMode>*) override {
+    status_t getDynamicDisplayInfo(const sp<IBinder>& /*display*/,
+                                   ui::DynamicDisplayInfo*) override {
         return NO_ERROR;
     }
     status_t getDisplayState(const sp<IBinder>& /*display*/, ui::DisplayState*) override {
@@ -745,34 +747,17 @@
     }
     status_t getDisplayStats(const sp<IBinder>& /*display*/,
             DisplayStatInfo* /*stats*/) override { return NO_ERROR; }
-    int getActiveDisplayModeId(const sp<IBinder>& /*display*/) override { return 0; }
-    status_t getDisplayColorModes(const sp<IBinder>& /*display*/,
-            Vector<ColorMode>* /*outColorModes*/) override {
-        return NO_ERROR;
-    }
     status_t getDisplayNativePrimaries(const sp<IBinder>& /*display*/,
             ui::DisplayPrimaries& /*primaries*/) override {
         return NO_ERROR;
     }
-    ColorMode getActiveColorMode(const sp<IBinder>& /*display*/)
-            override {
-        return ColorMode::NATIVE;
-    }
     status_t setActiveColorMode(const sp<IBinder>& /*display*/,
         ColorMode /*colorMode*/) override { return NO_ERROR; }
     status_t captureDisplay(const DisplayCaptureArgs& /* captureArgs */,
                             const sp<IScreenCaptureListener>& /* captureListener */) override {
         return NO_ERROR;
     }
-    status_t getAutoLowLatencyModeSupport(const sp<IBinder>& /*display*/,
-                                          bool* /*outSupport*/) const override {
-        return NO_ERROR;
-    }
     void setAutoLowLatencyMode(const sp<IBinder>& /*display*/, bool /*on*/) override {}
-    status_t getGameContentTypeSupport(const sp<IBinder>& /*display*/,
-                                       bool* /*outSupport*/) const override {
-        return NO_ERROR;
-    }
     void setGameContentType(const sp<IBinder>& /*display*/, bool /*on*/) override {}
     status_t captureDisplay(uint64_t /*displayOrLayerStack*/,
                             const sp<IScreenCaptureListener>& /* captureListener */) override {
@@ -787,10 +772,6 @@
     status_t getAnimationFrameStats(FrameStats* /*outStats*/) const override {
         return NO_ERROR;
     }
-    status_t getHdrCapabilities(const sp<IBinder>& /*display*/,
-            HdrCapabilities* /*outCapabilities*/) const override {
-        return NO_ERROR;
-    }
     status_t enableVSyncInjections(bool /*enable*/) override {
         return NO_ERROR;
     }
@@ -843,7 +824,12 @@
             const sp<IRegionSamplingListener>& /*listener*/) override {
         return NO_ERROR;
     }
-    status_t setDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/, size_t /*defaultMode*/,
+    status_t addFpsListener(int32_t /*taskId*/, const sp<gui::IFpsListener>& /*listener*/) {
+        return NO_ERROR;
+    }
+    status_t removeFpsListener(const sp<gui::IFpsListener>& /*listener*/) { return NO_ERROR; }
+    status_t setDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/,
+                                        ui::DisplayModeId /*defaultMode*/,
                                         bool /*allowGroupSwitching*/,
                                         float /*primaryRefreshRateMin*/,
                                         float /*primaryRefreshRateMax*/,
@@ -852,7 +838,7 @@
         return NO_ERROR;
     }
     status_t getDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/,
-                                        size_t* /*outDefaultMode*/,
+                                        ui::DisplayModeId* /*outDefaultMode*/,
                                         bool* /*outAllowGroupSwitching*/,
                                         float* /*outPrimaryRefreshRateMin*/,
                                         float* /*outPrimaryRefreshRateMax*/,
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 4253610..6f79f4b 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -83,13 +83,13 @@
                 "InputWindow.cpp",
                 "android/FocusRequest.aidl",
                 "android/InputApplicationInfo.aidl",
+                "android/os/BlockUntrustedTouchesMode.aidl",
                 "android/os/IInputConstants.aidl",
+                "android/os/IInputFlinger.aidl",
                 "android/os/InputEventInjectionResult.aidl",
                 "android/os/InputEventInjectionSync.aidl",
-                "android/os/TouchOcclusionMode.aidl",
-                "android/os/BlockUntrustedTouchesMode.aidl",
-                "android/os/IInputFlinger.aidl",
                 "android/os/ISetInputWindowsListener.aidl",
+                "android/os/TouchOcclusionMode.aidl",
             ],
 
             export_shared_lib_headers: ["libbinder"],
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 698cf6e..ffcc1cd 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -170,7 +170,8 @@
         mHasButtonUnderPad(other.mHasButtonUnderPad),
         mHasSensor(other.mHasSensor),
         mMotionRanges(other.mMotionRanges),
-        mSensors(other.mSensors) {}
+        mSensors(other.mSensors),
+        mLights(other.mLights) {}
 
 InputDeviceInfo::~InputDeviceInfo() {
 }
@@ -193,6 +194,7 @@
     mHasSensor = false;
     mMotionRanges.clear();
     mSensors.clear();
+    mLights.clear();
 }
 
 const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange(
@@ -229,6 +231,13 @@
     mSensors.insert_or_assign(info.type, info);
 }
 
+void InputDeviceInfo::addLightInfo(const InputDeviceLightInfo& info) {
+    if (mLights.find(info.id) != mLights.end()) {
+        ALOGW("Light id %d already exists, will be replaced by new light added.", info.id);
+    }
+    mLights.insert_or_assign(info.id, info);
+}
+
 const std::vector<InputDeviceSensorType> InputDeviceInfo::getSensorTypes() {
     std::vector<InputDeviceSensorType> types;
     for (const auto& [type, info] : mSensors) {
@@ -245,4 +254,20 @@
     return &it->second;
 }
 
+const std::vector<int32_t> InputDeviceInfo::getLightIds() {
+    std::vector<int32_t> ids;
+    for (const auto& [id, info] : mLights) {
+        ids.push_back(id);
+    }
+    return ids;
+}
+
+const InputDeviceLightInfo* InputDeviceInfo::getLightInfo(int32_t id) {
+    auto it = mLights.find(id);
+    if (it == mLights.end()) {
+        return nullptr;
+    }
+    return &it->second;
+}
+
 } // namespace android
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 6218fdc..ee2daec 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -34,6 +34,7 @@
 #include <utils/Trace.h>
 
 #include <input/InputTransport.h>
+#include <input/NamedEnum.h>
 
 using android::base::StringPrintf;
 
@@ -576,8 +577,8 @@
     msg.header.type = InputMessage::Type::FOCUS;
     msg.header.seq = seq;
     msg.body.focus.eventId = eventId;
-    msg.body.focus.hasFocus = hasFocus ? 1 : 0;
-    msg.body.focus.inTouchMode = inTouchMode ? 1 : 0;
+    msg.body.focus.hasFocus = hasFocus;
+    msg.body.focus.inTouchMode = inTouchMode;
     return mChannel->sendMessage(&msg);
 }
 
@@ -594,7 +595,7 @@
     msg.header.type = InputMessage::Type::CAPTURE;
     msg.header.seq = seq;
     msg.body.capture.eventId = eventId;
-    msg.body.capture.pointerCaptureEnabled = pointerCaptureEnabled ? 1 : 0;
+    msg.body.capture.pointerCaptureEnabled = pointerCaptureEnabled;
     return mChannel->sendMessage(&msg);
 }
 
@@ -610,11 +611,11 @@
         return result;
     }
     if (msg.header.type != InputMessage::Type::FINISHED) {
-        ALOGE("channel '%s' publisher ~ Received unexpected message of type %d from consumer",
-                mChannel->getName().c_str(), msg.header.type);
+        ALOGE("channel '%s' publisher ~ Received unexpected %s message from consumer",
+              mChannel->getName().c_str(), NamedEnum::string(msg.header.type).c_str());
         return UNKNOWN_ERROR;
     }
-    callback(msg.header.seq, msg.body.finished.handled == 1, msg.body.finished.consumeTime);
+    callback(msg.header.seq, msg.body.finished.handled, msg.body.finished.consumeTime);
     return OK;
 }
 
@@ -753,8 +754,9 @@
             }
 
             case InputMessage::Type::FINISHED: {
-                LOG_ALWAYS_FATAL("Consumed a FINISHED message, which should never be seen by "
-                                 "InputConsumer!");
+                LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by "
+                                 "InputConsumer!",
+                                 NamedEnum::string(mMsg.header.type).c_str());
                 break;
             }
 
@@ -1166,7 +1168,7 @@
     InputMessage msg;
     msg.header.type = InputMessage::Type::FINISHED;
     msg.header.seq = seq;
-    msg.body.finished.handled = handled ? 1 : 0;
+    msg.body.finished.handled = handled;
     msg.body.finished.consumeTime = getConsumeTime(seq);
     status_t result = mChannel->sendMessage(&msg);
     if (result == OK) {
@@ -1226,12 +1228,12 @@
 }
 
 void InputConsumer::initializeFocusEvent(FocusEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.focus.eventId, msg->body.focus.hasFocus == 1,
-                      msg->body.focus.inTouchMode == 1);
+    event->initialize(msg->body.focus.eventId, msg->body.focus.hasFocus,
+                      msg->body.focus.inTouchMode);
 }
 
 void InputConsumer::initializeCaptureEvent(CaptureEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.capture.eventId, msg->body.capture.pointerCaptureEnabled == 1);
+    event->initialize(msg->body.capture.eventId, msg->body.capture.pointerCaptureEnabled);
 }
 
 void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) {
@@ -1299,14 +1301,14 @@
     out = out + "mChannel = " + mChannel->getName() + "\n";
     out = out + "mMsgDeferred: " + toString(mMsgDeferred) + "\n";
     if (mMsgDeferred) {
-        out = out + "mMsg : " + InputMessage::typeToString(mMsg.header.type) + "\n";
+        out = out + "mMsg : " + NamedEnum::string(mMsg.header.type) + "\n";
     }
     out += "Batches:\n";
     for (const Batch& batch : mBatches) {
         out += "    Batch:\n";
         for (const InputMessage& msg : batch.samples) {
             out += android::base::StringPrintf("        Message %" PRIu32 ": %s ", msg.header.seq,
-                                               InputMessage::typeToString(msg.header.type));
+                                               NamedEnum::string(msg.header.type).c_str());
             switch (msg.header.type) {
                 case InputMessage::Type::KEY: {
                     out += android::base::StringPrintf("action=%s keycode=%" PRId32,
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index a886585..8f43608 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -49,6 +49,7 @@
   CHECK_OFFSET(InputMessage::Body::Key, downTime, 88);
 
   CHECK_OFFSET(InputMessage::Body::Motion, eventId, 0);
+  CHECK_OFFSET(InputMessage::Body::Motion, empty1, 4);
   CHECK_OFFSET(InputMessage::Body::Motion, eventTime, 8);
   CHECK_OFFSET(InputMessage::Body::Motion, deviceId, 16);
   CHECK_OFFSET(InputMessage::Body::Motion, source, 20);
@@ -60,6 +61,7 @@
   CHECK_OFFSET(InputMessage::Body::Motion, metaState, 72);
   CHECK_OFFSET(InputMessage::Body::Motion, buttonState, 76);
   CHECK_OFFSET(InputMessage::Body::Motion, classification, 80);
+  CHECK_OFFSET(InputMessage::Body::Motion, empty2, 81);
   CHECK_OFFSET(InputMessage::Body::Motion, edgeFlags, 84);
   CHECK_OFFSET(InputMessage::Body::Motion, downTime, 88);
   CHECK_OFFSET(InputMessage::Body::Motion, dsdx, 96);
@@ -73,16 +75,20 @@
   CHECK_OFFSET(InputMessage::Body::Motion, xCursorPosition, 128);
   CHECK_OFFSET(InputMessage::Body::Motion, yCursorPosition, 132);
   CHECK_OFFSET(InputMessage::Body::Motion, pointerCount, 136);
+  CHECK_OFFSET(InputMessage::Body::Motion, empty3, 140);
   CHECK_OFFSET(InputMessage::Body::Motion, pointers, 144);
 
   CHECK_OFFSET(InputMessage::Body::Focus, eventId, 0);
   CHECK_OFFSET(InputMessage::Body::Focus, hasFocus, 4);
-  CHECK_OFFSET(InputMessage::Body::Focus, inTouchMode, 6);
+  CHECK_OFFSET(InputMessage::Body::Focus, inTouchMode, 5);
+  CHECK_OFFSET(InputMessage::Body::Focus, empty, 6);
 
   CHECK_OFFSET(InputMessage::Body::Capture, eventId, 0);
   CHECK_OFFSET(InputMessage::Body::Capture, pointerCaptureEnabled, 4);
+  CHECK_OFFSET(InputMessage::Body::Capture, empty, 5);
 
-  CHECK_OFFSET(InputMessage::Body::Finished, handled, 4);
+  CHECK_OFFSET(InputMessage::Body::Finished, handled, 0);
+  CHECK_OFFSET(InputMessage::Body::Finished, empty, 1);
   CHECK_OFFSET(InputMessage::Body::Finished, consumeTime, 8);
 }
 
diff --git a/libs/math/Android.bp b/libs/math/Android.bp
index 3cf9f3f..907eb67 100644
--- a/libs/math/Android.bp
+++ b/libs/math/Android.bp
@@ -39,6 +39,7 @@
         "com.android.media.swcodec",
         "com.android.neuralnetworks",
     ],
+
     min_sdk_version: "29",
 
     export_include_dirs: ["include"],
@@ -49,4 +50,23 @@
     }
 }
 
+cc_library_headers {
+    name: "libmath_headers",
+    export_include_dirs: ["include"],
+    host_supported: true,
+    vendor_available: true,
+
+    apex_available: [
+        "//apex_available:anyapex",
+        "//apex_available:platform",
+    ],
+    min_sdk_version: "apex_inherit",
+
+    target:  {
+        windows: {
+            enabled: true,
+        }
+    }
+}
+
 subdirs = ["tests"]
diff --git a/libs/math/OWNERS b/libs/math/OWNERS
index 6fb149a..72d33bc 100644
--- a/libs/math/OWNERS
+++ b/libs/math/OWNERS
@@ -1,6 +1,3 @@
-jaesoo@google.com
-jiyong@google.com
 mathias@google.com
-pawin@google.com
 randolphs@google.com
 romainguy@google.com
diff --git a/libs/math/include/math/HashCombine.h b/libs/math/include/math/HashCombine.h
new file mode 100644
index 0000000..e91b52b
--- /dev/null
+++ b/libs/math/include/math/HashCombine.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <functional>
+
+namespace android {
+static inline void hashCombineSingleHashed(size_t& combinedHash, size_t hash) {
+    combinedHash = 31 * combinedHash + hash;
+}
+
+template<typename T>
+static inline void hashCombineSingle(size_t& combinedHash, const T& val) {
+    hashCombineSingleHashed(combinedHash, std::hash<T>{}(val));
+}
+
+template<typename... Types>
+static inline size_t hashCombine(const Types& ... args) {
+    size_t hash = 0;
+    ( hashCombineSingle(hash, args), ... );
+    return hash;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/math/include/math/TVecHelpers.h b/libs/math/include/math/TVecHelpers.h
index 20f852f..0dac662 100644
--- a/libs/math/include/math/TVecHelpers.h
+++ b/libs/math/include/math/TVecHelpers.h
@@ -19,9 +19,11 @@
 
 #include <math.h>
 #include <stdint.h>
+#include <math/HashCombine.h>
 #include <sys/types.h>
 
 #include <cmath>
+#include <functional>
 #include <limits>
 #include <iostream>
 
@@ -250,6 +252,17 @@
         }
         return r;
     }
+
+    // This isn't strictly a unary operator, but it is a common place shared between both
+    // matrix and vector classes
+    size_t hash() const {
+        VECTOR<T> const& rv(static_cast<VECTOR<T> const&>(*this));
+        size_t hashed = 0;
+        for (size_t i = 0; i < rv.size(); i++) {
+            android::hashCombineSingle(hashed, rv[i]);
+        }
+        return hashed;
+    }
 };
 
 /*
@@ -606,3 +619,16 @@
 // -------------------------------------------------------------------------------------
 }  // namespace details
 }  // namespace android
+
+namespace std {
+    template<template<typename T> class VECTOR, typename T>
+    struct hash<VECTOR<T>> {
+        static constexpr bool IS_VECTOR =
+            std::is_base_of<android::details::TVecUnaryOperators<VECTOR, T>, VECTOR<T>>::value;
+
+        typename std::enable_if<IS_VECTOR, size_t>::type
+        operator()(const VECTOR<T>& v) const {
+            return v.hash();
+        }
+    };
+}
diff --git a/libs/math/include/math/half.h b/libs/math/include/math/half.h
index 617a0ab..5ec9bf7 100644
--- a/libs/math/include/math/half.h
+++ b/libs/math/include/math/half.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <stdint.h>
+#include <functional>
 #include <iosfwd>
 #include <limits>
 #include <type_traits>
@@ -202,6 +203,12 @@
     inline static constexpr type signaling_NaN() noexcept { return android::half(android::half::binary, 0x7dff); }
 };
 
+template<> struct hash<android::half> {
+    size_t operator()(const android::half& half) {
+        return std::hash<float>{}(half);
+    }
+};
+
 } // namespace std
 
 #ifdef LIKELY_DEFINED_LOCAL
diff --git a/libs/math/tests/Android.bp b/libs/math/tests/Android.bp
index 4a7c4dd..14fb72a 100644
--- a/libs/math/tests/Android.bp
+++ b/libs/math/tests/Android.bp
@@ -50,3 +50,10 @@
     static_libs: ["libmath"],
     cflags: ["-Wall", "-Werror"],
 }
+
+cc_test {
+    name: "hashcombine_test",
+    srcs: ["hashcombine_test.cpp"],
+    static_libs: ["libmath"],
+    cflags: ["-Wall", "-Werror"],
+}
diff --git a/libs/math/tests/half_test.cpp b/libs/math/tests/half_test.cpp
index 604072e..a514d98 100644
--- a/libs/math/tests/half_test.cpp
+++ b/libs/math/tests/half_test.cpp
@@ -94,4 +94,13 @@
     EXPECT_EQ(f4.xy, h2);
 }
 
+
+TEST_F(HalfTest, Hash) {
+    float4 f4a(1,2,3,4);
+    float4 f4b(2,2,3,4);
+    half4 h4a(f4a), h4b(f4b);
+
+    EXPECT_NE(std::hash<half4>{}(h4a), std::hash<half4>{}(h4b));
+}
+
 }; // namespace android
diff --git a/libs/math/tests/hashcombine_test.cpp b/libs/math/tests/hashcombine_test.cpp
new file mode 100644
index 0000000..96c6e81
--- /dev/null
+++ b/libs/math/tests/hashcombine_test.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "HashCombineTest"
+
+#include <math.h>
+#include <stdlib.h>
+
+#include <math/half.h>
+#include <math/vec4.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+class HashCombineTest : public testing::Test {
+protected:
+};
+
+TEST_F(HashCombineTest, Basics) {
+    char a = 40;
+    int b = 32;
+    int c = 55;
+    float d = 42.f;
+    float d_ = 42.1f;
+
+    EXPECT_NE(hashCombine(a, b, c, d), hashCombine(a, b, c, d_));
+}
+
+}; // namespace android
diff --git a/libs/nativedisplay/ADisplay.cpp b/libs/nativedisplay/ADisplay.cpp
index c595aa6..6288194 100644
--- a/libs/nativedisplay/ADisplay.cpp
+++ b/libs/nativedisplay/ADisplay.cpp
@@ -16,10 +16,11 @@
 
 #include <apex/display.h>
 #include <gui/SurfaceComposerClient.h>
-#include <ui/DisplayInfo.h>
 #include <ui/DisplayMode.h>
+#include <ui/DynamicDisplayInfo.h>
 #include <ui/GraphicTypes.h>
 #include <ui/PixelFormat.h>
+#include <ui/StaticDisplayInfo.h>
 
 #include <algorithm>
 #include <optional>
@@ -33,6 +34,11 @@
  */
 struct DisplayConfigImpl {
     /**
+     * The ID of the display configuration.
+     */
+    size_t id;
+
+    /**
      * The width in pixels of the display configuration.
      */
     int32_t width{0};
@@ -139,17 +145,19 @@
     for (int i = 0; i < size; ++i) {
         const sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(ids[i]);
 
-        DisplayInfo info;
-        if (const status_t status = SurfaceComposerClient::getDisplayInfo(token, &info);
+        ui::StaticDisplayInfo staticInfo;
+        if (const status_t status = SurfaceComposerClient::getStaticDisplayInfo(token, &staticInfo);
             status != OK) {
             return status;
         }
 
-        Vector<ui::DisplayMode> modes;
-        if (const status_t status = SurfaceComposerClient::getDisplayModes(token, &modes);
+        ui::DynamicDisplayInfo dynamicInfo;
+        if (const status_t status =
+                    SurfaceComposerClient::getDynamicDisplayInfo(token, &dynamicInfo);
             status != OK) {
             return status;
         }
+        const auto& modes = dynamicInfo.supportedDisplayModes;
         if (modes.empty()) {
             return NO_INIT;
         }
@@ -159,9 +167,9 @@
         for (int j = 0; j < modes.size(); ++j) {
             const ui::DisplayMode& mode = modes[j];
             modesPerDisplay[i].emplace_back(
-                    DisplayConfigImpl{mode.resolution.getWidth(), mode.resolution.getHeight(),
-                                      info.density, mode.refreshRate, mode.sfVsyncOffset,
-                                      mode.appVsyncOffset});
+                    DisplayConfigImpl{static_cast<size_t>(mode.id), mode.resolution.getWidth(),
+                                      mode.resolution.getHeight(), staticInfo.density,
+                                      mode.refreshRate, mode.sfVsyncOffset, mode.appVsyncOffset});
         }
     }
 
@@ -257,15 +265,22 @@
     CHECK_NOT_NULL(display);
 
     sp<IBinder> token = getToken(display);
-    const int index = SurfaceComposerClient::getActiveDisplayModeId(token);
-    if (index < 0) {
-        return index;
+    ui::DynamicDisplayInfo info;
+    if (const auto status = SurfaceComposerClient::getDynamicDisplayInfo(token, &info);
+        status != OK) {
+        return status;
     }
 
     DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
+    for (size_t i = 0; i < impl->numConfigs; i++) {
+        auto* config = impl->configs + i;
+        if (config->id == info.activeDisplayModeId) {
+            *outConfig = reinterpret_cast<ADisplayConfig*>(config);
+            return OK;
+        }
+    }
 
-    *outConfig = reinterpret_cast<ADisplayConfig*>(impl->configs + index);
-    return OK;
+    return NAME_NOT_FOUND;
 }
 
 float ADisplayConfig_getDensity(ADisplayConfig* config) {
diff --git a/libs/nativedisplay/include/apex/display.h b/libs/nativedisplay/include/apex/display.h
index a7eaf87..bd94b55 100644
--- a/libs/nativedisplay/include/apex/display.h
+++ b/libs/nativedisplay/include/apex/display.h
@@ -97,6 +97,11 @@
  * such an update is observed, then this method should be recalled to get the
  * new current configuration.
  *
+ * After a subsequent hotplug "connected" event the supported display configs
+ * may change. Then the preloaded display configs will be stale and the
+ * call for current config may return NAME_NOT_FOUND. In this case the client
+ * should release and re-acquire the display handle.
+ *
  * Returns OK on success, -errno on failure.
  */
 int ADisplay_getCurrentConfig(ADisplay* display, ADisplayConfig** outConfig);
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index b2ad22d..79839c1 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -49,7 +49,8 @@
         case RenderEngineType::THREADED:
             ALOGD("Threaded RenderEngine with GLES Backend");
             return renderengine::threaded::RenderEngineThreaded::create(
-                    [args]() { return android::renderengine::gl::GLESRenderEngine::create(args); });
+                    [args]() { return android::renderengine::gl::GLESRenderEngine::create(args); },
+                    renderEngineType);
         case RenderEngineType::SKIA_GL:
             ALOGD("RenderEngine with SkiaGL Backend");
             return renderengine::skia::SkiaGLRenderEngine::create(args);
@@ -66,12 +67,14 @@
                             .setPrecacheToneMapperShaderOnly(args.precacheToneMapperShaderOnly)
                             .setSupportsBackgroundBlur(args.supportsBackgroundBlur)
                             .setContextPriority(args.contextPriority)
-                            .setRenderEngineType(RenderEngineType::SKIA_GL_THREADED)
+                            .setRenderEngineType(renderEngineType)
                             .build();
             ALOGD("Threaded RenderEngine with SkiaGL Backend");
-            return renderengine::threaded::RenderEngineThreaded::create([skiaArgs]() {
-                return android::renderengine::skia::SkiaGLRenderEngine::create(skiaArgs);
-            });
+            return renderengine::threaded::RenderEngineThreaded::create(
+                    [skiaArgs]() {
+                        return android::renderengine::skia::SkiaGLRenderEngine::create(skiaArgs);
+                    },
+                    renderEngineType);
         }
         case RenderEngineType::GLES:
         default:
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index 70ae0b2..397f038 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -379,7 +379,8 @@
 GLESRenderEngine::GLESRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
                                    EGLConfig config, EGLContext ctxt, EGLSurface stub,
                                    EGLContext protectedContext, EGLSurface protectedStub)
-      : mEGLDisplay(display),
+      : RenderEngine(args.renderEngineType),
+        mEGLDisplay(display),
         mEGLConfig(config),
         mEGLContext(ctxt),
         mStubSurface(stub),
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 163a163..572d348 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -87,6 +87,10 @@
 
     static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
 
+    RenderEngine() : RenderEngine(RenderEngineType::GLES) {}
+
+    RenderEngine(RenderEngineType type) : mRenderEngineType(type) {}
+
     virtual ~RenderEngine() = 0;
 
     // ----- BEGIN DEPRECATED INTERFACE -----
@@ -192,8 +196,14 @@
     // also supports background blur.  If false, no blur will be applied when drawing layers.
     virtual bool supportsBackgroundBlur() = 0;
 
+    // Returns the current type of RenderEngine instance that was created.
+    // TODO(b/180767535): This is only implemented to allow for backend-specific behavior, which
+    // we should not allow in general, so remove this.
+    RenderEngineType getRenderEngineType() const { return mRenderEngineType; }
+
 protected:
     friend class threaded::RenderEngineThreaded;
+    const RenderEngineType mRenderEngineType;
 };
 
 struct RenderEngineCreationArgs {
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 327b04c..cbb02a3 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -266,13 +266,13 @@
 SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
                                        EGLContext ctxt, EGLSurface placeholder,
                                        EGLContext protectedContext, EGLSurface protectedPlaceholder)
-      : mEGLDisplay(display),
+      : SkiaRenderEngine(args.renderEngineType),
+        mEGLDisplay(display),
         mEGLContext(ctxt),
         mPlaceholderSurface(placeholder),
         mProtectedEGLContext(protectedContext),
         mProtectedPlaceholderSurface(protectedPlaceholder),
-        mUseColorManagement(args.useColorManagement),
-        mRenderEngineType(args.renderEngineType) {
+        mUseColorManagement(args.useColorManagement) {
     sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface());
     LOG_ALWAYS_FATAL_IF(!glInterface.get());
 
@@ -404,10 +404,6 @@
     return true;
 }
 
-static bool hasUsage(const AHardwareBuffer_Desc& desc, uint64_t usage) {
-    return !!(desc.usage & usage);
-}
-
 static float toDegrees(uint32_t transform) {
     switch (transform) {
         case ui::Transform::ROT_90:
@@ -454,11 +450,6 @@
             sourceTransfer != destTransfer;
 }
 
-static bool needsLinearEffect(const mat4& colorTransform, ui::Dataspace sourceDataspace,
-                              ui::Dataspace destinationDataspace) {
-    return colorTransform != mat4() || needsToneMapping(sourceDataspace, destinationDataspace);
-}
-
 void SkiaGLRenderEngine::cacheExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
     // Only run this if RE is running on its own thread. This way the access to GL
     // operations is guaranteed to be happening on the same thread.
@@ -491,14 +482,19 @@
 sk_sp<SkShader> SkiaGLRenderEngine::createRuntimeEffectShader(sk_sp<SkShader> shader,
                                                               const LayerSettings* layer,
                                                               const DisplaySettings& display,
-                                                              bool undoPremultipliedAlpha) {
+                                                              bool undoPremultipliedAlpha,
+                                                              bool requiresLinearEffect) {
     if (layer->stretchEffect.hasEffect()) {
         // TODO: Implement
     }
-    if (mUseColorManagement &&
-        needsLinearEffect(layer->colorTransform, layer->sourceDataspace, display.outputDataspace)) {
-        LinearEffect effect = LinearEffect{.inputDataspace = layer->sourceDataspace,
-                                           .outputDataspace = display.outputDataspace,
+    if (requiresLinearEffect) {
+        const ui::Dataspace inputDataspace =
+                mUseColorManagement ? layer->sourceDataspace : ui::Dataspace::UNKNOWN;
+        const ui::Dataspace outputDataspace =
+                mUseColorManagement ? display.outputDataspace : ui::Dataspace::UNKNOWN;
+
+        LinearEffect effect = LinearEffect{.inputDataspace = inputDataspace,
+                                           .outputDataspace = outputDataspace,
                                            .undoPremultipliedAlpha = undoPremultipliedAlpha};
 
         auto effectIter = mRuntimeEffects.find(effect);
@@ -556,6 +552,26 @@
     canvas->translate(-display.clip.left, -display.clip.top);
 }
 
+class AutoSaveRestore {
+public:
+    AutoSaveRestore(SkCanvas* canvas) : mCanvas(canvas) { mSaveCount = canvas->save(); }
+    ~AutoSaveRestore() { restore(); }
+    void replace(SkCanvas* canvas) {
+        mCanvas = canvas;
+        mSaveCount = canvas->save();
+    }
+    void restore() {
+        if (mCanvas) {
+            mCanvas->restoreToCount(mSaveCount);
+            mCanvas = nullptr;
+        }
+    }
+
+private:
+    SkCanvas* mCanvas;
+    int mSaveCount;
+};
+
 status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
                                         const std::vector<const LayerSettings*>& layers,
                                         const sp<GraphicBuffer>& buffer,
@@ -586,8 +602,6 @@
     auto& cache = mInProtectedContext ? mProtectedTextureCache : mTextureCache;
     AHardwareBuffer_Desc bufferDesc;
     AHardwareBuffer_describe(buffer->toAHardwareBuffer(), &bufferDesc);
-    LOG_ALWAYS_FATAL_IF(!hasUsage(bufferDesc, AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE),
-                        "missing usage");
 
     std::shared_ptr<AutoBackendTexture::LocalRef> surfaceTextureRef = nullptr;
     if (useFramebufferCache) {
@@ -610,11 +624,10 @@
         }
     }
 
+    const ui::Dataspace dstDataspace =
+            mUseColorManagement ? display.outputDataspace : ui::Dataspace::UNKNOWN;
     sk_sp<SkSurface> dstSurface =
-            surfaceTextureRef->getTexture()->getOrCreateSurface(mUseColorManagement
-                                                                        ? display.outputDataspace
-                                                                        : ui::Dataspace::UNKNOWN,
-                                                                grContext.get());
+            surfaceTextureRef->getTexture()->getOrCreateSurface(dstDataspace, grContext.get());
 
     SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get());
     if (dstCanvas == nullptr) {
@@ -650,7 +663,7 @@
         }
     }
 
-    canvas->save();
+    AutoSaveRestore surfaceAutoSaveRestore(canvas);
     // Clear the entire canvas with a transparent black to prevent ghost images.
     canvas->clear(SK_ColorTRANSPARENT);
     initCanvas(canvas, display);
@@ -671,13 +684,18 @@
         SkPaint paint;
         sk_sp<SkShader> shader =
                 SkShaders::Color(SkColor4f{.fR = 0., .fG = 0., .fB = 0., .fA = 1.0},
-                                 toSkColorSpace(mUseColorManagement ? display.outputDataspace
-                                                                    : ui::Dataspace::UNKNOWN));
+                                 toSkColorSpace(dstDataspace));
         paint.setShader(shader);
         clearRegion.setRects(skRects, numRects);
         canvas->drawRegion(clearRegion, paint);
     }
 
+    // setup color filter if necessary
+    sk_sp<SkColorFilter> displayColorTransform;
+    if (display.colorTransform != mat4()) {
+        displayColorTransform = SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform));
+    }
+
     for (const auto& layer : layers) {
         ATRACE_NAME("DrawLayer");
 
@@ -705,7 +723,7 @@
 
             // assign dstCanvas to canvas and ensure that the canvas state is up to date
             canvas = dstCanvas;
-            canvas->save();
+            surfaceAutoSaveRestore.replace(canvas);
             initCanvas(canvas, display);
 
             LOG_ALWAYS_FATAL_IF(activeSurface->getCanvas()->getSaveCount() !=
@@ -717,7 +735,7 @@
             activeSurface = dstSurface;
         }
 
-        canvas->save();
+        SkAutoCanvasRestore layerAutoSaveRestore(canvas, true);
         if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
             // Record the name of the layer if the capture is running.
             std::stringstream layerSettings;
@@ -765,15 +783,34 @@
             }
         }
 
-        const ui::Dataspace targetDataspace = mUseColorManagement
-                ? (needsLinearEffect(layer->colorTransform, layer->sourceDataspace,
-                                     display.outputDataspace)
-                           // If we need to map to linear space, then mark the source image with the
-                           // same colorspace as the destination surface so that Skia's color
-                           // management is a no-op.
-                           ? display.outputDataspace
-                           : layer->sourceDataspace)
-                : ui::Dataspace::UNKNOWN;
+        // Shadows are assumed to live only on their own layer - it's not valid
+        // to draw the boundary rectangles when there is already a caster shadow
+        // TODO(b/175915334): consider relaxing this restriction to enable more flexible
+        // composition - using a well-defined invalid color is long-term less error-prone.
+        if (layer->shadow.length > 0) {
+            const auto rect = layer->geometry.roundedCornersRadius > 0
+                    ? getSkRect(layer->geometry.roundedCornersCrop)
+                    : bounds;
+            drawShadow(canvas, rect, layer->geometry.roundedCornersRadius, layer->shadow);
+            continue;
+        }
+
+        const bool requiresLinearEffect = layer->colorTransform != mat4() ||
+                (mUseColorManagement &&
+                 needsToneMapping(layer->sourceDataspace, display.outputDataspace));
+
+        // quick abort from drawing the remaining portion of the layer
+        if (layer->alpha == 0 && !requiresLinearEffect &&
+            (!displayColorTransform || displayColorTransform->isAlphaUnchanged())) {
+            continue;
+        }
+
+        // If we need to map to linear space or color management is disabled, then mark the source
+        // image with the same colorspace as the destination surface so that Skia's color
+        // management is a no-op.
+        const ui::Dataspace layerDataspace = (!mUseColorManagement || requiresLinearEffect)
+                ? dstDataspace
+                : layer->sourceDataspace;
 
         SkPaint paint;
         if (layer->source.buffer.buffer) {
@@ -792,7 +829,7 @@
             }
 
             sk_sp<SkImage> image =
-                    imageTextureRef->getTexture()->makeImage(targetDataspace,
+                    imageTextureRef->getTexture()->makeImage(layerDataspace,
                                                              item.usePremultipliedAlpha
                                                                      ? kPremul_SkAlphaType
                                                                      : kUnpremul_SkAlphaType,
@@ -848,12 +885,12 @@
             if (item.isOpaque) {
                 shader = SkShaders::Blend(SkBlendMode::kPlus, shader,
                                           SkShaders::Color(SkColors::kBlack,
-                                                           toSkColorSpace(targetDataspace)));
+                                                           toSkColorSpace(layerDataspace)));
             }
 
-            paint.setShader(
-                    createRuntimeEffectShader(shader, layer, display,
-                                              !item.isOpaque && item.usePremultipliedAlpha));
+            paint.setShader(createRuntimeEffectShader(shader, layer, display,
+                                                      !item.isOpaque && item.usePremultipliedAlpha,
+                                                      requiresLinearEffect));
             paint.setAlphaf(layer->alpha);
         } else {
             ATRACE_NAME("DrawColor");
@@ -862,35 +899,22 @@
                                                                 .fG = color.g,
                                                                 .fB = color.b,
                                                                 .fA = layer->alpha},
-                                                      toSkColorSpace(targetDataspace));
+                                                      toSkColorSpace(layerDataspace));
             paint.setShader(createRuntimeEffectShader(shader, layer, display,
-                                                      /* undoPremultipliedAlpha */ false));
+                                                      /* undoPremultipliedAlpha */ false,
+                                                      requiresLinearEffect));
         }
 
-        sk_sp<SkColorFilter> filter =
-                SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform));
+        paint.setColorFilter(displayColorTransform);
 
-        paint.setColorFilter(filter);
-
-        if (layer->shadow.length > 0) {
-            const auto rect = layer->geometry.roundedCornersRadius > 0
-                    ? getSkRect(layer->geometry.roundedCornersCrop)
-                    : bounds;
-            drawShadow(canvas, rect, layer->geometry.roundedCornersRadius, layer->shadow);
+        if (layer->geometry.roundedCornersRadius > 0) {
+            paint.setAntiAlias(true);
+            canvas->drawRRect(getRoundedRect(layer), paint);
         } else {
-            // Shadows are assumed to live only on their own layer - it's not valid
-            // to draw the boundary retangles when there is already a caster shadow
-            // TODO(b/175915334): consider relaxing this restriction to enable more flexible
-            // composition - using a well-defined invalid color is long-term less error-prone.
-            // Push the clipRRect onto the clip stack. Draw the image. Pop the clip.
-            if (layer->geometry.roundedCornersRadius > 0) {
-                canvas->clipRRect(getRoundedRect(layer), true);
-            }
             canvas->drawRect(bounds, paint);
         }
-        canvas->restore();
     }
-    canvas->restore();
+    surfaceAutoSaveRestore.restore();
     mCapture->endCapture();
     {
         ATRACE_NAME("flush surface");
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index 5779ae6..1c3a633 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -92,11 +92,12 @@
     void initCanvas(SkCanvas* canvas, const DisplaySettings& display);
     void drawShadow(SkCanvas* canvas, const SkRect& casterRect, float casterCornerRadius,
                     const ShadowSettings& shadowSettings);
-    // If mUseColorManagement is correct and layer needsLinearEffect, it returns a linear runtime
-    // shader. Otherwise it returns the input shader.
+    // If requiresLinearEffect is true or the layer has a stretchEffect a new shader is returned.
+    // Otherwise it returns the input shader.
     sk_sp<SkShader> createRuntimeEffectShader(sk_sp<SkShader> shader, const LayerSettings* layer,
                                               const DisplaySettings& display,
-                                              bool undoPremultipliedAlpha);
+                                              bool undoPremultipliedAlpha,
+                                              bool requiresLinearEffect);
 
     EGLDisplay mEGLDisplay;
     EGLContext mEGLContext;
@@ -129,10 +130,6 @@
     bool mInProtectedContext = false;
     // Object to capture commands send to Skia.
     std::unique_ptr<SkiaCapture> mCapture;
-
-    // Keep this information as a local variable to determine whether the access of the GL
-    // operations is working on the same threads.
-    const RenderEngineType mRenderEngineType = RenderEngineType::SKIA_GL;
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index 12b8586..79a1040 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -36,6 +36,7 @@
 class SkiaRenderEngine : public RenderEngine {
 public:
     static std::unique_ptr<SkiaRenderEngine> create(const RenderEngineCreationArgs& args);
+    SkiaRenderEngine(RenderEngineType type) : RenderEngine(type) {}
     ~SkiaRenderEngine() override {}
 
     virtual void primeCache() const override{};
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index 08b672a..63aa4c8 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -32,7 +32,8 @@
 
     void SetUp() override {
         mThreadedRE = renderengine::threaded::RenderEngineThreaded::create(
-                [this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); });
+                [this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); },
+                renderengine::RenderEngine::RenderEngineType::THREADED);
     }
 
     std::unique_ptr<renderengine::threaded::RenderEngineThreaded> mThreadedRE;
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index f448135..7c7d165 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -34,11 +34,13 @@
 namespace renderengine {
 namespace threaded {
 
-std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(CreateInstanceFactory factory) {
-    return std::make_unique<RenderEngineThreaded>(std::move(factory));
+std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(CreateInstanceFactory factory,
+                                                                   RenderEngineType type) {
+    return std::make_unique<RenderEngineThreaded>(std::move(factory), type);
 }
 
-RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory) {
+RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type)
+      : RenderEngine(type) {
     ATRACE_CALL();
 
     std::lock_guard lockThread(mThreadMutex);
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index 8279cbc..d362e17 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -37,9 +37,10 @@
  */
 class RenderEngineThreaded : public RenderEngine {
 public:
-    static std::unique_ptr<RenderEngineThreaded> create(CreateInstanceFactory factory);
+    static std::unique_ptr<RenderEngineThreaded> create(CreateInstanceFactory factory,
+                                                        RenderEngineType type);
 
-    RenderEngineThreaded(CreateInstanceFactory factory);
+    RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type);
     ~RenderEngineThreaded() override;
     void primeCache() const override;
 
diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp
index a6b0aaf..a6cacad 100644
--- a/libs/sensor/ISensorServer.cpp
+++ b/libs/sensor/ISensorServer.cpp
@@ -91,13 +91,14 @@
     }
 
     virtual sp<ISensorEventConnection> createSensorEventConnection(const String8& packageName,
-             int mode, const String16& opPackageName)
+             int mode, const String16& opPackageName, const String16& attributionTag)
     {
         Parcel data, reply;
         data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor());
         data.writeString8(packageName);
         data.writeInt32(mode);
         data.writeString16(opPackageName);
+        data.writeString16(attributionTag);
         remote()->transact(CREATE_SENSOR_EVENT_CONNECTION, data, &reply);
         return interface_cast<ISensorEventConnection>(reply.readStrongBinder());
     }
@@ -170,8 +171,9 @@
             String8 packageName = data.readString8();
             int32_t mode = data.readInt32();
             const String16& opPackageName = data.readString16();
+            const String16& attributionTag = data.readString16();
             sp<ISensorEventConnection> connection(createSensorEventConnection(packageName, mode,
-                    opPackageName));
+                    opPackageName, attributionTag));
             reply->writeStrongBinder(IInterface::asBinder(connection));
             return NO_ERROR;
         }
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index a4a5d13..62f4b4e 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -225,13 +225,14 @@
     return nullptr;
 }
 
-sp<SensorEventQueue> SensorManager::createEventQueue(String8 packageName, int mode) {
+sp<SensorEventQueue> SensorManager::createEventQueue(
+    String8 packageName, int mode, String16 attributionTag) {
     sp<SensorEventQueue> queue;
 
     Mutex::Autolock _l(mLock);
     while (assertStateLocked() == NO_ERROR) {
-        sp<ISensorEventConnection> connection =
-                mSensorServer->createSensorEventConnection(packageName, mode, mOpPackageName);
+        sp<ISensorEventConnection> connection = mSensorServer->createSensorEventConnection(
+            packageName, mode, mOpPackageName, attributionTag);
         if (connection == nullptr) {
             // SensorService just died or the app doesn't have required permissions.
             ALOGE("createEventQueue: connection is NULL.");
diff --git a/libs/sensor/include/sensor/ISensorServer.h b/libs/sensor/include/sensor/ISensorServer.h
index 402678f..ce5c672 100644
--- a/libs/sensor/include/sensor/ISensorServer.h
+++ b/libs/sensor/include/sensor/ISensorServer.h
@@ -45,7 +45,7 @@
     virtual Vector<Sensor> getDynamicSensorList(const String16& opPackageName) = 0;
 
     virtual sp<ISensorEventConnection> createSensorEventConnection(const String8& packageName,
-             int mode, const String16& opPackageName) = 0;
+             int mode, const String16& opPackageName, const String16& attributionTag) = 0;
     virtual int32_t isDataInjectionEnabled() = 0;
 
     virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName,
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index f09c9c6..09ac7ed 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -59,7 +59,8 @@
     ssize_t getSensorList(Sensor const* const** list);
     ssize_t getDynamicSensorList(Vector<Sensor>& list);
     Sensor const* getDefaultSensor(int type);
-    sp<SensorEventQueue> createEventQueue(String8 packageName = String8(""), int mode = 0);
+    sp<SensorEventQueue> createEventQueue(
+        String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16(""));
     bool isDataInjectionEnabled();
     int createDirectChannel(size_t size, int channelType, const native_handle_t *channelData);
     void destroyDirectChannel(int channelNativeHandle);
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 3b024be..74d17ce 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -94,6 +94,7 @@
         "libarect",
         "libmath",
     ],
+
 }
 
 cc_library_shared {
@@ -121,7 +122,8 @@
     srcs: [
         "DebugUtils.cpp",
         "DeviceProductInfo.cpp",
-        "DisplayInfo.cpp",
+        "DisplayMode.cpp",
+        "DynamicDisplayInfo.cpp",
         "Fence.cpp",
         "FenceTime.cpp",
         "FrameStats.cpp",
@@ -136,6 +138,7 @@
         "PixelFormat.cpp",
         "PublicFormat.cpp",
         "Size.cpp",
+        "StaticDisplayInfo.cpp",
     ],
 
     include_dirs: [
@@ -236,9 +239,11 @@
     },
     header_libs: [
         "libnativewindow_headers",
+        "libmath_headers",
     ],
     export_header_lib_headers: [
         "libnativewindow_headers",
+        "libmath_headers",
     ],
     min_sdk_version: "29",
 }
diff --git a/libs/ui/DisplayMode.cpp b/libs/ui/DisplayMode.cpp
new file mode 100644
index 0000000..cf05dbf
--- /dev/null
+++ b/libs/ui/DisplayMode.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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 <ui/DisplayMode.h>
+
+#include <cstdint>
+
+#include <ui/FlattenableHelpers.h>
+
+#define RETURN_IF_ERROR(op) \
+    if (const status_t status = (op); status != OK) return status;
+
+namespace android::ui {
+
+size_t DisplayMode::getFlattenedSize() const {
+    return FlattenableHelpers::getFlattenedSize(id) +
+            FlattenableHelpers::getFlattenedSize(resolution) +
+            FlattenableHelpers::getFlattenedSize(xDpi) +
+            FlattenableHelpers::getFlattenedSize(yDpi) +
+            FlattenableHelpers::getFlattenedSize(refreshRate) +
+            FlattenableHelpers::getFlattenedSize(appVsyncOffset) +
+            FlattenableHelpers::getFlattenedSize(sfVsyncOffset) +
+            FlattenableHelpers::getFlattenedSize(presentationDeadline) +
+            FlattenableHelpers::getFlattenedSize(group);
+}
+
+status_t DisplayMode::flatten(void* buffer, size_t size) const {
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, id));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, resolution));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, xDpi));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, yDpi));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, refreshRate));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, appVsyncOffset));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, sfVsyncOffset));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, presentationDeadline));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, group));
+    return OK;
+}
+
+status_t DisplayMode::unflatten(const void* buffer, size_t size) {
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &id));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &resolution));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &xDpi));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &yDpi));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &refreshRate));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &appVsyncOffset));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &sfVsyncOffset));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &presentationDeadline));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &group));
+    return OK;
+}
+
+} // namespace android::ui
diff --git a/libs/ui/DynamicDisplayInfo.cpp b/libs/ui/DynamicDisplayInfo.cpp
new file mode 100644
index 0000000..d5c4ef0
--- /dev/null
+++ b/libs/ui/DynamicDisplayInfo.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 <ui/DynamicDisplayInfo.h>
+
+#include <cstdint>
+
+#include <ui/FlattenableHelpers.h>
+
+#define RETURN_IF_ERROR(op) \
+    if (const status_t status = (op); status != OK) return status;
+
+namespace android::ui {
+
+std::optional<ui::DisplayMode> DynamicDisplayInfo::getActiveDisplayMode() const {
+    for (const auto& currMode : supportedDisplayModes) {
+        if (currMode.id == activeDisplayModeId) {
+            return currMode;
+        }
+    }
+    return {};
+}
+
+size_t DynamicDisplayInfo::getFlattenedSize() const {
+    return FlattenableHelpers::getFlattenedSize(supportedDisplayModes) +
+            FlattenableHelpers::getFlattenedSize(activeDisplayModeId) +
+            FlattenableHelpers::getFlattenedSize(supportedColorModes) +
+            FlattenableHelpers::getFlattenedSize(activeColorMode) +
+            FlattenableHelpers::getFlattenedSize(hdrCapabilities) +
+            FlattenableHelpers::getFlattenedSize(autoLowLatencyModeSupported) +
+            FlattenableHelpers::getFlattenedSize(gameContentTypeSupported);
+}
+
+status_t DynamicDisplayInfo::flatten(void* buffer, size_t size) const {
+    if (size < getFlattenedSize()) {
+        return NO_MEMORY;
+    }
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, supportedDisplayModes));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, activeDisplayModeId));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, supportedColorModes));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, activeColorMode));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, hdrCapabilities));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, autoLowLatencyModeSupported));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, gameContentTypeSupported));
+    return OK;
+}
+
+status_t DynamicDisplayInfo::unflatten(const void* buffer, size_t size) {
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &supportedDisplayModes));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &activeDisplayModeId));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &supportedColorModes));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &activeColorMode));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &hdrCapabilities));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &autoLowLatencyModeSupported));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &gameContentTypeSupported));
+    return OK;
+}
+
+} // namespace android::ui
diff --git a/libs/ui/HdrCapabilities.cpp b/libs/ui/HdrCapabilities.cpp
index a5b3e89..aec2fac 100644
--- a/libs/ui/HdrCapabilities.cpp
+++ b/libs/ui/HdrCapabilities.cpp
@@ -23,10 +23,6 @@
 #pragma clang diagnostic ignored "-Wundefined-reinterpret-cast"
 #endif
 
-HdrCapabilities::~HdrCapabilities() = default;
-HdrCapabilities::HdrCapabilities(HdrCapabilities&& other) noexcept = default;
-HdrCapabilities& HdrCapabilities::operator=(HdrCapabilities&& other) noexcept = default;
-
 size_t HdrCapabilities::getFlattenedSize() const {
     return  sizeof(mMaxLuminance) +
             sizeof(mMaxAverageLuminance) +
diff --git a/libs/ui/OWNERS b/libs/ui/OWNERS
index 5110a6c..a0b5fe7 100644
--- a/libs/ui/OWNERS
+++ b/libs/ui/OWNERS
@@ -1,3 +1,4 @@
+adyabr@google.com
 alecmouri@google.com
 chrisforbes@google.com
 jreck@google.com
diff --git a/libs/ui/DisplayInfo.cpp b/libs/ui/StaticDisplayInfo.cpp
similarity index 86%
rename from libs/ui/DisplayInfo.cpp
rename to libs/ui/StaticDisplayInfo.cpp
index 73a78af..b66b281 100644
--- a/libs/ui/DisplayInfo.cpp
+++ b/libs/ui/StaticDisplayInfo.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <ui/DisplayInfo.h>
+#include <ui/StaticDisplayInfo.h>
 
 #include <cstdint>
 
@@ -23,16 +23,16 @@
 #define RETURN_IF_ERROR(op) \
     if (const status_t status = (op); status != OK) return status;
 
-namespace android {
+namespace android::ui {
 
-size_t DisplayInfo::getFlattenedSize() const {
+size_t StaticDisplayInfo::getFlattenedSize() const {
     return FlattenableHelpers::getFlattenedSize(connectionType) +
             FlattenableHelpers::getFlattenedSize(density) +
             FlattenableHelpers::getFlattenedSize(secure) +
             FlattenableHelpers::getFlattenedSize(deviceProductInfo);
 }
 
-status_t DisplayInfo::flatten(void* buffer, size_t size) const {
+status_t StaticDisplayInfo::flatten(void* buffer, size_t size) const {
     if (size < getFlattenedSize()) {
         return NO_MEMORY;
     }
@@ -43,7 +43,7 @@
     return OK;
 }
 
-status_t DisplayInfo::unflatten(void const* buffer, size_t size) {
+status_t StaticDisplayInfo::unflatten(void const* buffer, size_t size) {
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &connectionType));
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &density));
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &secure));
@@ -51,4 +51,4 @@
     return OK;
 }
 
-} // namespace android
+} // namespace android::ui
diff --git a/libs/ui/include/ui/DisplayMode.h b/libs/ui/include/ui/DisplayMode.h
index 145d7ef..56f68e7 100644
--- a/libs/ui/include/ui/DisplayMode.h
+++ b/libs/ui/include/ui/DisplayMode.h
@@ -16,15 +16,21 @@
 
 #pragma once
 
+#include <cstdint>
 #include <type_traits>
 
 #include <ui/Size.h>
+#include <utils/Flattenable.h>
 #include <utils/Timers.h>
 
 namespace android::ui {
 
+// This value is going to be serialized over binder so we prefer a fixed width type.
+using DisplayModeId = int32_t;
+
 // Mode supported by physical display.
-struct DisplayMode {
+struct DisplayMode : LightFlattenable<DisplayMode> {
+    DisplayModeId id;
     ui::Size resolution;
     float xDpi = 0;
     float yDpi = 0;
@@ -33,9 +39,12 @@
     nsecs_t appVsyncOffset = 0;
     nsecs_t sfVsyncOffset = 0;
     nsecs_t presentationDeadline = 0;
-    int group = -1;
-};
+    int32_t group = -1;
 
-static_assert(std::is_trivially_copyable_v<DisplayMode>);
+    bool isFixedSize() const { return false; }
+    size_t getFlattenedSize() const;
+    status_t flatten(void* buffer, size_t size) const;
+    status_t unflatten(const void* buffer, size_t size);
+};
 
 } // namespace android::ui
diff --git a/libs/ui/include/ui/DynamicDisplayInfo.h b/libs/ui/include/ui/DynamicDisplayInfo.h
new file mode 100644
index 0000000..a4c2f71
--- /dev/null
+++ b/libs/ui/include/ui/DynamicDisplayInfo.h
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "DisplayMode.h"
+
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+#include <ui/GraphicTypes.h>
+#include <ui/HdrCapabilities.h>
+#include <utils/Flattenable.h>
+
+namespace android::ui {
+
+// Information about a physical display which may change on hotplug reconnect.
+struct DynamicDisplayInfo : LightFlattenable<DynamicDisplayInfo> {
+    std::vector<ui::DisplayMode> supportedDisplayModes;
+
+    // This struct is going to be serialized over binder, so
+    // we can't use size_t because it may have different width
+    // in the client process.
+    int32_t activeDisplayModeId;
+
+    std::vector<ui::ColorMode> supportedColorModes;
+    ui::ColorMode activeColorMode;
+    HdrCapabilities hdrCapabilities;
+
+    // True if the display reports support for HDMI 2.1 Auto Low Latency Mode.
+    // For more information, see the HDMI 2.1 specification.
+    bool autoLowLatencyModeSupported;
+
+    // True if the display reports support for Game Content Type.
+    // For more information, see the HDMI 1.4 specification.
+    bool gameContentTypeSupported;
+
+    std::optional<ui::DisplayMode> getActiveDisplayMode() const;
+
+    bool isFixedSize() const { return false; }
+    size_t getFlattenedSize() const;
+    status_t flatten(void* buffer, size_t size) const;
+    status_t unflatten(const void* buffer, size_t size);
+};
+
+} // namespace android::ui
diff --git a/libs/ui/include/ui/FloatRect.h b/libs/ui/include/ui/FloatRect.h
index bec2552..4c9c7b7 100644
--- a/libs/ui/include/ui/FloatRect.h
+++ b/libs/ui/include/ui/FloatRect.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <math/HashCombine.h>
 #include <ostream>
 
 namespace android {
@@ -48,6 +49,8 @@
     float top = 0.0f;
     float right = 0.0f;
     float bottom = 0.0f;
+
+    constexpr bool isEmpty() const { return !(left < right && top < bottom); }
 };
 
 inline bool operator==(const FloatRect& a, const FloatRect& b) {
@@ -60,3 +63,13 @@
 }
 
 }  // namespace android
+
+namespace std {
+
+template <>
+struct hash<android::FloatRect> {
+    size_t operator()(const android::FloatRect& rect) const {
+        return android::hashCombine(rect.left, rect.top, rect.right, rect.bottom);
+    }
+};
+} // namespace std
diff --git a/libs/ui/include/ui/HdrCapabilities.h b/libs/ui/include/ui/HdrCapabilities.h
index 65ac26c..813adde 100644
--- a/libs/ui/include/ui/HdrCapabilities.h
+++ b/libs/ui/include/ui/HdrCapabilities.h
@@ -36,18 +36,12 @@
         mMaxAverageLuminance(maxAverageLuminance),
         mMinLuminance(minLuminance) {}
 
-    // Make this move-constructable and move-assignable
-    HdrCapabilities(HdrCapabilities&& other) noexcept;
-    HdrCapabilities& operator=(HdrCapabilities&& other) noexcept;
-
     HdrCapabilities()
       : mSupportedHdrTypes(),
         mMaxLuminance(-1.0f),
         mMaxAverageLuminance(-1.0f),
         mMinLuminance(-1.0f) {}
 
-    ~HdrCapabilities();
-
     const std::vector<ui::Hdr>& getSupportedHdrTypes() const {
         return mSupportedHdrTypes;
     }
diff --git a/libs/ui/include/ui/Rect.h b/libs/ui/include/ui/Rect.h
index 58323e5..9e24a07 100644
--- a/libs/ui/include/ui/Rect.h
+++ b/libs/ui/include/ui/Rect.h
@@ -24,6 +24,7 @@
 #include <utils/Log.h>
 #include <utils/TypeHelpers.h>
 
+#include <math/HashCombine.h>
 #include <ui/FloatRect.h>
 #include <ui/Point.h>
 #include <ui/Size.h>
@@ -234,4 +235,13 @@
 
 }; // namespace android
 
+namespace std {
+template <>
+struct hash<android::Rect> {
+    size_t operator()(const android::Rect& rect) const {
+        return android::hashCombine(rect.left, rect.top, rect.right, rect.bottom);
+    }
+};
+} // namespace std
+
 #endif // ANDROID_UI_RECT
diff --git a/libs/ui/include/ui/Region.h b/libs/ui/include/ui/Region.h
index 6bb7b8d..927c334 100644
--- a/libs/ui/include/ui/Region.h
+++ b/libs/ui/include/ui/Region.h
@@ -21,6 +21,7 @@
 #include <sys/types.h>
 #include <ostream>
 
+#include <math/HashCombine.h>
 #include <ui/Rect.h>
 #include <utils/Flattenable.h>
 
@@ -234,4 +235,17 @@
 // ---------------------------------------------------------------------------
 }; // namespace android
 
+namespace std {
+template <>
+struct hash<android::Region> {
+    size_t operator()(const android::Region& region) const {
+        size_t hash = 0;
+        for (const android::Rect& rect : region) {
+            android::hashCombineSingle(hash, rect);
+        }
+        return hash;
+    }
+};
+} // namespace std
+
 #endif // ANDROID_UI_REGION_H
diff --git a/libs/ui/include/ui/DisplayInfo.h b/libs/ui/include/ui/StaticDisplayInfo.h
similarity index 90%
rename from libs/ui/include/ui/DisplayInfo.h
rename to libs/ui/include/ui/StaticDisplayInfo.h
index 03e0a38..e86ca29 100644
--- a/libs/ui/include/ui/DisplayInfo.h
+++ b/libs/ui/include/ui/StaticDisplayInfo.h
@@ -17,17 +17,16 @@
 #pragma once
 
 #include <optional>
-#include <type_traits>
 
 #include <ui/DeviceProductInfo.h>
 #include <utils/Flattenable.h>
 
-namespace android {
+namespace android::ui {
 
 enum class DisplayConnectionType { Internal, External };
 
 // Immutable information about physical display.
-struct DisplayInfo : LightFlattenable<DisplayInfo> {
+struct StaticDisplayInfo : LightFlattenable<StaticDisplayInfo> {
     DisplayConnectionType connectionType = DisplayConnectionType::Internal;
     float density = 0.f;
     bool secure = false;
@@ -39,4 +38,4 @@
     status_t unflatten(void const* buffer, size_t size);
 };
 
-} // namespace android
+} // namespace android::ui
diff --git a/libs/ui/include/ui/StretchEffect.h b/libs/ui/include/ui/StretchEffect.h
index 1d2460c..0803df3 100644
--- a/libs/ui/include/ui/StretchEffect.h
+++ b/libs/ui/include/ui/StretchEffect.h
@@ -19,6 +19,7 @@
 #include <utils/Flattenable.h>
 #include "FloatRect.h"
 
+#include <math.h>
 #include <type_traits>
 
 namespace android {
@@ -45,7 +46,7 @@
 
     void sanitize() {
         // If the area is empty, or the max amount is zero, then reset back to defaults
-        if (area.bottom >= area.top || area.left >= area.right || isZero(maxAmount)) {
+        if (area.isEmpty() || isZero(maxAmount)) {
             *this = StretchEffect{};
         }
     }
diff --git a/libs/ui/include_private/ui/FlattenableHelpers.h b/libs/ui/include_private/ui/FlattenableHelpers.h
index 8e316d8..378f37f 100644
--- a/libs/ui/include_private/ui/FlattenableHelpers.h
+++ b/libs/ui/include_private/ui/FlattenableHelpers.h
@@ -29,20 +29,29 @@
 namespace android {
 
 struct FlattenableHelpers {
-    // Helpers for reading and writing POD structures
-    template <class T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>>
+    // Helpers for reading and writing POD structures which are not LightFlattenable.
+    template <class T,
+              typename = std::enable_if_t<
+                      std::conjunction_v<std::is_trivially_copyable<T>,
+                                         std::negation<std::is_base_of<LightFlattenable<T>, T>>>>>
     static constexpr size_t getFlattenedSize(const T&) {
         return sizeof(T);
     }
 
-    template <class T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>>
+    template <class T,
+              typename = std::enable_if_t<
+                      std::conjunction_v<std::is_trivially_copyable<T>,
+                                         std::negation<std::is_base_of<LightFlattenable<T>, T>>>>>
     static status_t flatten(void** buffer, size_t* size, const T& value) {
         if (*size < sizeof(T)) return NO_MEMORY;
         FlattenableUtils::write(*buffer, *size, value);
         return OK;
     }
 
-    template <class T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>>
+    template <class T,
+              typename = std::enable_if_t<
+                      std::conjunction_v<std::is_trivially_copyable<T>,
+                                         std::negation<std::is_base_of<LightFlattenable<T>, T>>>>>
     static status_t unflatten(const void** buffer, size_t* size, T* value) {
         if (*size < sizeof(T)) return NO_MEMORY;
         FlattenableUtils::read(*buffer, *size, *value);
diff --git a/libs/ui/include_vndk/ui/DisplayInfo.h b/libs/ui/include_vndk/ui/DisplayInfo.h
deleted file mode 120000
index 75f14cf..0000000
--- a/libs/ui/include_vndk/ui/DisplayInfo.h
+++ /dev/null
@@ -1 +0,0 @@
-../../include/ui/DisplayInfo.h
\ No newline at end of file
diff --git a/libs/ui/include_vndk/ui/StaticDisplayInfo.h b/libs/ui/include_vndk/ui/StaticDisplayInfo.h
new file mode 120000
index 0000000..541a7a3
--- /dev/null
+++ b/libs/ui/include_vndk/ui/StaticDisplayInfo.h
@@ -0,0 +1 @@
+../../include/ui/StaticDisplayInfo.h
\ No newline at end of file
diff --git a/libs/ui/tests/FlattenableHelpers_test.cpp b/libs/ui/tests/FlattenableHelpers_test.cpp
index db32bc7..44e20b5 100644
--- a/libs/ui/tests/FlattenableHelpers_test.cpp
+++ b/libs/ui/tests/FlattenableHelpers_test.cpp
@@ -42,7 +42,7 @@
     }
 
     status_t unflatten(void const* buffer, size_t size) {
-        int value;
+        int32_t value;
         FlattenableUtils::read(buffer, size, value);
         ptr = std::make_unique<int32_t>(value);
         return OK;
@@ -132,5 +132,66 @@
     ASSERT_FALSE(valueRead.has_value());
 }
 
+// If a struct is both trivially copyable and light flattenable we should treat it
+// as LigthFlattenable.
+TEST_F(FlattenableHelpersTest, TriviallyCopyableAndLightFlattenableIsFlattenedAsLightFlattenable) {
+    static constexpr int32_t kSizeTag = 1234567;
+    static constexpr int32_t kFlattenTag = 987654;
+    static constexpr int32_t kUnflattenTag = 5926582;
+
+    struct LightFlattenableAndTriviallyCopyable
+          : LightFlattenable<LightFlattenableAndTriviallyCopyable> {
+        int32_t value;
+
+        bool isFixedSize() const { return true; }
+        size_t getFlattenedSize() const { return kSizeTag; }
+
+        status_t flatten(void* buffer, size_t size) const {
+            FlattenableUtils::write(buffer, size, kFlattenTag);
+            return OK;
+        }
+
+        status_t unflatten(void const*, size_t) {
+            value = kUnflattenTag;
+            return OK;
+        }
+    };
+
+    {
+        // Verify that getFlattenedSize uses the LightFlattenable overload
+        LightFlattenableAndTriviallyCopyable foo;
+        EXPECT_EQ(kSizeTag, FlattenableHelpers::getFlattenedSize(foo));
+    }
+
+    {
+        // Verify that flatten uses the LightFlattenable overload
+        std::vector<int8_t> buffer(sizeof(int32_t));
+        auto rawBuffer = reinterpret_cast<void*>(buffer.data());
+        size_t size = buffer.size();
+        LightFlattenableAndTriviallyCopyable foo;
+        ASSERT_EQ(OK, FlattenableHelpers::flatten(&rawBuffer, &size, foo));
+
+        auto rawReadBuffer = reinterpret_cast<const void*>(buffer.data());
+        int32_t value;
+        FlattenableHelpers::unflatten(&rawReadBuffer, &size, &value);
+        EXPECT_EQ(kFlattenTag, value);
+    }
+
+    {
+        // Verify that unflatten uses the LightFlattenable overload
+        std::vector<int8_t> buffer(sizeof(int32_t));
+        auto rawBuffer = reinterpret_cast<void*>(buffer.data());
+        size_t size = buffer.size();
+        int32_t value = 4;
+        ASSERT_EQ(OK, FlattenableHelpers::flatten(&rawBuffer, &size, value));
+
+        auto rawReadBuffer = reinterpret_cast<const void*>(buffer.data());
+
+        LightFlattenableAndTriviallyCopyable foo;
+        FlattenableHelpers::unflatten(&rawReadBuffer, &size, &foo);
+        EXPECT_EQ(kUnflattenTag, foo.value);
+    }
+}
+
 } // namespace
 } // namespace android
diff --git a/libs/ui/tests/Rect_test.cpp b/libs/ui/tests/Rect_test.cpp
index 5499a5b..9cc36bb 100644
--- a/libs/ui/tests/Rect_test.cpp
+++ b/libs/ui/tests/Rect_test.cpp
@@ -259,4 +259,33 @@
     EXPECT_EQ(FloatRect(10.f, 20.f, 50.f, 60.f), floatRect);
 }
 
+TEST(RectTest, RectHash) {
+    const std::vector<Rect> rects = {
+            Rect(10, 20, 50, 60), Rect(11, 20, 50, 60), Rect(11, 21, 50, 60),
+            Rect(11, 21, 51, 60), Rect(11, 21, 51, 61),
+    };
+
+    for (const auto& a : rects) {
+        for (const auto& b : rects) {
+            const bool hashEq = std::hash<Rect>{}(a) == std::hash<Rect>{}(b);
+            EXPECT_EQ(a == b, hashEq);
+        }
+    }
+}
+
+TEST(RectTest, FloatRectHash) {
+    const std::vector<FloatRect> floatRects = {
+            Rect(10, 20, 50, 60).toFloatRect(), Rect(11, 20, 50, 60).toFloatRect(),
+            Rect(11, 21, 50, 60).toFloatRect(), Rect(11, 21, 51, 60).toFloatRect(),
+            Rect(11, 21, 51, 61).toFloatRect(),
+    };
+
+    for (const auto& a : floatRects) {
+        for (const auto& b : floatRects) {
+            const bool hashEq = std::hash<FloatRect>{}(a) == std::hash<FloatRect>{}(b);
+            EXPECT_EQ(a == b, hashEq);
+        }
+    }
+}
+
 } // namespace android::ui
diff --git a/libs/ui/tests/Region_test.cpp b/libs/ui/tests/Region_test.cpp
index c6b826d..74924bd 100644
--- a/libs/ui/tests/Region_test.cpp
+++ b/libs/ui/tests/Region_test.cpp
@@ -167,5 +167,17 @@
     ASSERT_TRUE(touchableRegion.contains(50, 50));
 }
 
+TEST_F(RegionTest, RegionHash) {
+    Region region1;
+    region1.addRectUnchecked(10, 20, 30, 40);
+    region1.addRectUnchecked(40, 30, 20, 10);
+
+    Region region2;
+    region2.addRectUnchecked(11, 20, 30, 40);
+    region2.addRectUnchecked(40, 31, 20, 10);
+
+    EXPECT_NE(std::hash<Region>{}(region1), std::hash<Region>{}(region2));
+}
+
 }; // namespace android
 
diff --git a/libs/vr/libbroadcastring/Android.bp b/libs/vr/libbroadcastring/Android.bp
index 2eb2f9f..d4538f1 100644
--- a/libs/vr/libbroadcastring/Android.bp
+++ b/libs/vr/libbroadcastring/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libbufferhub/Android.bp b/libs/vr/libbufferhub/Android.bp
index 45bdd35..583ad1d 100644
--- a/libs/vr/libbufferhub/Android.bp
+++ b/libs/vr/libbufferhub/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libbufferhubqueue/Android.bp b/libs/vr/libbufferhubqueue/Android.bp
index f372bd7..0bda798 100644
--- a/libs/vr/libbufferhubqueue/Android.bp
+++ b/libs/vr/libbufferhubqueue/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libbufferhubqueue/benchmarks/Android.bp b/libs/vr/libbufferhubqueue/benchmarks/Android.bp
index fc1f376..e33e03b 100644
--- a/libs/vr/libbufferhubqueue/benchmarks/Android.bp
+++ b/libs/vr/libbufferhubqueue/benchmarks/Android.bp
@@ -5,8 +5,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libbufferhubqueue/tests/Android.bp b/libs/vr/libbufferhubqueue/tests/Android.bp
index e883916..33a0d75 100644
--- a/libs/vr/libbufferhubqueue/tests/Android.bp
+++ b/libs/vr/libbufferhubqueue/tests/Android.bp
@@ -5,8 +5,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libdisplay/Android.bp b/libs/vr/libdisplay/Android.bp
index 365a676..b0ed950 100644
--- a/libs/vr/libdisplay/Android.bp
+++ b/libs/vr/libdisplay/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libdvr/Android.bp b/libs/vr/libdvr/Android.bp
index 83c30d7..96023dd 100644
--- a/libs/vr/libdvr/Android.bp
+++ b/libs/vr/libdvr/Android.bp
@@ -19,8 +19,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libdvr/tests/Android.bp b/libs/vr/libdvr/tests/Android.bp
index 4ed80a4..fe7feb8 100644
--- a/libs/vr/libdvr/tests/Android.bp
+++ b/libs/vr/libdvr/tests/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libdvrcommon/Android.bp b/libs/vr/libdvrcommon/Android.bp
index 9e1e516..fe4dfc7 100644
--- a/libs/vr/libdvrcommon/Android.bp
+++ b/libs/vr/libdvrcommon/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libpdx_default_transport/Android.bp b/libs/vr/libpdx_default_transport/Android.bp
index ea73d7a..8046857 100644
--- a/libs/vr/libpdx_default_transport/Android.bp
+++ b/libs/vr/libpdx_default_transport/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libpdx_uds/Android.bp b/libs/vr/libpdx_uds/Android.bp
index 532d1a7..216ca9f 100644
--- a/libs/vr/libpdx_uds/Android.bp
+++ b/libs/vr/libpdx_uds/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libperformance/Android.bp b/libs/vr/libperformance/Android.bp
index 5beee35..38bf4ea 100644
--- a/libs/vr/libperformance/Android.bp
+++ b/libs/vr/libperformance/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libvrflinger/Android.bp b/libs/vr/libvrflinger/Android.bp
index 8aca9a5..bf848af 100644
--- a/libs/vr/libvrflinger/Android.bp
+++ b/libs/vr/libvrflinger/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libvrflinger/tests/Android.bp b/libs/vr/libvrflinger/tests/Android.bp
index dafd354..095f556 100644
--- a/libs/vr/libvrflinger/tests/Android.bp
+++ b/libs/vr/libvrflinger/tests/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libvrsensor/Android.bp b/libs/vr/libvrsensor/Android.bp
index 8f566a0..40a5099 100644
--- a/libs/vr/libvrsensor/Android.bp
+++ b/libs/vr/libvrsensor/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/opengl/libs/EGL/egl_angle_platform.cpp b/opengl/libs/EGL/egl_angle_platform.cpp
index dc8e587..d38f2ef 100644
--- a/opengl/libs/EGL/egl_angle_platform.cpp
+++ b/opengl/libs/EGL/egl_angle_platform.cpp
@@ -22,6 +22,8 @@
 #pragma GCC diagnostic ignored "-Wunused-parameter"
 #include <EGL/Platform.h>
 #pragma GCC diagnostic pop
+
+#include <android-base/properties.h>
 #include <android/dlext.h>
 #include <dlfcn.h>
 #include <graphicsenv/GraphicsEnv.h>
@@ -33,7 +35,6 @@
 
 namespace angle {
 
-constexpr char kAngleEs2Lib[] = "libGLESv2_angle.so";
 constexpr int kAngleDlFlags = RTLD_LOCAL | RTLD_NOW;
 
 static GetDisplayPlatformFunc angleGetDisplayPlatform = nullptr;
@@ -107,18 +108,36 @@
     android_namespace_t* ns = android::GraphicsEnv::getInstance().getAngleNamespace();
     void* so = nullptr;
     if (ns) {
+        // Loading from an APK, so hard-code the suffix to "_angle".
+        constexpr char kAngleEs2Lib[] = "libGLESv2_angle.so";
         const android_dlextinfo dlextinfo = {
                 .flags = ANDROID_DLEXT_USE_NAMESPACE,
                 .library_namespace = ns,
         };
         so = android_dlopen_ext(kAngleEs2Lib, kAngleDlFlags, &dlextinfo);
+        if (so) {
+            ALOGD("dlopen_ext from APK (%s) success at %p", kAngleEs2Lib, so);
+        } else {
+            ALOGE("dlopen_ext(\"%s\") failed: %s", kAngleEs2Lib, dlerror());
+            return false;
+        }
     } else {
         // If we are here, ANGLE is loaded as built-in gl driver in the sphal.
-        so = android_load_sphal_library(kAngleEs2Lib, kAngleDlFlags);
-    }
-    if (!so) {
-        ALOGE("%s failed to dlopen %s!", __FUNCTION__, kAngleEs2Lib);
-        return false;
+        // Get the specified ANGLE library filename suffix.
+        std::string angleEs2LibSuffix = android::base::GetProperty("ro.hardware.egl", "");
+        if (angleEs2LibSuffix.empty()) {
+            ALOGE("%s failed to get valid ANGLE library filename suffix!", __FUNCTION__);
+            return false;
+        }
+
+        std::string angleEs2LibName = "libGLESv2_" + angleEs2LibSuffix + ".so";
+        so = android_load_sphal_library(angleEs2LibName.c_str(), kAngleDlFlags);
+        if (so) {
+            ALOGD("dlopen (%s) success at %p", angleEs2LibName.c_str(), so);
+        } else {
+            ALOGE("%s failed to dlopen %s!", __FUNCTION__, angleEs2LibName.c_str());
+            return false;
+        }
     }
 
     angleGetDisplayPlatform =
@@ -129,8 +148,8 @@
         return false;
     }
 
-    angleResetDisplayPlatform = reinterpret_cast<ResetDisplayPlatformFunc>(
-            eglGetProcAddress("ANGLEResetDisplayPlatform"));
+    angleResetDisplayPlatform =
+            reinterpret_cast<ResetDisplayPlatformFunc>(dlsym(so, "ANGLEResetDisplayPlatform"));
 
     PlatformMethods* platformMethods = nullptr;
     if (!((angleGetDisplayPlatform)(dpy, g_PlatformMethodNames, g_NumPlatformMethods, nullptr,
diff --git a/opengl/tests/configdump/Android.bp b/opengl/tests/configdump/Android.bp
index ffb0c1f..1bb5983 100644
--- a/opengl/tests/configdump/Android.bp
+++ b/opengl/tests/configdump/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/opengl/tests/filter/Android.bp b/opengl/tests/filter/Android.bp
index 3b92b37..b93576f 100644
--- a/opengl/tests/filter/Android.bp
+++ b/opengl/tests/filter/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/opengl/tests/gl_basic/Android.bp b/opengl/tests/gl_basic/Android.bp
index f777401..f55cd0d 100644
--- a/opengl/tests/gl_basic/Android.bp
+++ b/opengl/tests/gl_basic/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/opengl/tests/tritex/Android.bp b/opengl/tests/tritex/Android.bp
index 759582c..87da93f 100644
--- a/opengl/tests/tritex/Android.bp
+++ b/opengl/tests/tritex/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
index 4b1f350..33b3e1e 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -50,9 +50,10 @@
 
 // --- NotifyKeyArgs ---
 
-NotifyKeyArgs::NotifyKeyArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
-                             int32_t displayId, uint32_t policyFlags, int32_t action, int32_t flags,
-                             int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime)
+NotifyKeyArgs::NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId,
+                             uint32_t source, int32_t displayId, uint32_t policyFlags,
+                             int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+                             int32_t metaState, nsecs_t downTime)
       : NotifyArgs(id, eventTime),
         deviceId(deviceId),
         source(source),
@@ -63,7 +64,8 @@
         keyCode(keyCode),
         scanCode(scanCode),
         metaState(metaState),
-        downTime(downTime) {}
+        downTime(downTime),
+        readTime(readTime) {}
 
 NotifyKeyArgs::NotifyKeyArgs(const NotifyKeyArgs& other)
       : NotifyArgs(other.id, other.eventTime),
@@ -76,13 +78,15 @@
         keyCode(other.keyCode),
         scanCode(other.scanCode),
         metaState(other.metaState),
-        downTime(other.downTime) {}
+        downTime(other.downTime),
+        readTime(other.readTime) {}
 
 bool NotifyKeyArgs::operator==(const NotifyKeyArgs& rhs) const {
-    return id == rhs.id && eventTime == rhs.eventTime && deviceId == rhs.deviceId &&
-            source == rhs.source && displayId == rhs.displayId && policyFlags == rhs.policyFlags &&
-            action == rhs.action && flags == rhs.flags && keyCode == rhs.keyCode &&
-            scanCode == rhs.scanCode && metaState == rhs.metaState && downTime == rhs.downTime;
+    return id == rhs.id && eventTime == rhs.eventTime && readTime == rhs.readTime &&
+            deviceId == rhs.deviceId && source == rhs.source && displayId == rhs.displayId &&
+            policyFlags == rhs.policyFlags && action == rhs.action && flags == rhs.flags &&
+            keyCode == rhs.keyCode && scanCode == rhs.scanCode && metaState == rhs.metaState &&
+            downTime == rhs.downTime;
 }
 
 void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
@@ -91,16 +95,14 @@
 
 // --- NotifyMotionArgs ---
 
-NotifyMotionArgs::NotifyMotionArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
-                                   int32_t displayId, uint32_t policyFlags, int32_t action,
-                                   int32_t actionButton, int32_t flags, int32_t metaState,
-                                   int32_t buttonState, MotionClassification classification,
-                                   int32_t edgeFlags, uint32_t pointerCount,
-                                   const PointerProperties* pointerProperties,
-                                   const PointerCoords* pointerCoords, float xPrecision,
-                                   float yPrecision, float xCursorPosition, float yCursorPosition,
-                                   nsecs_t downTime,
-                                   const std::vector<TouchVideoFrame>& videoFrames)
+NotifyMotionArgs::NotifyMotionArgs(
+        int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, uint32_t source,
+        int32_t displayId, uint32_t policyFlags, int32_t action, int32_t actionButton,
+        int32_t flags, int32_t metaState, int32_t buttonState, MotionClassification classification,
+        int32_t edgeFlags, uint32_t pointerCount, const PointerProperties* pointerProperties,
+        const PointerCoords* pointerCoords, float xPrecision, float yPrecision,
+        float xCursorPosition, float yCursorPosition, nsecs_t downTime,
+        const std::vector<TouchVideoFrame>& videoFrames)
       : NotifyArgs(id, eventTime),
         deviceId(deviceId),
         source(source),
@@ -119,6 +121,7 @@
         xCursorPosition(xCursorPosition),
         yCursorPosition(yCursorPosition),
         downTime(downTime),
+        readTime(readTime),
         videoFrames(videoFrames) {
     for (uint32_t i = 0; i < pointerCount; i++) {
         this->pointerProperties[i].copyFrom(pointerProperties[i]);
@@ -145,6 +148,7 @@
         xCursorPosition(other.xCursorPosition),
         yCursorPosition(other.yCursorPosition),
         downTime(other.downTime),
+        readTime(other.readTime),
         videoFrames(other.videoFrames) {
     for (uint32_t i = 0; i < pointerCount; i++) {
         pointerProperties[i].copyFrom(other.pointerProperties[i]);
@@ -157,11 +161,12 @@
 }
 
 bool NotifyMotionArgs::operator==(const NotifyMotionArgs& rhs) const {
-    bool equal = id == rhs.id && eventTime == rhs.eventTime && deviceId == rhs.deviceId &&
-            source == rhs.source && displayId == rhs.displayId && policyFlags == rhs.policyFlags &&
-            action == rhs.action && actionButton == rhs.actionButton && flags == rhs.flags &&
-            metaState == rhs.metaState && buttonState == rhs.buttonState &&
-            classification == rhs.classification && edgeFlags == rhs.edgeFlags &&
+    bool equal = id == rhs.id && eventTime == rhs.eventTime && readTime == rhs.readTime &&
+            deviceId == rhs.deviceId && source == rhs.source && displayId == rhs.displayId &&
+            policyFlags == rhs.policyFlags && action == rhs.action &&
+            actionButton == rhs.actionButton && flags == rhs.flags && metaState == rhs.metaState &&
+            buttonState == rhs.buttonState && classification == rhs.classification &&
+            edgeFlags == rhs.edgeFlags &&
             pointerCount == rhs.pointerCount
             // PointerProperties and PointerCoords are compared separately below
             && xPrecision == rhs.xPrecision && yPrecision == rhs.yPrecision &&
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index d6b7259..b2ddb42 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -105,7 +105,7 @@
 
     void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) override {}
 
-    void pokeUserActivity(nsecs_t, int32_t) override {}
+    void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
 
     bool checkInjectEventsPermissionNonReentrant(int32_t, int32_t) override { return false; }
 
@@ -249,8 +249,9 @@
 
     const nsecs_t currentTime = now();
     // Define a valid motion event.
-    NotifyMotionArgs args(/* id */ 0, currentTime, DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
-                          ADISPLAY_ID_DEFAULT, POLICY_FLAG_PASS_TO_USER, AMOTION_EVENT_ACTION_DOWN,
+    NotifyMotionArgs args(/* id */ 0, currentTime, currentTime, DEVICE_ID,
+                          AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, POLICY_FLAG_PASS_TO_USER,
+                          AMOTION_EVENT_ACTION_DOWN,
                           /* actionButton */ 0, /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
                           MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                           pointerProperties, pointerCoords,
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 45a007b..ed17e68 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -273,6 +273,7 @@
     bool enabled;
     int32_t pid;
     nsecs_t consumeTime; // time when the event was consumed by InputConsumer
+    int32_t displayId;
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/FocusResolver.cpp b/services/inputflinger/dispatcher/FocusResolver.cpp
index ee6b38b..2db8c13 100644
--- a/services/inputflinger/dispatcher/FocusResolver.cpp
+++ b/services/inputflinger/dispatcher/FocusResolver.cpp
@@ -40,110 +40,102 @@
     return it != mFocusedWindowTokenByDisplay.end() ? it->second.second : nullptr;
 }
 
-std::optional<FocusRequest> FocusResolver::getPendingRequest(int32_t displayId) {
-    auto it = mPendingFocusRequests.find(displayId);
-    return it != mPendingFocusRequests.end() ? std::make_optional<>(it->second) : std::nullopt;
+std::optional<FocusRequest> FocusResolver::getFocusRequest(int32_t displayId) {
+    auto it = mFocusRequestByDisplay.find(displayId);
+    return it != mFocusRequestByDisplay.end() ? std::make_optional<>(it->second) : std::nullopt;
 }
 
+/**
+ * 'setInputWindows' is called when the window properties change. Here we will check whether the
+ * currently focused window can remain focused. If the currently focused window remains eligible
+ * for focus ('isTokenFocusable' returns OK), then we will continue to grant it focus otherwise
+ * we will check if the previous focus request is eligible to receive focus.
+ */
 std::optional<FocusResolver::FocusChanges> FocusResolver::setInputWindows(
         int32_t displayId, const std::vector<sp<InputWindowHandle>>& windows) {
-    // If the current focused window becomes unfocusable, remove focus.
-    sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
+    std::string removeFocusReason;
+
+    // Check if the currently focused window is still focusable.
+    const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
     if (currentFocus) {
-        FocusResult result = isTokenFocusable(currentFocus, windows);
-        if (result != FocusResult::OK) {
-            return updateFocusedWindow(displayId, NamedEnum::string(result), nullptr);
+        Focusability result = isTokenFocusable(currentFocus, windows);
+        if (result == Focusability::OK) {
+            return std::nullopt;
+        }
+        removeFocusReason = NamedEnum::string(result);
+    }
+
+    // We don't have a focused window or the currently focused window is no longer focusable. Check
+    // to see if we can grant focus to the window that previously requested focus.
+    const std::optional<FocusRequest> request = getFocusRequest(displayId);
+    if (request) {
+        sp<IBinder> requestedFocus = request->token;
+        const Focusability result = isTokenFocusable(requestedFocus, windows);
+        const Focusability previousResult = mLastFocusResultByDisplay[displayId];
+        mLastFocusResultByDisplay[displayId] = result;
+        if (result == Focusability::OK) {
+            return updateFocusedWindow(displayId,
+                                       "Window became focusable. Previous reason: " +
+                                               NamedEnum::string(previousResult),
+                                       requestedFocus, request->windowName);
         }
     }
 
-    // Check if any pending focus requests can be resolved.
-    std::optional<FocusRequest> pendingRequest = getPendingRequest(displayId);
-    if (!pendingRequest) {
-        return std::nullopt;
-    }
-
-    sp<IBinder> requestedFocus = pendingRequest->token;
-    std::string windowName = pendingRequest->windowName;
-    if (currentFocus == requestedFocus) {
-        ALOGD_IF(DEBUG_FOCUS,
-                 "setFocusedWindow %s on display %" PRId32 " ignored, reason: already focused",
-                 windowName.c_str(), displayId);
-        mPendingFocusRequests.erase(displayId);
-        return std::nullopt;
-    }
-
-    FocusResult result = isTokenFocusable(requestedFocus, windows);
-    // If the window from the pending request is now visible, provide it focus.
-    if (result == FocusResult::OK) {
-        mPendingFocusRequests.erase(displayId);
-        return updateFocusedWindow(displayId, "Window became visible", requestedFocus, windowName);
-    }
-
-    if (result != FocusResult::NOT_VISIBLE) {
-        // Drop the request if we are unable to change the focus for a reason other than visibility.
-        ALOGW("Focus request %s on display %" PRId32 " ignored, reason:%s", windowName.c_str(),
-              displayId, NamedEnum::string(result).c_str());
-        mPendingFocusRequests.erase(displayId);
-    }
-    return std::nullopt;
+    // Focused window is no longer focusable and we don't have a suitable focus request to grant.
+    // Remove focus if needed.
+    return updateFocusedWindow(displayId, removeFocusReason, nullptr);
 }
 
 std::optional<FocusResolver::FocusChanges> FocusResolver::setFocusedWindow(
         const FocusRequest& request, const std::vector<sp<InputWindowHandle>>& windows) {
     const int32_t displayId = request.displayId;
     const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
-    if (request.focusedToken && currentFocus != request.focusedToken) {
-        ALOGW("setFocusedWindow %s on display %" PRId32
-              " ignored, reason: focusedToken  %s is not focused",
-              request.windowName.c_str(), displayId, request.focusedWindowName.c_str());
-        return std::nullopt;
-    }
-
-    std::optional<FocusRequest> pendingRequest = getPendingRequest(displayId);
-    if (pendingRequest) {
-        ALOGW("Pending focus request %s on display %" PRId32
-              " ignored, reason:replaced by new request",
-              pendingRequest->windowName.c_str(), displayId);
-
-        // clear any pending focus requests
-        mPendingFocusRequests.erase(displayId);
-    }
-
     if (currentFocus == request.token) {
         ALOGD_IF(DEBUG_FOCUS,
-                 "setFocusedWindow %s on display %" PRId32 " ignored, reason:already focused",
+                 "setFocusedWindow %s on display %" PRId32 " ignored, reason: already focused",
                  request.windowName.c_str(), displayId);
         return std::nullopt;
     }
 
-    FocusResult result = isTokenFocusable(request.token, windows);
-    if (result == FocusResult::OK) {
-        std::string reason =
-                (request.focusedToken) ? "setFocusedWindow with focus check" : "setFocusedWindow";
-        return updateFocusedWindow(displayId, reason, request.token, request.windowName);
-    }
-
-    if (result == FocusResult::NOT_VISIBLE) {
-        // The requested window is not currently visible. Wait for the window to become visible
-        // and then provide it focus. This is to handle situations where a user action triggers
-        // a new window to appear. We want to be able to queue any key events after the user
-        // action and deliver it to the newly focused window. In order for this to happen, we
-        // take focus from the currently focused window so key events can be queued.
-        ALOGD_IF(DEBUG_FOCUS,
-                 "setFocusedWindow %s on display %" PRId32
-                 " pending, reason: window is not visible",
-                 request.windowName.c_str(), displayId);
-        mPendingFocusRequests[displayId] = request;
-        return updateFocusedWindow(displayId, "Waiting for window to be visible", nullptr);
-    } else {
-        ALOGW("setFocusedWindow %s on display %" PRId32 " ignored, reason:%s",
+    // Handle conditional focus requests, i.e. requests that have a focused token. These requests
+    // are not persistent. If the window is no longer focusable, we expect focus to go back to the
+    // previously focused window.
+    if (request.focusedToken) {
+        if (currentFocus != request.focusedToken) {
+            ALOGW("setFocusedWindow %s on display %" PRId32
+                  " ignored, reason: focusedToken %s is not focused",
+                  request.windowName.c_str(), displayId, request.focusedWindowName.c_str());
+            return std::nullopt;
+        }
+        Focusability result = isTokenFocusable(request.token, windows);
+        if (result == Focusability::OK) {
+            return updateFocusedWindow(displayId, "setFocusedWindow with focus check",
+                                       request.token, request.windowName);
+        }
+        ALOGW("setFocusedWindow %s on display %" PRId32 " ignored, reason: %s",
               request.windowName.c_str(), displayId, NamedEnum::string(result).c_str());
+        return std::nullopt;
     }
 
-    return std::nullopt;
+    Focusability result = isTokenFocusable(request.token, windows);
+    // Update focus request. The focus resolver will always try to handle this request if there is
+    // no focused window on the display.
+    mFocusRequestByDisplay[displayId] = request;
+    mLastFocusResultByDisplay[displayId] = result;
+
+    if (result == Focusability::OK) {
+        return updateFocusedWindow(displayId, "setFocusedWindow", request.token,
+                                   request.windowName);
+    }
+
+    // The requested window is not currently focusable. Wait for the window to become focusable
+    // but remove focus from the current window so that input events can go into a pending queue
+    // and be sent to the window when it becomes focused.
+    return updateFocusedWindow(displayId, "Waiting for window because " + NamedEnum::string(result),
+                               nullptr);
 }
 
-FocusResolver::FocusResult FocusResolver::isTokenFocusable(
+FocusResolver::Focusability FocusResolver::isTokenFocusable(
         const sp<IBinder>& token, const std::vector<sp<InputWindowHandle>>& windows) {
     bool allWindowsAreFocusable = true;
     bool visibleWindowFound = false;
@@ -165,16 +157,16 @@
     }
 
     if (!windowFound) {
-        return FocusResult::NO_WINDOW;
+        return Focusability::NO_WINDOW;
     }
     if (!allWindowsAreFocusable) {
-        return FocusResult::NOT_FOCUSABLE;
+        return Focusability::NOT_FOCUSABLE;
     }
     if (!visibleWindowFound) {
-        return FocusResult::NOT_VISIBLE;
+        return Focusability::NOT_VISIBLE;
     }
 
-    return FocusResult::OK;
+    return Focusability::OK;
 }
 
 std::optional<FocusResolver::FocusChanges> FocusResolver::updateFocusedWindow(
@@ -209,15 +201,17 @@
 
 std::string FocusResolver::dump() const {
     std::string dump = dumpFocusedWindows();
-
-    if (mPendingFocusRequests.empty()) {
-        return dump + INDENT "PendingFocusRequests: <none>\n";
+    if (mFocusRequestByDisplay.empty()) {
+        return dump + INDENT "FocusRequests: <none>\n";
     }
 
-    dump += INDENT "PendingFocusRequests:\n";
-    for (const auto& [displayId, request] : mPendingFocusRequests) {
-        dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s'\n", displayId,
-                                   request.windowName.c_str());
+    dump += INDENT "FocusRequests:\n";
+    for (const auto& [displayId, request] : mFocusRequestByDisplay) {
+        auto it = mLastFocusResultByDisplay.find(displayId);
+        std::string result =
+                it != mLastFocusResultByDisplay.end() ? NamedEnum::string(it->second) : "";
+        dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s' result='%s'\n",
+                                   displayId, request.windowName.c_str(), result.c_str());
     }
     return dump;
 }
diff --git a/services/inputflinger/dispatcher/FocusResolver.h b/services/inputflinger/dispatcher/FocusResolver.h
index e067ad9..dc5eeeb 100644
--- a/services/inputflinger/dispatcher/FocusResolver.h
+++ b/services/inputflinger/dispatcher/FocusResolver.h
@@ -34,11 +34,18 @@
 //   is visible with the same token and all window handles with the same token are focusable.
 //   See FocusResolver::isTokenFocusable
 //
-//   Focus request - Request will be granted if the window is focusable. If the window is not
-//   visible, then the request is kept in a pending state and granted when it becomes visible.
-//   If window becomes not focusable, or another request comes in, the pending request is dropped.
+//   Focus request - Request will be granted if the window is focusable. If it's not
+//   focusable, then the request is persisted and granted when it becomes focusable. The currently
+//   focused window will lose focus and any pending keys will be added to a queue so it can be sent
+//   to the window when it gets focus.
+//
+//   Condition focus request - Request with a focus token specified. Request will be granted if the
+//   window is focusable and the focus token is the currently focused. Otherwise, the request is
+//   dropped. Conditional focus requests are not persisted. The window will lose focus and go back
+//   to the focus token if it becomes not focusable.
 //
 //   Window handle updates - Focus is lost when the currently focused window becomes not focusable.
+//   If the previous focus request is focusable, then we will try to grant that window focus.
 class FocusResolver {
 public:
     // Returns the focused window token on the specified display.
@@ -61,7 +68,7 @@
     std::string dump() const;
 
 private:
-    enum class FocusResult {
+    enum class Focusability {
         OK,
         NO_WINDOW,
         NOT_FOCUSABLE,
@@ -77,8 +84,8 @@
     // we expect the focusability of the windows to match since its hard to reason why one window
     // can receive focus events and the other cannot when both are backed by the same input channel.
     //
-    static FocusResult isTokenFocusable(const sp<IBinder>& token,
-                                        const std::vector<sp<InputWindowHandle>>& windows);
+    static Focusability isTokenFocusable(const sp<IBinder>& token,
+                                         const std::vector<sp<InputWindowHandle>>& windows);
 
     // Focus tracking for keys, trackball, etc. A window token can be associated with one or
     // more InputWindowHandles. If a window is mirrored, the window and its mirror will share
@@ -87,15 +94,18 @@
     typedef std::pair<std::string /* name */, sp<IBinder>> NamedToken;
     std::unordered_map<int32_t /* displayId */, NamedToken> mFocusedWindowTokenByDisplay;
 
-    // This map will store a single pending focus request per display that cannot be currently
-    // processed. This can happen if the window requested to be focused is not currently visible.
-    // Such a window might become visible later, and these requests would be processed at that time.
-    std::unordered_map<int32_t /* displayId */, FocusRequest> mPendingFocusRequests;
+    // This map will store the focus request per display. When the input window handles are updated,
+    // the current request will be checked to see if it can be processed at that time.
+    std::unordered_map<int32_t /* displayId */, FocusRequest> mFocusRequestByDisplay;
+
+    // Last reason for not granting a focus request. This is used to add more debug information
+    // in the event logs.
+    std::unordered_map<int32_t /* displayId */, Focusability> mLastFocusResultByDisplay;
 
     std::optional<FocusResolver::FocusChanges> updateFocusedWindow(
             int32_t displayId, const std::string& reason, const sp<IBinder>& token,
             const std::string& tokenName = "");
-    std::optional<FocusRequest> getPendingRequest(int32_t displayId);
+    std::optional<FocusRequest> getFocusRequest(int32_t displayId);
 };
 
 } // namespace android::inputdispatcher
\ No newline at end of file
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 9898e9d..465e8be 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -1439,7 +1439,7 @@
     ALOGD("notifySensorEvent eventTime=%" PRId64 ", hwTimestamp=%" PRId64 ", deviceId=%d, "
           "source=0x%x, sensorType=%s",
           entry->eventTime, entry->hwTimestamp, entry->deviceId, entry->source,
-          NamedEnum::string(sensorType).c_str());
+          NamedEnum::string(entry->sensorType).c_str());
 #endif
     std::unique_ptr<CommandEntry> commandEntry =
             std::make_unique<CommandEntry>(&InputDispatcher::doNotifySensorLockedInterruptible);
@@ -2591,6 +2591,7 @@
             std::make_unique<CommandEntry>(&InputDispatcher::doPokeUserActivityLockedInterruptible);
     commandEntry->eventTime = eventEntry.eventTime;
     commandEntry->userActivityEventType = eventType;
+    commandEntry->displayId = displayId;
     postCommandLocked(std::move(commandEntry));
 }
 
@@ -5715,7 +5716,8 @@
 void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) {
     mLock.unlock();
 
-    mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->userActivityEventType);
+    mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->userActivityEventType,
+                              commandEntry->displayId);
 
     mLock.lock();
 }
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 909ce54..439d85e 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -134,7 +134,7 @@
                               uint32_t policyFlags) = 0;
 
     /* Poke user activity for an event dispatched to a window. */
-    virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) = 0;
+    virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) = 0;
 
     /* Checks whether a given application pid/uid has permission to inject input events
      * into other applications.
diff --git a/services/inputflinger/host/InputFlinger.h b/services/inputflinger/host/InputFlinger.h
index 47773d9..8112038 100644
--- a/services/inputflinger/host/InputFlinger.h
+++ b/services/inputflinger/host/InputFlinger.h
@@ -43,19 +43,19 @@
 
     InputFlinger() ANDROID_API;
 
-    virtual status_t dump(int fd, const Vector<String16>& args);
+    status_t dump(int fd, const Vector<String16>& args) override;
     binder::Status setInputWindows(const std::vector<InputWindowInfo>&,
-                                   const sp<ISetInputWindowsListener>&) {
+                                   const sp<ISetInputWindowsListener>&) override {
         return binder::Status::ok();
     }
-    binder::Status createInputChannel(const std::string&, InputChannel*) {
+    binder::Status createInputChannel(const std::string&, InputChannel*) override {
         return binder::Status::ok();
     }
-    binder::Status removeInputChannel(const sp<IBinder>&) { return binder::Status::ok(); }
-    binder::Status setFocusedWindow(const FocusRequest&) { return binder::Status::ok(); }
+    binder::Status removeInputChannel(const sp<IBinder>&) override { return binder::Status::ok(); }
+    binder::Status setFocusedWindow(const FocusRequest&) override { return binder::Status::ok(); }
 
 private:
-    virtual ~InputFlinger();
+    ~InputFlinger() override;
 
     void dumpInternal(String8& result);
 
diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h
index 094bc0c..4b7d26d 100644
--- a/services/inputflinger/include/InputListener.h
+++ b/services/inputflinger/include/InputListener.h
@@ -73,12 +73,14 @@
     int32_t scanCode;
     int32_t metaState;
     nsecs_t downTime;
+    nsecs_t readTime;
 
     inline NotifyKeyArgs() { }
 
-    NotifyKeyArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
-                  int32_t displayId, uint32_t policyFlags, int32_t action, int32_t flags,
-                  int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime);
+    NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId,
+                  uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action,
+                  int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+                  nsecs_t downTime);
 
     bool operator==(const NotifyKeyArgs& rhs) const;
 
@@ -120,13 +122,14 @@
     float xCursorPosition;
     float yCursorPosition;
     nsecs_t downTime;
+    nsecs_t readTime;
     std::vector<TouchVideoFrame> videoFrames;
 
     inline NotifyMotionArgs() { }
 
-    NotifyMotionArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
-                     int32_t displayId, uint32_t policyFlags, int32_t action, int32_t actionButton,
-                     int32_t flags, int32_t metaState, int32_t buttonState,
+    NotifyMotionArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId,
+                     uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action,
+                     int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
                      MotionClassification classification, int32_t edgeFlags, uint32_t pointerCount,
                      const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
                      float xPrecision, float yPrecision, float xCursorPosition,
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 69aea84..3bf212a 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -113,6 +113,10 @@
     /* Get battery status of a particular input device. */
     virtual std::optional<int32_t> getBatteryStatus(int32_t deviceId) = 0;
 
+    virtual std::vector<int32_t> getLightIds(int32_t deviceId) = 0;
+
+    virtual const InputDeviceLightInfo* getLightInfo(int32_t deviceId, int32_t lightId) = 0;
+
     /* Return true if the device can send input events to the specified display. */
     virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0;
 
@@ -126,6 +130,15 @@
 
     /* Flush sensor data in input reader mapper. */
     virtual void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0;
+
+    /* Set color for the light */
+    virtual bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) = 0;
+    /* Set player ID for the light */
+    virtual bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) = 0;
+    /* Get light color */
+    virtual std::optional<int32_t> getLightColor(int32_t deviceId, int32_t lightId) = 0;
+    /* Get light player ID */
+    virtual std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) = 0;
 };
 
 // --- InputReaderConfiguration ---
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index 518e295..8a2828c 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -45,6 +45,7 @@
         "mapper/InputMapper.cpp",
         "mapper/JoystickInputMapper.cpp",
         "mapper/KeyboardInputMapper.cpp",
+        "mapper/LightInputMapper.cpp",
         "mapper/MultiTouchInputMapper.cpp",
         "mapper/RotaryEncoderInputMapper.cpp",
         "mapper/SensorInputMapper.cpp",
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 8f8c051..1a7fcd5 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -37,6 +37,7 @@
 
 // #define LOG_NDEBUG 0
 #include <android-base/file.h>
+#include <android-base/strings.h>
 #include <android-base/stringprintf.h>
 #include <cutils/properties.h>
 #include <input/KeyCharacterMap.h>
@@ -48,6 +49,7 @@
 #include <utils/Timers.h>
 
 #include <filesystem>
+#include <regex>
 
 #include "EventHub.h"
 
@@ -84,6 +86,30 @@
                                                                        {"Full", 100},
                                                                        {"Unknown", 50}};
 
+// Mapping for input led class node names lookup.
+// https://www.kernel.org/doc/html/latest/leds/leds-class.html
+static const std::unordered_map<std::string, InputLightClass> LIGHT_CLASSES =
+        {{"red", InputLightClass::RED},
+         {"green", InputLightClass::GREEN},
+         {"blue", InputLightClass::BLUE},
+         {"global", InputLightClass::GLOBAL},
+         {"brightness", InputLightClass::BRIGHTNESS},
+         {"multi_index", InputLightClass::MULTI_INDEX},
+         {"multi_intensity", InputLightClass::MULTI_INTENSITY},
+         {"max_brightness", InputLightClass::MAX_BRIGHTNESS}};
+
+// Mapping for input multicolor led class node names.
+// https://www.kernel.org/doc/html/latest/leds/leds-class-multicolor.html
+static const std::unordered_map<InputLightClass, std::string> LIGHT_NODES =
+        {{InputLightClass::BRIGHTNESS, "brightness"},
+         {InputLightClass::MULTI_INDEX, "multi_index"},
+         {InputLightClass::MULTI_INTENSITY, "multi_intensity"}};
+
+// Mapping for light color name and the light color
+const std::unordered_map<std::string, LightColor> LIGHT_COLORS = {{"red", LightColor::RED},
+                                                                  {"green", LightColor::GREEN},
+                                                                  {"blue", LightColor::BLUE}};
+
 static inline const char* toString(bool value) {
     return value ? "true" : "false";
 }
@@ -151,14 +177,14 @@
  * Returns the sysfs root path of the input device
  *
  */
-static std::filesystem::path getSysfsRootPath(const char* devicePath) {
+static std::optional<std::filesystem::path> getSysfsRootPath(const char* devicePath) {
     std::error_code errorCode;
 
     // Stat the device path to get the major and minor number of the character file
     struct stat statbuf;
     if (stat(devicePath, &statbuf) == -1) {
         ALOGE("Could not stat device %s due to error: %s.", devicePath, std::strerror(errno));
-        return std::filesystem::path();
+        return std::nullopt;
     }
 
     unsigned int major_num = major(statbuf.st_rdev);
@@ -173,7 +199,7 @@
     if (errorCode) {
         ALOGW("Could not run filesystem::canonical() due to error %d : %s.", errorCode.value(),
               errorCode.message().c_str());
-        return std::filesystem::path();
+        return std::nullopt;
     }
 
     // Continue to go up a directory until we reach a directory named "input"
@@ -192,26 +218,68 @@
         }
 
         // Not found
-        return std::filesystem::path();
+        return std::nullopt;
     }
 
     return sysfsPath;
 }
 
 /**
- * Returns the power supply node in sys fs
- *
+ * Returns the list of files under a specified path.
  */
-static std::filesystem::path findPowerSupplyNode(const std::filesystem::path& sysfsRootPath) {
-    for (auto path = sysfsRootPath; path != "/"; path = path.parent_path()) {
-        std::error_code errorCode;
-        auto iter = std::filesystem::directory_iterator(path / "power_supply", errorCode);
-        if (!errorCode && iter != std::filesystem::directory_iterator()) {
-            return iter->path();
+static std::vector<std::filesystem::path> allFilesInPath(const std::filesystem::path& path) {
+    std::vector<std::filesystem::path> nodes;
+    std::error_code errorCode;
+    auto iter = std::filesystem::directory_iterator(path, errorCode);
+    while (!errorCode && iter != std::filesystem::directory_iterator()) {
+        nodes.push_back(iter->path());
+        iter++;
+    }
+    return nodes;
+}
+
+/**
+ * Returns the list of files under a specified directory in a sysfs path.
+ * Example:
+ * findSysfsNodes(sysfsRootPath, SysfsClass::LEDS) will return all led nodes under "leds" directory
+ * in the sysfs path.
+ */
+static std::vector<std::filesystem::path> findSysfsNodes(const std::filesystem::path& sysfsRoot,
+                                                         SysfsClass clazz) {
+    std::string nodeStr = NamedEnum::string(clazz);
+    std::for_each(nodeStr.begin(), nodeStr.end(),
+                  [](char& c) { c = std::tolower(static_cast<unsigned char>(c)); });
+    std::vector<std::filesystem::path> nodes;
+    for (auto path = sysfsRoot; path != "/" && nodes.empty(); path = path.parent_path()) {
+        nodes = allFilesInPath(path / nodeStr);
+    }
+    return nodes;
+}
+
+static std::optional<std::array<LightColor, COLOR_NUM>> getColorIndexArray(
+        std::filesystem::path path) {
+    std::string indexStr;
+    if (!base::ReadFileToString(path, &indexStr)) {
+        return std::nullopt;
+    }
+
+    // Parse the multi color LED index file, refer to kernel docs
+    // leds/leds-class-multicolor.html
+    std::regex indexPattern("(red|green|blue)\\s(red|green|blue)\\s(red|green|blue)[\\n]");
+    std::smatch results;
+    std::array<LightColor, COLOR_NUM> colors;
+    if (!std::regex_match(indexStr, results, indexPattern)) {
+        return std::nullopt;
+    }
+
+    for (size_t i = 1; i < results.size(); i++) {
+        const auto it = LIGHT_COLORS.find(results[i].str());
+        if (it != LIGHT_COLORS.end()) {
+            // intensities.emplace(it->second, 0);
+            colors[i - 1] = it->second;
         }
     }
-    // Not found
-    return std::filesystem::path();
+    return colors;
 }
 
 // --- Global Functions ---
@@ -280,6 +348,7 @@
         virtualKeyMap(nullptr),
         ffEffectPlaying(false),
         ffEffectId(-1),
+        nextLightId(0),
         controllerNumber(0),
         enabled(true),
         isVirtual(fd < 0) {}
@@ -469,6 +538,70 @@
     return NAME_NOT_FOUND;
 }
 
+// Check the sysfs path for any input device batteries, returns true if battery found.
+bool EventHub::Device::configureBatteryLocked() {
+    if (!sysfsRootPath.has_value()) {
+        return false;
+    }
+    // Check if device has any batteries.
+    std::vector<std::filesystem::path> batteryPaths =
+            findSysfsNodes(sysfsRootPath.value(), SysfsClass::POWER_SUPPLY);
+    // We only support single battery for an input device, if multiple batteries exist only the
+    // first one is supported.
+    if (batteryPaths.empty()) {
+        // Set path to be empty
+        sysfsBatteryPath = std::nullopt;
+        return false;
+    }
+    // If a battery exists
+    sysfsBatteryPath = batteryPaths[0];
+    return true;
+}
+
+// Check the sysfs path for any input device lights, returns true if lights found.
+bool EventHub::Device::configureLightsLocked() {
+    if (!sysfsRootPath.has_value()) {
+        return false;
+    }
+    // Check if device has any lights.
+    const auto& paths = findSysfsNodes(sysfsRootPath.value(), SysfsClass::LEDS);
+    for (const auto& nodePath : paths) {
+        RawLightInfo info;
+        info.id = ++nextLightId;
+        info.path = nodePath;
+        info.name = nodePath.filename();
+        info.maxBrightness = std::nullopt;
+        size_t nameStart = info.name.rfind(":");
+        if (nameStart != std::string::npos) {
+            // Trim the name to color name
+            info.name = info.name.substr(nameStart + 1);
+            // Set InputLightClass flag for colors
+            const auto it = LIGHT_CLASSES.find(info.name);
+            if (it != LIGHT_CLASSES.end()) {
+                info.flags |= it->second;
+            }
+        }
+        // Scan the path for all the files
+        // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt
+        const auto& files = allFilesInPath(nodePath);
+        for (const auto& file : files) {
+            const auto it = LIGHT_CLASSES.find(file.filename().string());
+            if (it != LIGHT_CLASSES.end()) {
+                info.flags |= it->second;
+                // If the node has maximum brightness, read it
+                if (it->second == InputLightClass::MAX_BRIGHTNESS) {
+                    std::string str;
+                    if (base::ReadFileToString(file, &str)) {
+                        info.maxBrightness = std::stoi(str);
+                    }
+                }
+            }
+        }
+        lightInfos.insert_or_assign(info.id, info);
+    }
+    return !lightInfos.empty();
+}
+
 /**
  * Get the capabilities for the current process.
  * Crashes the system if unable to create / check / destroy the capabilities object.
@@ -829,6 +962,161 @@
     return Errorf("Device not found or device has no key layout.");
 }
 
+const std::vector<int32_t> EventHub::getRawLightIds(int32_t deviceId) {
+    std::scoped_lock _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    std::vector<int32_t> lightIds;
+
+    if (device != nullptr) {
+        for (const auto [id, info] : device->lightInfos) {
+            lightIds.push_back(id);
+        }
+    }
+    return lightIds;
+}
+
+std::optional<RawLightInfo> EventHub::getRawLightInfo(int32_t deviceId, int32_t lightId) {
+    std::scoped_lock _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+
+    if (device != nullptr) {
+        auto it = device->lightInfos.find(lightId);
+        if (it != device->lightInfos.end()) {
+            return it->second;
+        }
+    }
+    return std::nullopt;
+}
+
+std::optional<int32_t> EventHub::getLightBrightness(int32_t deviceId, int32_t lightId) {
+    std::scoped_lock _l(mLock);
+
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) {
+        return std::nullopt;
+    }
+
+    auto it = device->lightInfos.find(lightId);
+    if (it == device->lightInfos.end()) {
+        return std::nullopt;
+    }
+    std::string buffer;
+    if (!base::ReadFileToString(it->second.path / LIGHT_NODES.at(InputLightClass::BRIGHTNESS),
+                                &buffer)) {
+        return std::nullopt;
+    }
+    return std::stoi(buffer);
+}
+
+std::optional<std::unordered_map<LightColor, int32_t>> EventHub::getLightIntensities(
+        int32_t deviceId, int32_t lightId) {
+    std::scoped_lock _l(mLock);
+
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) {
+        return std::nullopt;
+    }
+
+    auto lightIt = device->lightInfos.find(lightId);
+    if (lightIt == device->lightInfos.end()) {
+        return std::nullopt;
+    }
+
+    auto ret =
+            getColorIndexArray(lightIt->second.path / LIGHT_NODES.at(InputLightClass::MULTI_INDEX));
+
+    if (!ret.has_value()) {
+        return std::nullopt;
+    }
+    std::array<LightColor, COLOR_NUM> colors = ret.value();
+
+    std::string intensityStr;
+    if (!base::ReadFileToString(lightIt->second.path /
+                                        LIGHT_NODES.at(InputLightClass::MULTI_INTENSITY),
+                                &intensityStr)) {
+        return std::nullopt;
+    }
+
+    // Intensity node outputs 3 color values
+    std::regex intensityPattern("([0-9]+)\\s([0-9]+)\\s([0-9]+)[\\n]");
+    std::smatch results;
+
+    if (!std::regex_match(intensityStr, results, intensityPattern)) {
+        return std::nullopt;
+    }
+    std::unordered_map<LightColor, int32_t> intensities;
+    for (size_t i = 1; i < results.size(); i++) {
+        int value = std::stoi(results[i].str());
+        intensities.emplace(colors[i - 1], value);
+    }
+    return intensities;
+}
+
+void EventHub::setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) {
+    std::scoped_lock _l(mLock);
+
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) {
+        ALOGE("Device Id %d does not exist", deviceId);
+        return;
+    }
+    auto lightIt = device->lightInfos.find(lightId);
+    if (lightIt == device->lightInfos.end()) {
+        ALOGE("Light Id %d does not exist.", lightId);
+        return;
+    }
+
+    if (!base::WriteStringToFile(std::to_string(brightness),
+                                 lightIt->second.path /
+                                         LIGHT_NODES.at(InputLightClass::BRIGHTNESS))) {
+        ALOGE("Can not write to file, error: %s", strerror(errno));
+    }
+}
+
+void EventHub::setLightIntensities(int32_t deviceId, int32_t lightId,
+                                   std::unordered_map<LightColor, int32_t> intensities) {
+    std::scoped_lock _l(mLock);
+
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) {
+        ALOGE("Device Id %d does not exist", deviceId);
+        return;
+    }
+    auto lightIt = device->lightInfos.find(lightId);
+    if (lightIt == device->lightInfos.end()) {
+        ALOGE("Light Id %d does not exist.", lightId);
+        return;
+    }
+
+    auto ret =
+            getColorIndexArray(lightIt->second.path / LIGHT_NODES.at(InputLightClass::MULTI_INDEX));
+
+    if (!ret.has_value()) {
+        return;
+    }
+    std::array<LightColor, COLOR_NUM> colors = ret.value();
+
+    std::string rgbStr;
+    for (size_t i = 0; i < COLOR_NUM; i++) {
+        auto it = intensities.find(colors[i]);
+        if (it != intensities.end()) {
+            rgbStr += std::to_string(it->second);
+            // Insert space between colors
+            if (i < COLOR_NUM - 1) {
+                rgbStr += " ";
+            }
+        }
+    }
+    // Append new line
+    rgbStr += "\n";
+
+    if (!base::WriteStringToFile(rgbStr,
+                                 lightIt->second.path /
+                                         LIGHT_NODES.at(InputLightClass::MULTI_INTENSITY))) {
+        ALOGE("Can not write to file, error: %s", strerror(errno));
+    }
+}
+
 void EventHub::setExcludedDevices(const std::vector<std::string>& devices) {
     std::scoped_lock _l(mLock);
 
@@ -1068,19 +1356,20 @@
     Device* device = getDeviceLocked(deviceId);
     std::string buffer;
 
-    if (!device || (device->sysfsBatteryPath.empty())) {
+    if (device == nullptr || !device->sysfsBatteryPath.has_value()) {
         return std::nullopt;
     }
 
     // Some devices report battery capacity as an integer through the "capacity" file
-    if (base::ReadFileToString(device->sysfsBatteryPath / "capacity", &buffer)) {
-        return std::stoi(buffer);
+    if (base::ReadFileToString(device->sysfsBatteryPath.value() / "capacity", &buffer)) {
+        return std::stoi(base::Trim(buffer));
     }
 
     // Other devices report capacity as an enum value POWER_SUPPLY_CAPACITY_LEVEL_XXX
     // These values are taken from kernel source code include/linux/power_supply.h
-    if (base::ReadFileToString(device->sysfsBatteryPath / "capacity_level", &buffer)) {
-        const auto it = BATTERY_LEVEL.find(buffer);
+    if (base::ReadFileToString(device->sysfsBatteryPath.value() / "capacity_level", &buffer)) {
+        // Remove any white space such as trailing new line
+        const auto it = BATTERY_LEVEL.find(base::Trim(buffer));
         if (it != BATTERY_LEVEL.end()) {
             return it->second;
         }
@@ -1093,18 +1382,17 @@
     Device* device = getDeviceLocked(deviceId);
     std::string buffer;
 
-    if (!device || (device->sysfsBatteryPath.empty())) {
+    if (device == nullptr || !device->sysfsBatteryPath.has_value()) {
         return std::nullopt;
     }
 
-    if (!base::ReadFileToString(device->sysfsBatteryPath / "status", &buffer)) {
+    if (!base::ReadFileToString(device->sysfsBatteryPath.value() / "status", &buffer)) {
         ALOGE("Failed to read sysfs battery info: %s", strerror(errno));
         return std::nullopt;
     }
 
-    // Remove trailing new line
-    buffer.erase(std::remove(buffer.begin(), buffer.end(), '\n'), buffer.end());
-    const auto it = BATTERY_STATUS.find(buffer);
+    // Remove white space like trailing new line
+    const auto it = BATTERY_STATUS.find(base::Trim(buffer));
 
     if (it != BATTERY_STATUS.end()) {
         return it->second;
@@ -1280,6 +1568,7 @@
                     for (size_t i = 0; i < count; i++) {
                         struct input_event& iev = readBuffer[i];
                         event->when = processEventTimestamp(iev);
+                        event->readTime = systemTime(SYSTEM_TIME_MONOTONIC);
                         event->deviceId = deviceId;
                         event->type = iev.type;
                         event->code = iev.code;
@@ -1482,7 +1771,17 @@
     }
 }
 
-status_t EventHub::openDeviceLocked(const std::string& devicePath) {
+void EventHub::openDeviceLocked(const std::string& devicePath) {
+    // If an input device happens to register around the time when EventHub's constructor runs, it
+    // is possible that the same input event node (for example, /dev/input/event3) will be noticed
+    // in both 'inotify' callback and also in the 'scanDirLocked' pass. To prevent duplicate devices
+    // from getting registered, ensure that this path is not already covered by an existing device.
+    for (const auto& [deviceId, device] : mDevices) {
+        if (device->path == devicePath) {
+            return; // device was already registered
+        }
+    }
+
     char buffer[80];
 
     ALOGV("Opening device: %s", devicePath.c_str());
@@ -1490,7 +1789,7 @@
     int fd = open(devicePath.c_str(), O_RDWR | O_CLOEXEC | O_NONBLOCK);
     if (fd < 0) {
         ALOGE("could not open %s, %s\n", devicePath.c_str(), strerror(errno));
-        return -1;
+        return;
     }
 
     InputDeviceIdentifier identifier;
@@ -1509,7 +1808,7 @@
         if (identifier.name == item) {
             ALOGI("ignoring event id %s driver %s\n", devicePath.c_str(), item.c_str());
             close(fd);
-            return -1;
+            return;
         }
     }
 
@@ -1518,7 +1817,7 @@
     if (ioctl(fd, EVIOCGVERSION, &driverVersion)) {
         ALOGE("could not get driver version for %s, %s\n", devicePath.c_str(), strerror(errno));
         close(fd);
-        return -1;
+        return;
     }
 
     // Get device identifier.
@@ -1526,7 +1825,7 @@
     if (ioctl(fd, EVIOCGID, &inputId)) {
         ALOGE("could not get device input id for %s, %s\n", devicePath.c_str(), strerror(errno));
         close(fd);
-        return -1;
+        return;
     }
     identifier.bus = inputId.bustype;
     identifier.product = inputId.product;
@@ -1572,6 +1871,12 @@
     // Load the configuration file for the device.
     device->loadConfigurationLocked();
 
+    // Grab the device's sysfs path
+    device->sysfsRootPath = getSysfsRootPath(devicePath.c_str());
+    // find related components
+    bool hasBattery = device->configureBatteryLocked();
+    bool hasLights = device->configureLightsLocked();
+
     // Figure out the kinds of events the device reports.
     device->readDeviceBitMask(EVIOCGBIT(EV_KEY, 0), device->keyBitmask);
     device->readDeviceBitMask(EVIOCGBIT(EV_ABS, 0), device->absBitmask);
@@ -1718,19 +2023,17 @@
     if (device->classes == Flags<InputDeviceClass>(0)) {
         ALOGV("Dropping device: id=%d, path='%s', name='%s'", deviceId, devicePath.c_str(),
               device->identifier.name.c_str());
-        return -1;
+        return;
     }
 
-    // Grab the device's sysfs path
-    device->sysfsRootPath = getSysfsRootPath(devicePath.c_str());
+    // Classify InputDeviceClass::BATTERY.
+    if (hasBattery) {
+        device->classes |= InputDeviceClass::BATTERY;
+    }
 
-    if (!device->sysfsRootPath.empty()) {
-        device->sysfsBatteryPath = findPowerSupplyNode(device->sysfsRootPath);
-
-        // Check if a battery exists
-        if (!device->sysfsBatteryPath.empty()) {
-            device->classes |= InputDeviceClass::BATTERY;
-        }
+    // Classify InputDeviceClass::LIGHT.
+    if (hasLights) {
+        device->classes |= InputDeviceClass::LIGHT;
     }
 
     // Determine whether the device has a mic.
@@ -1750,7 +2053,7 @@
     }
 
     if (registerDeviceForEpollLocked(*device) != OK) {
-        return -1;
+        return;
     }
 
     device->configureFd();
@@ -1763,7 +2066,6 @@
           toString(mBuiltInKeyboardId == deviceId));
 
     addDeviceLocked(std::move(device));
-    return OK;
 }
 
 void EventHub::openVideoDeviceLocked(const std::string& devicePath) {
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 574f651..dd1abeb 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -27,6 +27,7 @@
 #include "InputReaderContext.h"
 #include "JoystickInputMapper.h"
 #include "KeyboardInputMapper.h"
+#include "LightInputMapper.h"
 #include "MultiTouchInputMapper.h"
 #include "RotaryEncoderInputMapper.h"
 #include "SensorInputMapper.h"
@@ -161,9 +162,22 @@
         mappers.push_back(std::make_unique<VibratorInputMapper>(*contextPtr));
     }
 
-    // Battery-like devices.
+    // Battery-like devices. Only one battery mapper for each EventHub device.
     if (classes.test(InputDeviceClass::BATTERY)) {
-        mappers.push_back(std::make_unique<BatteryInputMapper>(*contextPtr));
+        InputDeviceInfo deviceInfo;
+        getDeviceInfo(&deviceInfo);
+        if (!deviceInfo.hasBattery()) {
+            mappers.push_back(std::make_unique<BatteryInputMapper>(*contextPtr));
+        }
+    }
+
+    // Light-containing devices. Only one light mapper for each EventHub device.
+    if (classes.test(InputDeviceClass::LIGHT)) {
+        InputDeviceInfo deviceInfo;
+        getDeviceInfo(&deviceInfo);
+        if (deviceInfo.getLightIds().empty()) {
+            mappers.push_back(std::make_unique<LightInputMapper>(*contextPtr));
+        }
     }
 
     // Keyboard-like devices.
@@ -492,8 +506,8 @@
     for_each_mapper([sensorType](InputMapper& mapper) { mapper.flushSensor(sensorType); });
 }
 
-void InputDevice::cancelTouch(nsecs_t when) {
-    for_each_mapper([when](InputMapper& mapper) { mapper.cancelTouch(when); });
+void InputDevice::cancelTouch(nsecs_t when, nsecs_t readTime) {
+    for_each_mapper([when, readTime](InputMapper& mapper) { mapper.cancelTouch(when, readTime); });
 }
 
 std::optional<int32_t> InputDevice::getBatteryCapacity() {
@@ -505,6 +519,32 @@
     return first_in_mappers<int32_t>([](InputMapper& mapper) { return mapper.getBatteryStatus(); });
 }
 
+bool InputDevice::setLightColor(int32_t lightId, int32_t color) {
+    bool success = true;
+    for_each_mapper([&success, lightId, color](InputMapper& mapper) {
+        success &= mapper.setLightColor(lightId, color);
+    });
+    return success;
+}
+
+bool InputDevice::setLightPlayerId(int32_t lightId, int32_t playerId) {
+    bool success = true;
+    for_each_mapper([&success, lightId, playerId](InputMapper& mapper) {
+        success &= mapper.setLightPlayerId(lightId, playerId);
+    });
+    return success;
+}
+
+std::optional<int32_t> InputDevice::getLightColor(int32_t lightId) {
+    return first_in_mappers<int32_t>(
+            [lightId](InputMapper& mapper) { return mapper.getLightColor(lightId); });
+}
+
+std::optional<int32_t> InputDevice::getLightPlayerId(int32_t lightId) {
+    return first_in_mappers<int32_t>(
+            [lightId](InputMapper& mapper) { return mapper.getLightPlayerId(lightId); });
+}
+
 int32_t InputDevice::getMetaState() {
     int32_t result = 0;
     for_each_mapper([&result](InputMapper& mapper) { result |= mapper.getMetaState(); });
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 14fb77b..2d0fdf7 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -696,6 +696,70 @@
     return std::nullopt;
 }
 
+std::vector<int32_t> InputReader::getLightIds(int32_t deviceId) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device) {
+        InputDeviceInfo info;
+        device->getDeviceInfo(&info);
+        return info.getLightIds();
+    }
+    return {};
+}
+
+const InputDeviceLightInfo* InputReader::getLightInfo(int32_t deviceId, int32_t lightId) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device) {
+        InputDeviceInfo info;
+        device->getDeviceInfo(&info);
+        return info.getLightInfo(lightId);
+    }
+    return nullptr;
+}
+
+bool InputReader::setLightColor(int32_t deviceId, int32_t lightId, int32_t color) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device) {
+        return device->setLightColor(lightId, color);
+    }
+    return false;
+}
+
+bool InputReader::setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device) {
+        return device->setLightPlayerId(lightId, playerId);
+    }
+    return false;
+}
+
+std::optional<int32_t> InputReader::getLightColor(int32_t deviceId, int32_t lightId) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device) {
+        return device->getLightColor(lightId);
+    }
+    return std::nullopt;
+}
+
+std::optional<int32_t> InputReader::getLightPlayerId(int32_t deviceId, int32_t lightId) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device) {
+        return device->getLightPlayerId(lightId);
+    }
+    return std::nullopt;
+}
+
 bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
     std::scoped_lock _l(mLock);
 
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 30967df..5e5f85e 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -47,11 +47,17 @@
 
 namespace android {
 
+/* Number of colors : {red, green, blue} */
+static constexpr size_t COLOR_NUM = 3;
 /*
  * A raw event as retrieved from the EventHub.
  */
 struct RawEvent {
+    // Time when the event happened
     nsecs_t when;
+    // Time when the event was read by EventHub. Only populated for input events.
+    // For other events (device added/removed/etc), this value is undefined and should not be read.
+    nsecs_t readTime;
     int32_t deviceId;
     int32_t type;
     int32_t code;
@@ -127,6 +133,9 @@
     /* The input device has a battery */
     BATTERY = 0x00004000,
 
+    /* The input device has sysfs controllable lights */
+    LIGHT = 0x00008000,
+
     /* The input device is virtual (not a real device, not part of UI configuration). */
     VIRTUAL = 0x40000000,
 
@@ -134,6 +143,46 @@
     EXTERNAL = 0x80000000,
 };
 
+enum class SysfsClass : uint32_t {
+    POWER_SUPPLY = 0,
+    LEDS = 1,
+};
+
+enum class LightColor : uint32_t {
+    RED = 0,
+    GREEN = 1,
+    BLUE = 2,
+};
+
+enum class InputLightClass : uint32_t {
+    /* The input light has brightness node. */
+    BRIGHTNESS = 0x00000001,
+    /* The input light has red name. */
+    RED = 0x00000002,
+    /* The input light has green name. */
+    GREEN = 0x00000004,
+    /* The input light has blue name. */
+    BLUE = 0x00000008,
+    /* The input light has global name. */
+    GLOBAL = 0x00000010,
+    /* The input light has multi index node. */
+    MULTI_INDEX = 0x00000020,
+    /* The input light has multi intensity node. */
+    MULTI_INTENSITY = 0x00000040,
+    /* The input light has max brightness node. */
+    MAX_BRIGHTNESS = 0x00000080,
+};
+
+/* Describes a raw light. */
+struct RawLightInfo {
+    int32_t id;
+    std::string name;
+    std::optional<int32_t> maxBrightness;
+    Flags<InputLightClass> flags;
+    std::array<int32_t, COLOR_NUM> rgbIndex;
+    std::filesystem::path path;
+};
+
 /*
  * Gets the class that owns an axis, in cases where multiple classes might claim
  * the same axis for different purposes.
@@ -214,7 +263,16 @@
     virtual std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) = 0;
     virtual base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(int32_t deviceId,
                                                                               int32_t absCode) = 0;
-
+    // Raw lights are sysfs led light nodes we found from the EventHub device sysfs node,
+    // containing the raw info of the sysfs node structure.
+    virtual const std::vector<int32_t> getRawLightIds(int32_t deviceId) = 0;
+    virtual std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) = 0;
+    virtual std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) = 0;
+    virtual void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) = 0;
+    virtual std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
+            int32_t deviceId, int32_t lightId) = 0;
+    virtual void setLightIntensities(int32_t deviceId, int32_t lightId,
+                                     std::unordered_map<LightColor, int32_t> intensities) = 0;
     /*
      * Query current input state.
      */
@@ -377,6 +435,17 @@
     base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(
             int32_t deviceId, int32_t absCode) override final;
 
+    const std::vector<int32_t> getRawLightIds(int32_t deviceId) override final;
+
+    std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) override final;
+
+    std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) override final;
+    void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override final;
+    std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
+            int32_t deviceId, int32_t lightId) override final;
+    void setLightIntensities(int32_t deviceId, int32_t lightId,
+                             std::unordered_map<LightColor, int32_t> intensities) override final;
+
     void setExcludedDevices(const std::vector<std::string>& devices) override final;
 
     int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override final;
@@ -458,9 +527,12 @@
         bool ffEffectPlaying;
         int16_t ffEffectId; // initially -1
 
-        // The paths are invalid when .empty() returns true
-        std::filesystem::path sysfsRootPath;
-        std::filesystem::path sysfsBatteryPath;
+        // The paths are invalid when they're std::nullopt
+        std::optional<std::filesystem::path> sysfsRootPath;
+        std::optional<std::filesystem::path> sysfsBatteryPath;
+        // maps from light id to light info
+        std::unordered_map<int32_t, RawLightInfo> lightInfos;
+        int32_t nextLightId;
 
         int32_t controllerNumber;
 
@@ -491,9 +563,14 @@
         void setLedForControllerLocked();
         status_t mapLed(int32_t led, int32_t* outScanCode) const;
         void setLedStateLocked(int32_t led, bool on);
+        bool configureBatteryLocked();
+        bool configureLightsLocked();
     };
 
-    status_t openDeviceLocked(const std::string& devicePath);
+    /**
+     * Create a new device for the provided path.
+     */
+    void openDeviceLocked(const std::string& devicePath);
     void openVideoDeviceLocked(const std::string& devicePath);
     /**
      * Try to associate a video device with an input device. If the association succeeds,
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index e4186c8..863cd41 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -86,7 +86,7 @@
     void cancelVibrate(int32_t token);
     bool isVibrating();
     std::vector<int32_t> getVibratorIds();
-    void cancelTouch(nsecs_t when);
+    void cancelTouch(nsecs_t when, nsecs_t readTime);
     bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod,
                       std::chrono::microseconds maxBatchReportLatency);
     void disableSensor(InputDeviceSensorType sensorType);
@@ -95,6 +95,11 @@
     std::optional<int32_t> getBatteryCapacity();
     std::optional<int32_t> getBatteryStatus();
 
+    bool setLightColor(int32_t lightId, int32_t color);
+    bool setLightPlayerId(int32_t lightId, int32_t playerId);
+    std::optional<int32_t> getLightColor(int32_t lightId);
+    std::optional<int32_t> getLightPlayerId(int32_t lightId);
+
     int32_t getMetaState();
     void updateMetaState(int32_t keyCode);
 
@@ -254,6 +259,30 @@
         return mEventHub->mapSensor(mId, absCode);
     }
 
+    inline const std::vector<int32_t> getRawLightIds() { return mEventHub->getRawLightIds(mId); }
+
+    inline std::optional<RawLightInfo> getRawLightInfo(int32_t lightId) {
+        return mEventHub->getRawLightInfo(mId, lightId);
+    }
+
+    inline std::optional<int32_t> getLightBrightness(int32_t lightId) {
+        return mEventHub->getLightBrightness(mId, lightId);
+    }
+
+    inline void setLightBrightness(int32_t lightId, int32_t brightness) {
+        return mEventHub->setLightBrightness(mId, lightId, brightness);
+    }
+
+    inline std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
+            int32_t lightId) {
+        return mEventHub->getLightIntensities(mId, lightId);
+    }
+
+    inline void setLightIntensities(int32_t lightId,
+                                    std::unordered_map<LightColor, int32_t> intensities) {
+        return mEventHub->setLightIntensities(mId, lightId, intensities);
+    }
+
     inline std::vector<TouchVideoFrame> getVideoFrames() { return mEventHub->getVideoFrames(mId); }
     inline int32_t getScanCodeState(int32_t scanCode) const {
         return mEventHub->getScanCodeState(mId, scanCode);
@@ -322,7 +351,7 @@
     inline std::optional<DisplayViewport> getAssociatedViewport() const {
         return mDevice.getAssociatedViewport();
     }
-    inline void cancelTouch(nsecs_t when) { mDevice.cancelTouch(when); }
+    inline void cancelTouch(nsecs_t when, nsecs_t readTime) { mDevice.cancelTouch(when, readTime); }
     inline void bumpGeneration() { mDevice.bumpGeneration(); }
     inline const PropertyMap& getConfiguration() { return mDevice.getConfiguration(); }
 
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 81e3e9a..1405671 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -99,6 +99,18 @@
 
     std::optional<int32_t> getBatteryStatus(int32_t deviceId) override;
 
+    std::vector<int32_t> getLightIds(int32_t deviceId) override;
+
+    const InputDeviceLightInfo* getLightInfo(int32_t deviceId, int32_t lightId) override;
+
+    bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) override;
+
+    bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) override;
+
+    std::optional<int32_t> getLightColor(int32_t deviceId, int32_t lightId) override;
+
+    std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) override;
+
 protected:
     // These members are protected so they can be instrumented by test cases.
     virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId,
diff --git a/services/inputflinger/reader/mapper/BatteryInputMapper.cpp b/services/inputflinger/reader/mapper/BatteryInputMapper.cpp
index afdc5ab..e4fb3a6 100644
--- a/services/inputflinger/reader/mapper/BatteryInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/BatteryInputMapper.cpp
@@ -24,7 +24,7 @@
       : InputMapper(deviceContext) {}
 
 uint32_t BatteryInputMapper::getSources() {
-    return 0;
+    return AINPUT_SOURCE_UNKNOWN;
 }
 
 void BatteryInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 254b64b..bb12be7 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -265,11 +265,11 @@
     mCursorScrollAccumulator.process(rawEvent);
 
     if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        sync(rawEvent->when);
+        sync(rawEvent->when, rawEvent->readTime);
     }
 }
 
-void CursorInputMapper::sync(nsecs_t when) {
+void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) {
     int32_t lastButtonState = mButtonState;
     int32_t currentButtonState = mCursorButtonAccumulator.getButtonState();
     mButtonState = currentButtonState;
@@ -362,8 +362,8 @@
     }
 
     // Synthesize key down from buttons if needed.
-    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
-                         displayId, policyFlags, lastButtonState, currentButtonState);
+    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(),
+                         mSource, displayId, policyFlags, lastButtonState, currentButtonState);
 
     // Send motion event.
     if (downChanged || moved || scrolled || buttonsChanged) {
@@ -383,8 +383,8 @@
             while (!released.isEmpty()) {
                 int32_t actionButton = BitSet32::valueForBit(released.clearFirstMarkedBit());
                 buttonState &= ~actionButton;
-                NotifyMotionArgs releaseArgs(getContext()->getNextId(), when, getDeviceId(),
-                                             mSource, displayId, policyFlags,
+                NotifyMotionArgs releaseArgs(getContext()->getNextId(), when, readTime,
+                                             getDeviceId(), mSource, displayId, policyFlags,
                                              AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0,
                                              metaState, buttonState, MotionClassification::NONE,
                                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
@@ -395,11 +395,11 @@
             }
         }
 
-        NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
-                              policyFlags, motionEventAction, 0, 0, metaState, currentButtonState,
-                              MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
-                              &pointerProperties, &pointerCoords, mXPrecision, mYPrecision,
-                              xCursorPosition, yCursorPosition, downTime,
+        NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
+                              displayId, policyFlags, motionEventAction, 0, 0, metaState,
+                              currentButtonState, MotionClassification::NONE,
+                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords,
+                              mXPrecision, mYPrecision, xCursorPosition, yCursorPosition, downTime,
                               /* videoFrames */ {});
         getListener()->notifyMotion(&args);
 
@@ -408,8 +408,8 @@
             while (!pressed.isEmpty()) {
                 int32_t actionButton = BitSet32::valueForBit(pressed.clearFirstMarkedBit());
                 buttonState |= actionButton;
-                NotifyMotionArgs pressArgs(getContext()->getNextId(), when, getDeviceId(), mSource,
-                                           displayId, policyFlags,
+                NotifyMotionArgs pressArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
+                                           mSource, displayId, policyFlags,
                                            AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0,
                                            metaState, buttonState, MotionClassification::NONE,
                                            AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
@@ -424,9 +424,10 @@
 
         // Send hover move after UP to tell the application that the mouse is hovering now.
         if (motionEventAction == AMOTION_EVENT_ACTION_UP && (mSource == AINPUT_SOURCE_MOUSE)) {
-            NotifyMotionArgs hoverArgs(getContext()->getNextId(), when, getDeviceId(), mSource,
-                                       displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
-                                       0, metaState, currentButtonState, MotionClassification::NONE,
+            NotifyMotionArgs hoverArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
+                                       mSource, displayId, policyFlags,
+                                       AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
+                                       currentButtonState, MotionClassification::NONE,
                                        AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                        &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
                                        yCursorPosition, downTime, /* videoFrames */ {});
@@ -438,9 +439,10 @@
             pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
             pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
 
-            NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, getDeviceId(), mSource,
-                                        displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
-                                        metaState, currentButtonState, MotionClassification::NONE,
+            NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
+                                        mSource, displayId, policyFlags,
+                                        AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState,
+                                        currentButtonState, MotionClassification::NONE,
                                         AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                         &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
                                         yCursorPosition, downTime, /* videoFrames */ {});
@@ -449,7 +451,7 @@
     }
 
     // Synthesize key up from buttons if needed.
-    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
+    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), mSource,
                          displayId, policyFlags, lastButtonState, currentButtonState);
 
     mCursorMotionAccumulator.finishSync();
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 05bbb26..9a8ca01 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -114,7 +114,7 @@
     void configureParameters();
     void dumpParameters(std::string& dump);
 
-    void sync(nsecs_t when);
+    void sync(nsecs_t when, nsecs_t readTime);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index 1ce54ae..df1acd4 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -68,7 +68,7 @@
     return {};
 }
 
-void InputMapper::cancelTouch(nsecs_t when) {}
+void InputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) {}
 
 bool InputMapper::enableSensor(InputDeviceSensorType sensorType,
                                std::chrono::microseconds samplingPeriod,
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index 1cc5979..15cff1c 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -67,7 +67,7 @@
     virtual void cancelVibrate(int32_t token);
     virtual bool isVibrating();
     virtual std::vector<int32_t> getVibratorIds();
-    virtual void cancelTouch(nsecs_t when);
+    virtual void cancelTouch(nsecs_t when, nsecs_t readTime);
     virtual bool enableSensor(InputDeviceSensorType sensorType,
                               std::chrono::microseconds samplingPeriod,
                               std::chrono::microseconds maxBatchReportLatency);
@@ -77,6 +77,11 @@
     virtual std::optional<int32_t> getBatteryCapacity() { return std::nullopt; }
     virtual std::optional<int32_t> getBatteryStatus() { return std::nullopt; }
 
+    virtual bool setLightColor(int32_t lightId, int32_t color) { return true; }
+    virtual bool setLightPlayerId(int32_t lightId, int32_t playerId) { return true; }
+    virtual std::optional<int32_t> getLightColor(int32_t lightId) { return std::nullopt; }
+    virtual std::optional<int32_t> getLightPlayerId(int32_t lightId) { return std::nullopt; }
+
     virtual int32_t getMetaState();
     virtual void updateMetaState(int32_t keyCode);
 
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index 37aa140..0dc312e 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -299,14 +299,14 @@
         case EV_SYN:
             switch (rawEvent->code) {
                 case SYN_REPORT:
-                    sync(rawEvent->when, false /*force*/);
+                    sync(rawEvent->when, rawEvent->readTime, false /*force*/);
                     break;
             }
             break;
     }
 }
 
-void JoystickInputMapper::sync(nsecs_t when, bool force) {
+void JoystickInputMapper::sync(nsecs_t when, nsecs_t readTime, bool force) {
     if (!filterAxes(force)) {
         return;
     }
@@ -337,9 +337,10 @@
     // TODO: Use the input device configuration to control this behavior more finely.
     uint32_t policyFlags = 0;
 
-    NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), AINPUT_SOURCE_JOYSTICK,
-                          ADISPLAY_ID_NONE, policyFlags, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
-                          buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+    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,
                           AMOTION_EVENT_INVALID_CURSOR_POSITION,
                           AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {});
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h
index 0cf60a2..bba95ad 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.h
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h
@@ -92,7 +92,7 @@
     // Axes indexed by raw ABS_* axis index.
     std::unordered_map<int32_t, Axis> mAxes;
 
-    void sync(nsecs_t when, bool force);
+    void sync(nsecs_t when, nsecs_t readTime, bool force);
 
     bool haveAxis(int32_t axisId);
     void pruneAxes(bool ignoreExplicitlyMappedAxes);
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 8b9f235..2c5a576 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -216,7 +216,8 @@
             mCurrentHidUsage = 0;
 
             if (isKeyboardOrGamepadKey(scanCode)) {
-                processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);
+                processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0, scanCode,
+                           usageCode);
             }
             break;
         }
@@ -269,7 +270,8 @@
     return false;
 }
 
-void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode, int32_t usageCode) {
+void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode,
+                                     int32_t usageCode) {
     int32_t keyCode;
     int32_t keyMetaState;
     uint32_t policyFlags;
@@ -299,7 +301,7 @@
                 return;
             }
             if (policyFlags & POLICY_FLAG_GESTURE) {
-                getDeviceContext().cancelTouch(when);
+                getDeviceContext().cancelTouch(when, readTime);
             }
 
             KeyDown keyDown;
@@ -350,8 +352,9 @@
         policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
     }
 
-    NotifyKeyArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, getDisplayId(),
-                       policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
+    NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
+                       getDisplayId(), policyFlags,
+                       down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
                        AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
     getListener()->notifyKey(&args);
 }
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 4c0b42a..ca41712 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -86,7 +86,7 @@
     bool isKeyboardOrGamepadKey(int32_t scanCode);
     bool isMediaKey(int32_t keyCode);
 
-    void processKey(nsecs_t when, bool down, int32_t scanCode, int32_t usageCode);
+    void processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode, int32_t usageCode);
 
     bool updateMetaStateIfNeeded(int32_t keyCode, bool down);
 
diff --git a/services/inputflinger/reader/mapper/LightInputMapper.cpp b/services/inputflinger/reader/mapper/LightInputMapper.cpp
new file mode 100644
index 0000000..be1f722
--- /dev/null
+++ b/services/inputflinger/reader/mapper/LightInputMapper.cpp
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <locale>
+#include <regex>
+
+#include "../Macros.h"
+
+#include "LightInputMapper.h"
+#include "input/NamedEnum.h"
+
+// Log detailed debug messages about input device lights.
+static constexpr bool DEBUG_LIGHT_DETAILS = false;
+
+namespace android {
+
+static inline int32_t getAlpha(int32_t color) {
+    return (color >> 24) & 0xff;
+}
+
+static inline int32_t getRed(int32_t color) {
+    return (color >> 16) & 0xff;
+}
+
+static inline int32_t getGreen(int32_t color) {
+    return (color >> 8) & 0xff;
+}
+
+static inline int32_t getBlue(int32_t color) {
+    return color & 0xff;
+}
+
+static inline int32_t toArgb(int32_t brightness, int32_t red, int32_t green, int32_t blue) {
+    return (brightness & 0xff) << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
+}
+
+/**
+ * Light input mapper owned by InputReader device, implements the native API for querying input
+ * lights, getting and setting the lights brightness and color, by interacting with EventHub
+ * devices.
+ * TODO b/180342233: Reconsider the inputflinger design to accommodate the device class
+ * like lights and battery.
+ */
+LightInputMapper::LightInputMapper(InputDeviceContext& deviceContext)
+      : InputMapper(deviceContext) {}
+
+LightInputMapper::~LightInputMapper() {}
+
+std::optional<std::int32_t> LightInputMapper::Light::getRawLightBrightness(int32_t rawLightId) {
+    std::optional<RawLightInfo> rawInfo = context.getRawLightInfo(rawLightId);
+    std::optional<int32_t> ret = context.getLightBrightness(rawLightId);
+    if (!rawInfo.has_value() || !ret.has_value()) {
+        return std::nullopt;
+    }
+    int brightness = ret.value();
+
+    // If the light node doesn't have max brightness, use the default max brightness.
+    int rawMaxBrightness = rawInfo->maxBrightness.value_or(MAX_BRIGHTNESS);
+    float ratio = MAX_BRIGHTNESS / rawMaxBrightness;
+    // Scale the returned brightness in [0, rawMaxBrightness] to [0, 255]
+    if (rawMaxBrightness != MAX_BRIGHTNESS) {
+        brightness = brightness * ratio;
+    }
+    if (DEBUG_LIGHT_DETAILS) {
+        ALOGD("getRawLightBrightness rawLightId %d brightness 0x%x ratio %.2f", rawLightId,
+              brightness, ratio);
+    }
+    return brightness;
+}
+
+void LightInputMapper::Light::setRawLightBrightness(int32_t rawLightId, int32_t brightness) {
+    std::optional<RawLightInfo> rawInfo = context.getRawLightInfo(rawLightId);
+    if (!rawInfo.has_value()) {
+        return;
+    }
+    // If the light node doesn't have max brightness, use the default max brightness.
+    int rawMaxBrightness = rawInfo->maxBrightness.value_or(MAX_BRIGHTNESS);
+    float ratio = MAX_BRIGHTNESS / rawMaxBrightness;
+    // Scale the requested brightness in [0, 255] to [0, rawMaxBrightness]
+    if (rawMaxBrightness != MAX_BRIGHTNESS) {
+        brightness = ceil(brightness / ratio);
+    }
+    if (DEBUG_LIGHT_DETAILS) {
+        ALOGD("setRawLightBrightness rawLightId %d brightness 0x%x ratio %.2f", rawLightId,
+              brightness, ratio);
+    }
+    context.setLightBrightness(rawLightId, brightness);
+}
+
+bool LightInputMapper::SingleLight::setLightColor(int32_t color) {
+    int32_t brightness = getAlpha(color);
+    setRawLightBrightness(rawId, brightness);
+
+    return true;
+}
+
+bool LightInputMapper::RgbLight::setLightColor(int32_t color) {
+    // Compose color value as per:
+    // https://developer.android.com/reference/android/graphics/Color?hl=en
+    // int color = (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 8 | (B & 0xff);
+    // The alpha component is used to scale the R,G,B leds brightness, with the ratio to
+    // MAX_BRIGHTNESS.
+    brightness = getAlpha(color);
+    int32_t red = 0;
+    int32_t green = 0;
+    int32_t blue = 0;
+    if (brightness > 0) {
+        float ratio = MAX_BRIGHTNESS / brightness;
+        red = ceil(getRed(color) / ratio);
+        green = ceil(getGreen(color) / ratio);
+        blue = ceil(getBlue(color) / ratio);
+    }
+    setRawLightBrightness(rawRgbIds.at(LightColor::RED), red);
+    setRawLightBrightness(rawRgbIds.at(LightColor::GREEN), green);
+    setRawLightBrightness(rawRgbIds.at(LightColor::BLUE), blue);
+    if (rawGlobalId.has_value()) {
+        setRawLightBrightness(rawGlobalId.value(), brightness);
+    }
+
+    return true;
+}
+
+bool LightInputMapper::MultiColorLight::setLightColor(int32_t color) {
+    std::unordered_map<LightColor, int32_t> intensities;
+    intensities.emplace(LightColor::RED, getRed(color));
+    intensities.emplace(LightColor::GREEN, getGreen(color));
+    intensities.emplace(LightColor::BLUE, getBlue(color));
+
+    context.setLightIntensities(rawId, intensities);
+    setRawLightBrightness(rawId, getAlpha(color));
+    return true;
+}
+
+std::optional<int32_t> LightInputMapper::SingleLight::getLightColor() {
+    std::optional<int32_t> brightness = getRawLightBrightness(rawId);
+    if (!brightness.has_value()) {
+        return std::nullopt;
+    }
+
+    return toArgb(brightness.value(), 0 /* red */, 0 /* green */, 0 /* blue */);
+}
+
+std::optional<int32_t> LightInputMapper::RgbLight::getLightColor() {
+    // If the Alpha component is zero, then return color 0.
+    if (brightness == 0) {
+        return 0;
+    }
+    // Compose color value as per:
+    // https://developer.android.com/reference/android/graphics/Color?hl=en
+    // int color = (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 8 | (B & 0xff);
+    std::optional<int32_t> redOr = getRawLightBrightness(rawRgbIds.at(LightColor::RED));
+    std::optional<int32_t> greenOr = getRawLightBrightness(rawRgbIds.at(LightColor::GREEN));
+    std::optional<int32_t> blueOr = getRawLightBrightness(rawRgbIds.at(LightColor::BLUE));
+    // If we can't get brightness for any of the RGB light
+    if (!redOr.has_value() || !greenOr.has_value() || !blueOr.has_value()) {
+        return std::nullopt;
+    }
+
+    // Compose the ARGB format color. As the R,G,B color led brightness is scaled by Alpha
+    // value, scale it back to return the nominal color value.
+    float ratio = MAX_BRIGHTNESS / brightness;
+    int32_t red = round(redOr.value() * ratio);
+    int32_t green = round(greenOr.value() * ratio);
+    int32_t blue = round(blueOr.value() * ratio);
+
+    if (red > MAX_BRIGHTNESS || green > MAX_BRIGHTNESS || blue > MAX_BRIGHTNESS) {
+        // Previously stored brightness isn't valid for current LED values, so just reset to max
+        // brightness since an app couldn't have provided these values in the first place.
+        red = redOr.value();
+        green = greenOr.value();
+        blue = blueOr.value();
+        brightness = MAX_BRIGHTNESS;
+    }
+
+    return toArgb(brightness, red, green, blue);
+}
+
+std::optional<int32_t> LightInputMapper::MultiColorLight::getLightColor() {
+    auto ret = context.getLightIntensities(rawId);
+    if (!ret.has_value()) {
+        return std::nullopt;
+    }
+    std::unordered_map<LightColor, int32_t> intensities = ret.value();
+    // Get red, green, blue colors
+    int32_t color = toArgb(0 /* brightness */, intensities.at(LightColor::RED) /* red */,
+                           intensities.at(LightColor::GREEN) /* green */,
+                           intensities.at(LightColor::BLUE) /* blue */);
+    // Get brightness
+    std::optional<int32_t> brightness = getRawLightBrightness(rawId);
+    if (brightness.has_value()) {
+        return toArgb(brightness.value() /* A */, 0, 0, 0) | color;
+    }
+    return std::nullopt;
+}
+
+bool LightInputMapper::PlayerIdLight::setLightPlayerId(int32_t playerId) {
+    if (rawLightIds.find(playerId) == rawLightIds.end()) {
+        return false;
+    }
+    for (const auto& [id, rawId] : rawLightIds) {
+        if (playerId == id) {
+            setRawLightBrightness(rawId, MAX_BRIGHTNESS);
+        } else {
+            setRawLightBrightness(rawId, 0);
+        }
+    }
+    return true;
+}
+
+std::optional<int32_t> LightInputMapper::PlayerIdLight::getLightPlayerId() {
+    for (const auto& [id, rawId] : rawLightIds) {
+        std::optional<int32_t> brightness = getRawLightBrightness(rawId);
+        if (brightness.has_value() && brightness.value() > 0) {
+            return id;
+        }
+    }
+    return std::nullopt;
+}
+
+void LightInputMapper::SingleLight::dump(std::string& dump) {
+    dump += StringPrintf(INDENT4 "Color: 0x%x\n", getLightColor().value_or(0));
+}
+
+void LightInputMapper::PlayerIdLight::dump(std::string& dump) {
+    dump += StringPrintf(INDENT4 "PlayerId: %d\n", getLightPlayerId().value_or(-1));
+    dump += StringPrintf(INDENT4 "Raw Player ID LEDs:");
+    for (const auto& [id, rawId] : rawLightIds) {
+        dump += StringPrintf("id %d -> %d ", id, rawId);
+    }
+    dump += "\n";
+}
+
+void LightInputMapper::RgbLight::dump(std::string& dump) {
+    dump += StringPrintf(INDENT4 "Color: 0x%x\n", getLightColor().value_or(0));
+    dump += StringPrintf(INDENT4 "Raw RGB LEDs: [%d, %d, %d] ", rawRgbIds.at(LightColor::RED),
+                         rawRgbIds.at(LightColor::GREEN), rawRgbIds.at(LightColor::BLUE));
+    if (rawGlobalId.has_value()) {
+        dump += StringPrintf(INDENT4 "Raw Global LED: [%d] ", rawGlobalId.value());
+    }
+    dump += "\n";
+}
+
+void LightInputMapper::MultiColorLight::dump(std::string& dump) {
+    dump += StringPrintf(INDENT4 "Color: 0x%x\n", getLightColor().value_or(0));
+}
+
+uint32_t LightInputMapper::getSources() {
+    return AINPUT_SOURCE_UNKNOWN;
+}
+
+void LightInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    for (const auto& [lightId, light] : mLights) {
+        // Input device light doesn't support ordinal, always pass 1.
+        InputDeviceLightInfo lightInfo(light->name, light->id, light->type, 1 /* ordinal */);
+        info->addLightInfo(lightInfo);
+    }
+}
+
+void LightInputMapper::dump(std::string& dump) {
+    dump += INDENT2 "Light Input Mapper:\n";
+    dump += INDENT3 "Lights:\n";
+    for (const auto& [lightId, light] : mLights) {
+        dump += StringPrintf(INDENT4 "Id: %d", lightId);
+        dump += StringPrintf(INDENT4 "Name: %s", light->name.c_str());
+        dump += StringPrintf(INDENT4 "Type: %s", NamedEnum::string(light->type).c_str());
+        light->dump(dump);
+    }
+    // Dump raw lights
+    dump += INDENT3 "RawLights:\n";
+    dump += INDENT4 "Id:\t Name:\t Flags:\t Max brightness:\t Brightness\n";
+    const std::vector<int32_t> rawLightIds = getDeviceContext().getRawLightIds();
+    // Map from raw light id to raw light info
+    std::unordered_map<int32_t, RawLightInfo> rawInfos;
+    for (const auto& rawId : rawLightIds) {
+        std::optional<RawLightInfo> rawInfo = getDeviceContext().getRawLightInfo(rawId);
+        if (!rawInfo.has_value()) {
+            continue;
+        }
+        dump += StringPrintf(INDENT4 "%d", rawId);
+        dump += StringPrintf(INDENT4 "%s", rawInfo->name.c_str());
+        dump += StringPrintf(INDENT4 "%s", rawInfo->flags.string().c_str());
+        dump += StringPrintf(INDENT4 "%d", rawInfo->maxBrightness.value_or(MAX_BRIGHTNESS));
+        dump += StringPrintf(INDENT4 "%d\n",
+                             getDeviceContext().getLightBrightness(rawId).value_or(-1));
+    }
+}
+
+void LightInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
+                                 uint32_t changes) {
+    InputMapper::configure(when, config, changes);
+
+    if (!changes) { // first time only
+        bool hasRedLed = false;
+        bool hasGreenLed = false;
+        bool hasBlueLed = false;
+        std::optional<int32_t> rawGlobalId = std::nullopt;
+        // Player ID light common name string
+        std::string playerIdName;
+        // Raw RGB color to raw light ID
+        std::unordered_map<LightColor, int32_t /* rawLightId */> rawRgbIds;
+        // Map from player Id to raw light Id
+        std::unordered_map<int32_t, int32_t> playerIdLightIds;
+        mLights.clear();
+
+        // Check raw lights
+        const std::vector<int32_t> rawLightIds = getDeviceContext().getRawLightIds();
+        // Map from raw light id to raw light info
+        std::unordered_map<int32_t, RawLightInfo> rawInfos;
+        for (const auto& rawId : rawLightIds) {
+            std::optional<RawLightInfo> rawInfo = getDeviceContext().getRawLightInfo(rawId);
+            if (!rawInfo.has_value()) {
+                continue;
+            }
+            rawInfos.insert_or_assign(rawId, rawInfo.value());
+            // Check if this is a group LEDs for player ID
+            std::regex lightPattern("([a-z]+)([0-9]+)");
+            std::smatch results;
+            if (std::regex_match(rawInfo->name, results, lightPattern)) {
+                std::string commonName = results[1].str();
+                int32_t playerId = std::stoi(results[2]);
+                if (playerIdLightIds.empty()) {
+                    playerIdName = commonName;
+                    playerIdLightIds.insert_or_assign(playerId, rawId);
+                } else {
+                    // Make sure the player ID leds have common string name
+                    if (playerIdName.compare(commonName) == 0 &&
+                        playerIdLightIds.find(playerId) == playerIdLightIds.end()) {
+                        playerIdLightIds.insert_or_assign(playerId, rawId);
+                    }
+                }
+            }
+            // Check if this is an LED of RGB light
+            if (rawInfo->flags.test(InputLightClass::RED)) {
+                hasRedLed = true;
+                rawRgbIds.emplace(LightColor::RED, rawId);
+            }
+            if (rawInfo->flags.test(InputLightClass::GREEN)) {
+                hasGreenLed = true;
+                rawRgbIds.emplace(LightColor::GREEN, rawId);
+            }
+            if (rawInfo->flags.test(InputLightClass::BLUE)) {
+                hasBlueLed = true;
+                rawRgbIds.emplace(LightColor::BLUE, rawId);
+            }
+            if (rawInfo->flags.test(InputLightClass::GLOBAL)) {
+                rawGlobalId = rawId;
+            }
+            if (DEBUG_LIGHT_DETAILS) {
+                ALOGD("Light rawId %d name %s max %d flags %s \n", rawInfo->id,
+                      rawInfo->name.c_str(), rawInfo->maxBrightness.value_or(MAX_BRIGHTNESS),
+                      rawInfo->flags.string().c_str());
+            }
+        }
+
+        // Construct a player ID light
+        if (playerIdLightIds.size() > 1) {
+            std::unique_ptr<Light> light =
+                    std::make_unique<PlayerIdLight>(getDeviceContext(), playerIdName, ++mNextId,
+                                                    playerIdLightIds);
+            mLights.insert_or_assign(light->id, std::move(light));
+            // Remove these raw lights from raw light info as they've been used to compose a
+            // Player ID light, so we do not expose these raw lights as single lights.
+            for (const auto& [playerId, rawId] : playerIdLightIds) {
+                rawInfos.erase(rawId);
+            }
+        }
+        // Construct a RGB light for composed RGB light
+        if (hasRedLed && hasGreenLed && hasBlueLed) {
+            if (DEBUG_LIGHT_DETAILS) {
+                ALOGD("Rgb light ids [%d, %d, %d] \n", rawRgbIds.at(LightColor::RED),
+                      rawRgbIds.at(LightColor::GREEN), rawRgbIds.at(LightColor::BLUE));
+            }
+            std::unique_ptr<Light> light = std::make_unique<RgbLight>(getDeviceContext(), ++mNextId,
+                                                                      rawRgbIds, rawGlobalId);
+            mLights.insert_or_assign(light->id, std::move(light));
+            // Remove from raw light info as they've been composed a RBG light.
+            rawInfos.erase(rawRgbIds.at(LightColor::RED));
+            rawInfos.erase(rawRgbIds.at(LightColor::GREEN));
+            rawInfos.erase(rawRgbIds.at(LightColor::BLUE));
+            if (rawGlobalId.has_value()) {
+                rawInfos.erase(rawGlobalId.value());
+            }
+        }
+
+        // Check the rest of raw light infos
+        for (const auto& [rawId, rawInfo] : rawInfos) {
+            // If the node is multi-color led, construct a MULTI_COLOR light
+            if (rawInfo.flags.test(InputLightClass::MULTI_INDEX) &&
+                rawInfo.flags.test(InputLightClass::MULTI_INTENSITY)) {
+                if (DEBUG_LIGHT_DETAILS) {
+                    ALOGD("Multicolor light Id %d name %s \n", rawInfo.id, rawInfo.name.c_str());
+                }
+                std::unique_ptr<Light> light =
+                        std::make_unique<MultiColorLight>(getDeviceContext(), rawInfo.name,
+                                                          ++mNextId, rawInfo.id);
+                mLights.insert_or_assign(light->id, std::move(light));
+                continue;
+            }
+            // Construct a single LED light
+            if (DEBUG_LIGHT_DETAILS) {
+                ALOGD("Single light Id %d name %s \n", rawInfo.id, rawInfo.name.c_str());
+            }
+            std::unique_ptr<Light> light =
+                    std::make_unique<SingleLight>(getDeviceContext(), rawInfo.name, ++mNextId,
+                                                  rawInfo.id);
+
+            mLights.insert_or_assign(light->id, std::move(light));
+        }
+    }
+}
+
+void LightInputMapper::reset(nsecs_t when) {
+    InputMapper::reset(when);
+}
+
+void LightInputMapper::process(const RawEvent* rawEvent) {}
+
+bool LightInputMapper::setLightColor(int32_t lightId, int32_t color) {
+    auto it = mLights.find(lightId);
+    if (it == mLights.end()) {
+        return false;
+    }
+    auto& light = it->second;
+    if (DEBUG_LIGHT_DETAILS) {
+        ALOGD("setLightColor lightId %d type %s color 0x%x", lightId,
+              NamedEnum::string(light->type).c_str(), color);
+    }
+    return light->setLightColor(color);
+}
+
+std::optional<int32_t> LightInputMapper::getLightColor(int32_t lightId) {
+    auto it = mLights.find(lightId);
+    if (it == mLights.end()) {
+        return std::nullopt;
+    }
+    auto& light = it->second;
+    std::optional<int32_t> color = light->getLightColor();
+    if (DEBUG_LIGHT_DETAILS) {
+        ALOGD("getLightColor lightId %d type %s color 0x%x", lightId,
+              NamedEnum::string(light->type).c_str(), color.value_or(0));
+    }
+    return color;
+}
+
+bool LightInputMapper::setLightPlayerId(int32_t lightId, int32_t playerId) {
+    auto it = mLights.find(lightId);
+    if (it == mLights.end()) {
+        return false;
+    }
+    auto& light = it->second;
+    return light->setLightPlayerId(playerId);
+}
+
+std::optional<int32_t> LightInputMapper::getLightPlayerId(int32_t lightId) {
+    auto it = mLights.find(lightId);
+    if (it == mLights.end()) {
+        return std::nullopt;
+    }
+    auto& light = it->second;
+    return light->getLightPlayerId();
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/LightInputMapper.h b/services/inputflinger/reader/mapper/LightInputMapper.h
new file mode 100644
index 0000000..43141b8
--- /dev/null
+++ b/services/inputflinger/reader/mapper/LightInputMapper.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUTREADER_LIGHT_INPUT_MAPPER_H
+#define _UI_INPUTREADER_LIGHT_INPUT_MAPPER_H
+
+#include "InputMapper.h"
+
+namespace android {
+
+class LightInputMapper : public InputMapper {
+    // Refer to https://developer.android.com/reference/kotlin/android/graphics/Color
+    /* Number of colors : {red, green, blue} */
+    static constexpr size_t COLOR_NUM = 3;
+    static constexpr int32_t MAX_BRIGHTNESS = 0xff;
+
+public:
+    explicit LightInputMapper(InputDeviceContext& deviceContext);
+    ~LightInputMapper() override;
+
+    uint32_t getSources() override;
+    void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
+    void dump(std::string& dump) override;
+    void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override;
+    void reset(nsecs_t when) override;
+    void process(const RawEvent* rawEvent) override;
+    bool setLightColor(int32_t lightId, int32_t color) override;
+    bool setLightPlayerId(int32_t lightId, int32_t playerId) override;
+    std::optional<int32_t> getLightColor(int32_t lightId) override;
+    std::optional<int32_t> getLightPlayerId(int32_t lightId) override;
+
+private:
+    struct Light {
+        explicit Light(InputDeviceContext& context, const std::string& name, int32_t id,
+                       InputDeviceLightType type)
+              : context(context), name(name), id(id), type(type) {}
+        virtual ~Light() {}
+        InputDeviceContext& context;
+        std::string name;
+        int32_t id;
+        InputDeviceLightType type;
+
+        virtual bool setLightColor(int32_t color) { return false; }
+        virtual std::optional<int32_t> getLightColor() { return std::nullopt; }
+        virtual bool setLightPlayerId(int32_t playerId) { return false; }
+        virtual std::optional<int32_t> getLightPlayerId() { return std::nullopt; }
+
+        virtual void dump(std::string& dump) {}
+
+        std::optional<std::int32_t> getRawLightBrightness(int32_t rawLightId);
+        void setRawLightBrightness(int32_t rawLightId, int32_t brightness);
+    };
+
+    struct SingleLight : public Light {
+        explicit SingleLight(InputDeviceContext& context, const std::string& name, int32_t id,
+                             int32_t rawId)
+              : Light(context, name, id, InputDeviceLightType::SINGLE), rawId(rawId) {}
+        int32_t rawId;
+
+        bool setLightColor(int32_t color) override;
+        std::optional<int32_t> getLightColor() override;
+        void dump(std::string& dump) override;
+    };
+
+    struct RgbLight : public Light {
+        explicit RgbLight(InputDeviceContext& context, int32_t id,
+                          const std::unordered_map<LightColor, int32_t>& rawRgbIds,
+                          std::optional<int32_t> rawGlobalId)
+              : Light(context, "RGB", id, InputDeviceLightType::RGB),
+                rawRgbIds(rawRgbIds),
+                rawGlobalId(rawGlobalId) {
+            brightness = rawGlobalId.has_value()
+                    ? getRawLightBrightness(rawGlobalId.value()).value_or(MAX_BRIGHTNESS)
+                    : MAX_BRIGHTNESS;
+        }
+        // Map from color to raw light id.
+        std::unordered_map<LightColor, int32_t /* rawLightId */> rawRgbIds;
+        // Optional global control raw light id.
+        std::optional<int32_t> rawGlobalId;
+        int32_t brightness;
+
+        bool setLightColor(int32_t color) override;
+        std::optional<int32_t> getLightColor() override;
+        void dump(std::string& dump) override;
+    };
+
+    struct MultiColorLight : public Light {
+        explicit MultiColorLight(InputDeviceContext& context, const std::string& name, int32_t id,
+                                 int32_t rawId)
+              : Light(context, name, id, InputDeviceLightType::MULTI_COLOR), rawId(rawId) {}
+        int32_t rawId;
+
+        bool setLightColor(int32_t color) override;
+        std::optional<int32_t> getLightColor() override;
+        void dump(std::string& dump) override;
+    };
+
+    struct PlayerIdLight : public Light {
+        explicit PlayerIdLight(InputDeviceContext& context, const std::string& name, int32_t id,
+                               const std::unordered_map<int32_t, int32_t>& rawLightIds)
+              : Light(context, name, id, InputDeviceLightType::PLAYER_ID),
+                rawLightIds(rawLightIds) {}
+        // Map from player Id to raw light Id
+        std::unordered_map<int32_t, int32_t> rawLightIds;
+
+        bool setLightPlayerId(int32_t palyerId) override;
+        std::optional<int32_t> getLightPlayerId() override;
+        void dump(std::string& dump) override;
+    };
+
+    int32_t mNextId = 0;
+
+    // Light color map from light color to the color index.
+    static const std::unordered_map<std::string, size_t> LIGHT_COLORS;
+
+    // Light map from light ID to Light
+    std::unordered_map<int32_t, std::unique_ptr<Light>> mLights;
+};
+
+} // namespace android
+
+#endif // _UI_INPUTREADER_LIGHT_INPUT_MAPPER_H
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 594ff42..e9d0189 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -87,11 +87,11 @@
     mRotaryEncoderScrollAccumulator.process(rawEvent);
 
     if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        sync(rawEvent->when);
+        sync(rawEvent->when, rawEvent->readTime);
     }
 }
 
-void RotaryEncoderInputMapper::sync(nsecs_t when) {
+void RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) {
     PointerCoords pointerCoords;
     pointerCoords.clear();
 
@@ -121,9 +121,9 @@
         int32_t metaState = getContext()->getGlobalMetaState();
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SCROLL, scroll * mScalingFactor);
 
-        NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, getDeviceId(), mSource,
-                                    displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
-                                    metaState, /* buttonState */ 0, MotionClassification::NONE,
+        NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
+                                    mSource, displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0,
+                                    0, metaState, /* buttonState */ 0, MotionClassification::NONE,
                                     AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                     &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                     AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {});
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index 7a77b12..e0c9404 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -42,7 +42,7 @@
     float mScalingFactor;
     int32_t mOrientation;
 
-    void sync(nsecs_t when);
+    void sync(nsecs_t when, nsecs_t readTime);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
index a86443d..5344227 100644
--- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
+++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
@@ -59,27 +59,27 @@
 }
 
 static void synthesizeButtonKey(InputReaderContext* context, int32_t action, nsecs_t when,
-                                int32_t deviceId, uint32_t source, int32_t displayId,
-                                uint32_t policyFlags, int32_t lastButtonState,
+                                nsecs_t readTime, int32_t deviceId, uint32_t source,
+                                int32_t displayId, uint32_t policyFlags, int32_t lastButtonState,
                                 int32_t currentButtonState, int32_t buttonState, int32_t keyCode) {
     if ((action == AKEY_EVENT_ACTION_DOWN && !(lastButtonState & buttonState) &&
          (currentButtonState & buttonState)) ||
         (action == AKEY_EVENT_ACTION_UP && (lastButtonState & buttonState) &&
          !(currentButtonState & buttonState))) {
-        NotifyKeyArgs args(context->getNextId(), when, deviceId, source, displayId, policyFlags,
-                           action, 0, keyCode, 0, context->getGlobalMetaState(), when);
+        NotifyKeyArgs args(context->getNextId(), when, readTime, deviceId, source, displayId,
+                           policyFlags, action, 0, keyCode, 0, context->getGlobalMetaState(), when);
         context->getListener()->notifyKey(&args);
     }
 }
 
 static void synthesizeButtonKeys(InputReaderContext* context, int32_t action, nsecs_t when,
-                                 int32_t deviceId, uint32_t source, int32_t displayId,
-                                 uint32_t policyFlags, int32_t lastButtonState,
+                                 nsecs_t readTime, int32_t deviceId, uint32_t source,
+                                 int32_t displayId, uint32_t policyFlags, int32_t lastButtonState,
                                  int32_t currentButtonState) {
-    synthesizeButtonKey(context, action, when, deviceId, source, displayId, policyFlags,
+    synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, policyFlags,
                         lastButtonState, currentButtonState, AMOTION_EVENT_BUTTON_BACK,
                         AKEYCODE_BACK);
-    synthesizeButtonKey(context, action, when, deviceId, source, displayId, policyFlags,
+    synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, policyFlags,
                         lastButtonState, currentButtonState, AMOTION_EVENT_BUTTON_FORWARD,
                         AKEYCODE_FORWARD);
 }
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index d1df37b..1a7ddee 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -1016,7 +1016,8 @@
             mPointerGestureMaxSwipeWidth = mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal;
 
             // Abort current pointer usages because the state has changed.
-            abortPointerUsage(when, 0 /*policyFlags*/);
+            const nsecs_t readTime = when; // synthetic event
+            abortPointerUsage(when, readTime, 0 /*policyFlags*/);
         }
 
         // Inform the dispatcher about the changes.
@@ -1406,32 +1407,34 @@
     mTouchButtonAccumulator.process(rawEvent);
 
     if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        sync(rawEvent->when);
+        sync(rawEvent->when, rawEvent->readTime);
     }
 }
 
-void TouchInputMapper::sync(nsecs_t when) {
-    const RawState* last =
-            mRawStatesPending.empty() ? &mCurrentRawState : &mRawStatesPending.back();
-
+void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) {
     // Push a new state.
     mRawStatesPending.emplace_back();
 
-    RawState* next = &mRawStatesPending.back();
-    next->clear();
-    next->when = when;
+    RawState& next = mRawStatesPending.back();
+    next.clear();
+    next.when = when;
+    next.readTime = readTime;
 
     // Sync button state.
-    next->buttonState =
+    next.buttonState =
             mTouchButtonAccumulator.getButtonState() | mCursorButtonAccumulator.getButtonState();
 
     // Sync scroll
-    next->rawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
-    next->rawHScroll = mCursorScrollAccumulator.getRelativeHWheel();
+    next.rawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
+    next.rawHScroll = mCursorScrollAccumulator.getRelativeHWheel();
     mCursorScrollAccumulator.finishSync();
 
     // Sync touch
-    syncTouch(when, next);
+    syncTouch(when, &next);
+
+    // The last RawState is the actually second to last, since we just added a new state
+    const RawState& last =
+            mRawStatesPending.size() == 1 ? mCurrentRawState : mRawStatesPending.rbegin()[1];
 
     // Assign pointer ids.
     if (!mHavePointerIds) {
@@ -1441,10 +1444,10 @@
 #if DEBUG_RAW_EVENTS
     ALOGD("syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, "
           "hovering ids 0x%08x -> 0x%08x, canceled ids 0x%08x",
-          last->rawPointerData.pointerCount, next->rawPointerData.pointerCount,
-          last->rawPointerData.touchingIdBits.value, next->rawPointerData.touchingIdBits.value,
-          last->rawPointerData.hoveringIdBits.value, next->rawPointerData.hoveringIdBits.value,
-          next->rawPointerData.canceledIdBits.value);
+          last.rawPointerData.pointerCount, next.rawPointerData.pointerCount,
+          last.rawPointerData.touchingIdBits.value, next.rawPointerData.touchingIdBits.value,
+          last.rawPointerData.hoveringIdBits.value, next.rawPointerData.hoveringIdBits.value,
+          next.rawPointerData.canceledIdBits.value);
 #endif
 
     processRawTouches(false /*timeout*/);
@@ -1453,7 +1456,7 @@
 void TouchInputMapper::processRawTouches(bool timeout) {
     if (mDeviceMode == DeviceMode::DISABLED) {
         // Drop all input if the device is disabled.
-        cancelTouch(mCurrentRawState.when);
+        cancelTouch(mCurrentRawState.when, mCurrentRawState.readTime);
         mCurrentCookedState.clear();
         updateTouchSpots();
         return;
@@ -1479,8 +1482,9 @@
         mCurrentRawState.copyFrom(next);
         if (mCurrentRawState.when < mLastRawState.when) {
             mCurrentRawState.when = mLastRawState.when;
+            mCurrentRawState.readTime = mLastRawState.readTime;
         }
-        cookAndDispatch(mCurrentRawState.when);
+        cookAndDispatch(mCurrentRawState.when, mCurrentRawState.readTime);
     }
     if (count != 0) {
         mRawStatesPending.erase(mRawStatesPending.begin(), mRawStatesPending.begin() + count);
@@ -1494,7 +1498,8 @@
 #if DEBUG_STYLUS_FUSION
             ALOGD("Timeout expired, synthesizing event with new stylus data");
 #endif
-            cookAndDispatch(when);
+            const nsecs_t readTime = when; // consider this synthetic event to be zero latency
+            cookAndDispatch(when, readTime);
         } else if (mExternalStylusFusionTimeout == LLONG_MAX) {
             mExternalStylusFusionTimeout = mExternalStylusState.when + TOUCH_DATA_TIMEOUT;
             getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
@@ -1502,7 +1507,7 @@
     }
 }
 
-void TouchInputMapper::cookAndDispatch(nsecs_t when) {
+void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) {
     // Always start with a clean state.
     mCurrentCookedState.clear();
 
@@ -1528,7 +1533,7 @@
 
     // Consume raw off-screen touches before cooking pointer data.
     // If touches are consumed, subsequent code will not receive any pointer data.
-    if (consumeRawTouches(when, policyFlags)) {
+    if (consumeRawTouches(when, readTime, policyFlags)) {
         mCurrentRawState.rawPointerData.clear();
     }
 
@@ -1541,8 +1546,8 @@
     applyExternalStylusTouchState(when);
 
     // Synthesize key down from raw buttons if needed.
-    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
-                         mViewport.displayId, policyFlags, mLastCookedState.buttonState,
+    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(),
+                         mSource, mViewport.displayId, policyFlags, mLastCookedState.buttonState,
                          mCurrentCookedState.buttonState);
 
     // Dispatch the touches either directly or by translation through a pointer on screen.
@@ -1585,16 +1590,16 @@
             pointerUsage = PointerUsage::GESTURES;
         }
 
-        dispatchPointerUsage(when, policyFlags, pointerUsage);
+        dispatchPointerUsage(when, readTime, policyFlags, pointerUsage);
     } else {
         updateTouchSpots();
 
         if (!mCurrentMotionAborted) {
-            dispatchButtonRelease(when, policyFlags);
-            dispatchHoverExit(when, policyFlags);
-            dispatchTouches(when, policyFlags);
-            dispatchHoverEnterAndMove(when, policyFlags);
-            dispatchButtonPress(when, policyFlags);
+            dispatchButtonRelease(when, readTime, policyFlags);
+            dispatchHoverExit(when, readTime, policyFlags);
+            dispatchTouches(when, readTime, policyFlags);
+            dispatchHoverEnterAndMove(when, readTime, policyFlags);
+            dispatchButtonPress(when, readTime, policyFlags);
         }
 
         if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
@@ -1603,7 +1608,7 @@
     }
 
     // Synthesize key up from raw buttons if needed.
-    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
+    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), mSource,
                          mViewport.displayId, policyFlags, mLastCookedState.buttonState,
                          mCurrentCookedState.buttonState);
 
@@ -1716,7 +1721,9 @@
 void TouchInputMapper::timeoutExpired(nsecs_t when) {
     if (mDeviceMode == DeviceMode::POINTER) {
         if (mPointerUsage == PointerUsage::GESTURES) {
-            dispatchPointerGestures(when, 0 /*policyFlags*/, true /*isTimeout*/);
+            // Since this is a synthetic event, we can consider its latency to be zero
+            const nsecs_t readTime = when;
+            dispatchPointerGestures(when, readTime, 0 /*policyFlags*/, true /*isTimeout*/);
         }
     } else if (mDeviceMode == DeviceMode::DIRECT) {
         if (mExternalStylusFusionTimeout < when) {
@@ -1738,7 +1745,7 @@
     }
 }
 
-bool TouchInputMapper::consumeRawTouches(nsecs_t when, uint32_t policyFlags) {
+bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
     // Check for release of a virtual key.
     if (mCurrentVirtualKey.down) {
         if (mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
@@ -1749,7 +1756,7 @@
                 ALOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
                       mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
 #endif
-                dispatchVirtualKey(when, policyFlags, AKEY_EVENT_ACTION_UP,
+                dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP,
                                    AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
             }
             return true;
@@ -1776,7 +1783,7 @@
             ALOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", mCurrentVirtualKey.keyCode,
                   mCurrentVirtualKey.scanCode);
 #endif
-            dispatchVirtualKey(when, policyFlags, AKEY_EVENT_ACTION_UP,
+            dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP,
                                AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY |
                                        AKEY_EVENT_FLAG_CANCELED);
         }
@@ -1807,7 +1814,7 @@
                         ALOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
                               mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
 #endif
-                        dispatchVirtualKey(when, policyFlags, AKEY_EVENT_ACTION_DOWN,
+                        dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_DOWN,
                                            AKEY_EVENT_FLAG_FROM_SYSTEM |
                                                    AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
                     }
@@ -1838,7 +1845,7 @@
     return false;
 }
 
-void TouchInputMapper::dispatchVirtualKey(nsecs_t when, uint32_t policyFlags,
+void TouchInputMapper::dispatchVirtualKey(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
                                           int32_t keyEventAction, int32_t keyEventFlags) {
     int32_t keyCode = mCurrentVirtualKey.keyCode;
     int32_t scanCode = mCurrentVirtualKey.scanCode;
@@ -1846,19 +1853,19 @@
     int32_t metaState = getContext()->getGlobalMetaState();
     policyFlags |= POLICY_FLAG_VIRTUAL;
 
-    NotifyKeyArgs args(getContext()->getNextId(), when, getDeviceId(), AINPUT_SOURCE_KEYBOARD,
-                       mViewport.displayId, policyFlags, keyEventAction, keyEventFlags, keyCode,
-                       scanCode, metaState, downTime);
+    NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(),
+                       AINPUT_SOURCE_KEYBOARD, mViewport.displayId, policyFlags, keyEventAction,
+                       keyEventFlags, keyCode, scanCode, metaState, downTime);
     getListener()->notifyKey(&args);
 }
 
-void TouchInputMapper::abortTouches(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
     BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
     if (!currentIdBits.isEmpty()) {
         int32_t metaState = getContext()->getGlobalMetaState();
         int32_t buttonState = mCurrentCookedState.buttonState;
-        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState,
-                       buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0,
+                       metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                        mCurrentCookedState.cookedPointerData.pointerProperties,
                        mCurrentCookedState.cookedPointerData.pointerCoords,
                        mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, -1,
@@ -1867,7 +1874,7 @@
     }
 }
 
-void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
     BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
     BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits;
     int32_t metaState = getContext()->getGlobalMetaState();
@@ -1877,8 +1884,8 @@
         if (!currentIdBits.isEmpty()) {
             // No pointer id changes so this is a move event.
             // The listener takes care of batching moves so we don't have to deal with that here.
-            dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
-                           buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0,
+                           metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                            mCurrentCookedState.cookedPointerData.pointerProperties,
                            mCurrentCookedState.cookedPointerData.pointerCoords,
                            mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, -1,
@@ -1912,7 +1919,7 @@
             if (isCanceled) {
                 ALOGI("Canceling pointer %d for the palm event was detected.", upId);
             }
-            dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0,
+            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0,
                            isCanceled ? AMOTION_EVENT_FLAG_CANCELED : 0, metaState, buttonState, 0,
                            mLastCookedState.cookedPointerData.pointerProperties,
                            mLastCookedState.cookedPointerData.pointerCoords,
@@ -1927,8 +1934,9 @@
         // events, they do not generally handle them except when presented in a move event.
         if (moveNeeded && !moveIdBits.isEmpty()) {
             ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value);
-            dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
-                           buttonState, 0, mCurrentCookedState.cookedPointerData.pointerProperties,
+            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0,
+                           metaState, buttonState, 0,
+                           mCurrentCookedState.cookedPointerData.pointerProperties,
                            mCurrentCookedState.cookedPointerData.pointerCoords,
                            mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, -1,
                            mOrientedXPrecision, mOrientedYPrecision, mDownTime);
@@ -1944,8 +1952,8 @@
                 mDownTime = when;
             }
 
-            dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0,
-                           metaState, buttonState, 0,
+            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN,
+                           0, 0, metaState, buttonState, 0,
                            mCurrentCookedState.cookedPointerData.pointerProperties,
                            mCurrentCookedState.cookedPointerData.pointerCoords,
                            mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits,
@@ -1954,13 +1962,13 @@
     }
 }
 
-void TouchInputMapper::dispatchHoverExit(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::dispatchHoverExit(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
     if (mSentHoverEnter &&
         (mCurrentCookedState.cookedPointerData.hoveringIdBits.isEmpty() ||
          !mCurrentCookedState.cookedPointerData.touchingIdBits.isEmpty())) {
         int32_t metaState = getContext()->getGlobalMetaState();
-        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, metaState,
-                       mLastCookedState.buttonState, 0,
+        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0,
+                       metaState, mLastCookedState.buttonState, 0,
                        mLastCookedState.cookedPointerData.pointerProperties,
                        mLastCookedState.cookedPointerData.pointerCoords,
                        mLastCookedState.cookedPointerData.idToIndex,
@@ -1970,13 +1978,14 @@
     }
 }
 
-void TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime,
+                                                 uint32_t policyFlags) {
     if (mCurrentCookedState.cookedPointerData.touchingIdBits.isEmpty() &&
         !mCurrentCookedState.cookedPointerData.hoveringIdBits.isEmpty()) {
         int32_t metaState = getContext()->getGlobalMetaState();
         if (!mSentHoverEnter) {
-            dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0,
-                           metaState, mCurrentRawState.buttonState, 0,
+            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_ENTER,
+                           0, 0, metaState, mCurrentRawState.buttonState, 0,
                            mCurrentCookedState.cookedPointerData.pointerProperties,
                            mCurrentCookedState.cookedPointerData.pointerCoords,
                            mCurrentCookedState.cookedPointerData.idToIndex,
@@ -1985,8 +1994,8 @@
             mSentHoverEnter = true;
         }
 
-        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
-                       mCurrentRawState.buttonState, 0,
+        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0,
+                       metaState, mCurrentRawState.buttonState, 0,
                        mCurrentCookedState.cookedPointerData.pointerProperties,
                        mCurrentCookedState.cookedPointerData.pointerCoords,
                        mCurrentCookedState.cookedPointerData.idToIndex,
@@ -1995,7 +2004,7 @@
     }
 }
 
-void TouchInputMapper::dispatchButtonRelease(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::dispatchButtonRelease(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
     BitSet32 releasedButtons(mLastCookedState.buttonState & ~mCurrentCookedState.buttonState);
     const BitSet32& idBits = findActiveIdBits(mLastCookedState.cookedPointerData);
     const int32_t metaState = getContext()->getGlobalMetaState();
@@ -2003,7 +2012,7 @@
     while (!releasedButtons.isEmpty()) {
         int32_t actionButton = BitSet32::valueForBit(releasedButtons.clearFirstMarkedBit());
         buttonState &= ~actionButton;
-        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
                        actionButton, 0, metaState, buttonState, 0,
                        mCurrentCookedState.cookedPointerData.pointerProperties,
                        mCurrentCookedState.cookedPointerData.pointerCoords,
@@ -2012,7 +2021,7 @@
     }
 }
 
-void TouchInputMapper::dispatchButtonPress(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::dispatchButtonPress(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
     BitSet32 pressedButtons(mCurrentCookedState.buttonState & ~mLastCookedState.buttonState);
     const BitSet32& idBits = findActiveIdBits(mCurrentCookedState.cookedPointerData);
     const int32_t metaState = getContext()->getGlobalMetaState();
@@ -2020,8 +2029,8 @@
     while (!pressedButtons.isEmpty()) {
         int32_t actionButton = BitSet32::valueForBit(pressedButtons.clearFirstMarkedBit());
         buttonState |= actionButton;
-        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton,
-                       0, metaState, buttonState, 0,
+        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                       actionButton, 0, metaState, buttonState, 0,
                        mCurrentCookedState.cookedPointerData.pointerProperties,
                        mCurrentCookedState.cookedPointerData.pointerCoords,
                        mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1,
@@ -2308,38 +2317,38 @@
     }
 }
 
-void TouchInputMapper::dispatchPointerUsage(nsecs_t when, uint32_t policyFlags,
+void TouchInputMapper::dispatchPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
                                             PointerUsage pointerUsage) {
     if (pointerUsage != mPointerUsage) {
-        abortPointerUsage(when, policyFlags);
+        abortPointerUsage(when, readTime, policyFlags);
         mPointerUsage = pointerUsage;
     }
 
     switch (mPointerUsage) {
         case PointerUsage::GESTURES:
-            dispatchPointerGestures(when, policyFlags, false /*isTimeout*/);
+            dispatchPointerGestures(when, readTime, policyFlags, false /*isTimeout*/);
             break;
         case PointerUsage::STYLUS:
-            dispatchPointerStylus(when, policyFlags);
+            dispatchPointerStylus(when, readTime, policyFlags);
             break;
         case PointerUsage::MOUSE:
-            dispatchPointerMouse(when, policyFlags);
+            dispatchPointerMouse(when, readTime, policyFlags);
             break;
         case PointerUsage::NONE:
             break;
     }
 }
 
-void TouchInputMapper::abortPointerUsage(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::abortPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
     switch (mPointerUsage) {
         case PointerUsage::GESTURES:
-            abortPointerGestures(when, policyFlags);
+            abortPointerGestures(when, readTime, policyFlags);
             break;
         case PointerUsage::STYLUS:
-            abortPointerStylus(when, policyFlags);
+            abortPointerStylus(when, readTime, policyFlags);
             break;
         case PointerUsage::MOUSE:
-            abortPointerMouse(when, policyFlags);
+            abortPointerMouse(when, readTime, policyFlags);
             break;
         case PointerUsage::NONE:
             break;
@@ -2348,7 +2357,8 @@
     mPointerUsage = PointerUsage::NONE;
 }
 
-void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout) {
+void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
+                                               bool isTimeout) {
     // Update current gesture coordinates.
     bool cancelPreviousGesture, finishPreviousGesture;
     bool sendEvents =
@@ -2441,8 +2451,8 @@
     BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits);
     if (!dispatchedGestureIdBits.isEmpty()) {
         if (cancelPreviousGesture) {
-            dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState,
-                           buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0,
+                           metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                            mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords,
                            mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0,
                            mPointerGesture.downTime);
@@ -2459,9 +2469,9 @@
             while (!upGestureIdBits.isEmpty()) {
                 uint32_t id = upGestureIdBits.clearFirstMarkedBit();
 
-                dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0, 0,
-                               metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
-                               mPointerGesture.lastGestureProperties,
+                dispatchMotion(when, readTime, policyFlags, mSource,
+                               AMOTION_EVENT_ACTION_POINTER_UP, 0, 0, metaState, buttonState,
+                               AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties,
                                mPointerGesture.lastGestureCoords,
                                mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, id, 0,
                                0, mPointerGesture.downTime);
@@ -2473,8 +2483,8 @@
 
     // Send motion events for all pointers that moved.
     if (moveNeeded) {
-        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
-                       buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0,
+                       metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                        mPointerGesture.currentGestureProperties,
                        mPointerGesture.currentGestureCoords,
                        mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0,
@@ -2493,8 +2503,9 @@
                 mPointerGesture.downTime = when;
             }
 
-            dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0,
-                           metaState, buttonState, 0, mPointerGesture.currentGestureProperties,
+            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN,
+                           0, 0, metaState, buttonState, 0,
+                           mPointerGesture.currentGestureProperties,
                            mPointerGesture.currentGestureCoords,
                            mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, id, 0,
                            0, mPointerGesture.downTime);
@@ -2503,8 +2514,8 @@
 
     // Send motion events for hover.
     if (mPointerGesture.currentGestureMode == PointerGesture::Mode::HOVER) {
-        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
-                       buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0,
+                       metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                        mPointerGesture.currentGestureProperties,
                        mPointerGesture.currentGestureCoords,
                        mPointerGesture.currentGestureIdToIndex,
@@ -2528,11 +2539,11 @@
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
 
         const int32_t displayId = mPointerController->getDisplayId();
-        NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
-                              policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
-                              buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
-                              1, &pointerProperties, &pointerCoords, 0, 0, x, y,
-                              mPointerGesture.downTime, /* videoFrames */ {});
+        NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0,
+                              metaState, buttonState, MotionClassification::NONE,
+                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords,
+                              0, 0, x, y, mPointerGesture.downTime, /* videoFrames */ {});
         getListener()->notifyMotion(&args);
     }
 
@@ -2554,13 +2565,13 @@
     }
 }
 
-void TouchInputMapper::abortPointerGestures(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
     // Cancel previously dispatches pointers.
     if (!mPointerGesture.lastGestureIdBits.isEmpty()) {
         int32_t metaState = getContext()->getGlobalMetaState();
         int32_t buttonState = mCurrentRawState.buttonState;
-        dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState,
-                       buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0,
+                       metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                        mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords,
                        mPointerGesture.lastGestureIdToIndex, mPointerGesture.lastGestureIdBits, -1,
                        0, 0, mPointerGesture.downTime);
@@ -3335,7 +3346,7 @@
     return true;
 }
 
-void TouchInputMapper::dispatchPointerStylus(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
     mPointerSimple.currentCoords.clear();
     mPointerSimple.currentProperties.clear();
 
@@ -3363,14 +3374,14 @@
         hovering = false;
     }
 
-    dispatchPointerSimple(when, policyFlags, down, hovering);
+    dispatchPointerSimple(when, readTime, policyFlags, down, hovering);
 }
 
-void TouchInputMapper::abortPointerStylus(nsecs_t when, uint32_t policyFlags) {
-    abortPointerSimple(when, policyFlags);
+void TouchInputMapper::abortPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
+    abortPointerSimple(when, readTime, policyFlags);
 }
 
-void TouchInputMapper::dispatchPointerMouse(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
     mPointerSimple.currentCoords.clear();
     mPointerSimple.currentProperties.clear();
 
@@ -3417,17 +3428,17 @@
         hovering = false;
     }
 
-    dispatchPointerSimple(when, policyFlags, down, hovering);
+    dispatchPointerSimple(when, readTime, policyFlags, down, hovering);
 }
 
-void TouchInputMapper::abortPointerMouse(nsecs_t when, uint32_t policyFlags) {
-    abortPointerSimple(when, policyFlags);
+void TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
+    abortPointerSimple(when, readTime, policyFlags);
 
     mPointerVelocityControl.reset();
 }
 
-void TouchInputMapper::dispatchPointerSimple(nsecs_t when, uint32_t policyFlags, bool down,
-                                             bool hovering) {
+void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
+                                             bool down, bool hovering) {
     int32_t metaState = getContext()->getGlobalMetaState();
 
     if (down || hovering) {
@@ -3448,8 +3459,8 @@
         mPointerSimple.down = false;
 
         // Send up.
-        NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
-                              policyFlags, AMOTION_EVENT_ACTION_UP, 0, 0, metaState,
+        NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_UP, 0, 0, metaState,
                               mLastRawState.buttonState, MotionClassification::NONE,
                               AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties,
                               &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision,
@@ -3462,9 +3473,9 @@
         mPointerSimple.hovering = false;
 
         // Send hover exit.
-        NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
-                              policyFlags, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, metaState,
-                              mLastRawState.buttonState, MotionClassification::NONE,
+        NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0,
+                              metaState, mLastRawState.buttonState, MotionClassification::NONE,
                               AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties,
                               &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision,
                               xCursorPosition, yCursorPosition, mPointerSimple.downTime,
@@ -3478,7 +3489,7 @@
             mPointerSimple.downTime = when;
 
             // Send down.
-            NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource,
+            NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
                                   displayId, policyFlags, AMOTION_EVENT_ACTION_DOWN, 0, 0,
                                   metaState, mCurrentRawState.buttonState,
                                   MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
@@ -3489,8 +3500,8 @@
         }
 
         // Send move.
-        NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
-                              policyFlags, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
+        NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
                               mCurrentRawState.buttonState, MotionClassification::NONE,
                               AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties,
                               &mPointerSimple.currentCoords, mOrientedXPrecision,
@@ -3504,7 +3515,7 @@
             mPointerSimple.hovering = true;
 
             // Send hover enter.
-            NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource,
+            NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
                                   displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0,
                                   metaState, mCurrentRawState.buttonState,
                                   MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
@@ -3515,9 +3526,9 @@
         }
 
         // Send hover move.
-        NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
-                              policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
-                              mCurrentRawState.buttonState, MotionClassification::NONE,
+        NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0,
+                              metaState, mCurrentRawState.buttonState, MotionClassification::NONE,
                               AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties,
                               &mPointerSimple.currentCoords, mOrientedXPrecision,
                               mOrientedYPrecision, xCursorPosition, yCursorPosition,
@@ -3537,8 +3548,8 @@
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
 
-        NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
-                              policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState,
+        NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState,
                               mCurrentRawState.buttonState, MotionClassification::NONE,
                               AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties,
                               &pointerCoords, mOrientedXPrecision, mOrientedYPrecision,
@@ -3556,17 +3567,17 @@
     }
 }
 
-void TouchInputMapper::abortPointerSimple(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
     mPointerSimple.currentCoords.clear();
     mPointerSimple.currentProperties.clear();
 
-    dispatchPointerSimple(when, policyFlags, false, false);
+    dispatchPointerSimple(when, readTime, policyFlags, false, false);
 }
 
-void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
-                                      int32_t action, int32_t actionButton, int32_t flags,
-                                      int32_t metaState, int32_t buttonState, int32_t edgeFlags,
-                                      const PointerProperties* properties,
+void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
+                                      uint32_t source, int32_t action, int32_t actionButton,
+                                      int32_t flags, int32_t metaState, int32_t buttonState,
+                                      int32_t edgeFlags, const PointerProperties* properties,
                                       const PointerCoords* coords, const uint32_t* idToIndex,
                                       BitSet32 idBits, int32_t changedId, float xPrecision,
                                       float yPrecision, nsecs_t downTime) {
@@ -3615,8 +3626,8 @@
     std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
     std::for_each(frames.begin(), frames.end(),
                   [this](TouchVideoFrame& frame) { frame.rotate(this->mSurfaceOrientation); });
-    NotifyMotionArgs args(getContext()->getNextId(), when, deviceId, source, displayId, policyFlags,
-                          action, actionButton, flags, metaState, buttonState,
+    NotifyMotionArgs args(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
+                          policyFlags, action, actionButton, flags, metaState, buttonState,
                           MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties,
                           pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,
                           downTime, std::move(frames));
@@ -3653,9 +3664,9 @@
     return changed;
 }
 
-void TouchInputMapper::cancelTouch(nsecs_t when) {
-    abortPointerUsage(when, 0 /*policyFlags*/);
-    abortTouches(when, 0 /* policyFlags*/);
+void TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) {
+    abortPointerUsage(when, readTime, 0 /*policyFlags*/);
+    abortTouches(when, readTime, 0 /* policyFlags*/);
 }
 
 // Transform raw coordinate to surface coordinate
@@ -3721,11 +3732,11 @@
     return nullptr;
 }
 
-void TouchInputMapper::assignPointerIds(const RawState* last, RawState* current) {
-    uint32_t currentPointerCount = current->rawPointerData.pointerCount;
-    uint32_t lastPointerCount = last->rawPointerData.pointerCount;
+void TouchInputMapper::assignPointerIds(const RawState& last, RawState& current) {
+    uint32_t currentPointerCount = current.rawPointerData.pointerCount;
+    uint32_t lastPointerCount = last.rawPointerData.pointerCount;
 
-    current->rawPointerData.clearIdBits();
+    current.rawPointerData.clearIdBits();
 
     if (currentPointerCount == 0) {
         // No pointers to assign.
@@ -3736,20 +3747,20 @@
         // All pointers are new.
         for (uint32_t i = 0; i < currentPointerCount; i++) {
             uint32_t id = i;
-            current->rawPointerData.pointers[i].id = id;
-            current->rawPointerData.idToIndex[id] = i;
-            current->rawPointerData.markIdBit(id, current->rawPointerData.isHovering(i));
+            current.rawPointerData.pointers[i].id = id;
+            current.rawPointerData.idToIndex[id] = i;
+            current.rawPointerData.markIdBit(id, current.rawPointerData.isHovering(i));
         }
         return;
     }
 
     if (currentPointerCount == 1 && lastPointerCount == 1 &&
-        current->rawPointerData.pointers[0].toolType == last->rawPointerData.pointers[0].toolType) {
+        current.rawPointerData.pointers[0].toolType == last.rawPointerData.pointers[0].toolType) {
         // Only one pointer and no change in count so it must have the same id as before.
-        uint32_t id = last->rawPointerData.pointers[0].id;
-        current->rawPointerData.pointers[0].id = id;
-        current->rawPointerData.idToIndex[id] = 0;
-        current->rawPointerData.markIdBit(id, current->rawPointerData.isHovering(0));
+        uint32_t id = last.rawPointerData.pointers[0].id;
+        current.rawPointerData.pointers[0].id = id;
+        current.rawPointerData.idToIndex[id] = 0;
+        current.rawPointerData.markIdBit(id, current.rawPointerData.isHovering(0));
         return;
     }
 
@@ -3767,9 +3778,9 @@
         for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount;
              lastPointerIndex++) {
             const RawPointerData::Pointer& currentPointer =
-                    current->rawPointerData.pointers[currentPointerIndex];
+                    current.rawPointerData.pointers[currentPointerIndex];
             const RawPointerData::Pointer& lastPointer =
-                    last->rawPointerData.pointers[lastPointerIndex];
+                    last.rawPointerData.pointers[lastPointerIndex];
             if (currentPointer.toolType == lastPointer.toolType) {
                 int64_t deltaX = currentPointer.x - lastPointer.x;
                 int64_t deltaY = currentPointer.y - lastPointer.y;
@@ -3873,12 +3884,12 @@
             matchedCurrentBits.markBit(currentPointerIndex);
             matchedLastBits.markBit(lastPointerIndex);
 
-            uint32_t id = last->rawPointerData.pointers[lastPointerIndex].id;
-            current->rawPointerData.pointers[currentPointerIndex].id = id;
-            current->rawPointerData.idToIndex[id] = currentPointerIndex;
-            current->rawPointerData.markIdBit(id,
-                                              current->rawPointerData.isHovering(
-                                                      currentPointerIndex));
+            uint32_t id = last.rawPointerData.pointers[lastPointerIndex].id;
+            current.rawPointerData.pointers[currentPointerIndex].id = id;
+            current.rawPointerData.idToIndex[id] = currentPointerIndex;
+            current.rawPointerData.markIdBit(id,
+                                             current.rawPointerData.isHovering(
+                                                     currentPointerIndex));
             usedIdBits.markBit(id);
 
 #if DEBUG_POINTER_ASSIGNMENT
@@ -3895,10 +3906,10 @@
         uint32_t currentPointerIndex = matchedCurrentBits.markFirstUnmarkedBit();
         uint32_t id = usedIdBits.markFirstUnmarkedBit();
 
-        current->rawPointerData.pointers[currentPointerIndex].id = id;
-        current->rawPointerData.idToIndex[id] = currentPointerIndex;
-        current->rawPointerData.markIdBit(id,
-                                          current->rawPointerData.isHovering(currentPointerIndex));
+        current.rawPointerData.pointers[currentPointerIndex].id = id;
+        current.rawPointerData.idToIndex[id] = currentPointerIndex;
+        current.rawPointerData.markIdBit(id,
+                                         current.rawPointerData.isHovering(currentPointerIndex));
 
 #if DEBUG_POINTER_ASSIGNMENT
         ALOGD("assignPointerIds - assigned: cur=%" PRIu32 ", id=%" PRIu32, currentPointerIndex, id);
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 6621825..9b84ed5 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -150,7 +150,7 @@
     bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes,
                                uint8_t* outFlags) override;
 
-    void cancelTouch(nsecs_t when) override;
+    void cancelTouch(nsecs_t when, nsecs_t readTime) override;
     void timeoutExpired(nsecs_t when) override;
     void updateExternalStylusState(const StylusState& state) override;
     std::optional<int32_t> getAssociatedDisplayId() override;
@@ -298,6 +298,7 @@
 
     struct RawState {
         nsecs_t when;
+        nsecs_t readTime;
 
         // Raw pointer sample data.
         RawPointerData rawPointerData;
@@ -310,6 +311,7 @@
 
         void copyFrom(const RawState& other) {
             when = other.when;
+            readTime = other.readTime;
             rawPointerData.copyFrom(other.rawPointerData);
             buttonState = other.buttonState;
             rawVScroll = other.rawVScroll;
@@ -318,6 +320,7 @@
 
         void clear() {
             when = 0;
+            readTime = 0;
             rawPointerData.clear();
             buttonState = 0;
             rawVScroll = 0;
@@ -702,39 +705,42 @@
     void resetExternalStylus();
     void clearStylusDataPendingFlags();
 
-    void sync(nsecs_t when);
+    void sync(nsecs_t when, nsecs_t readTime);
 
-    bool consumeRawTouches(nsecs_t when, uint32_t policyFlags);
+    bool consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
     void processRawTouches(bool timeout);
-    void cookAndDispatch(nsecs_t when);
-    void dispatchVirtualKey(nsecs_t when, uint32_t policyFlags, int32_t keyEventAction,
-                            int32_t keyEventFlags);
+    void cookAndDispatch(nsecs_t when, nsecs_t readTime);
+    void dispatchVirtualKey(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
+                            int32_t keyEventAction, int32_t keyEventFlags);
 
-    void dispatchTouches(nsecs_t when, uint32_t policyFlags);
-    void dispatchHoverExit(nsecs_t when, uint32_t policyFlags);
-    void dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags);
-    void dispatchButtonRelease(nsecs_t when, uint32_t policyFlags);
-    void dispatchButtonPress(nsecs_t when, uint32_t policyFlags);
+    void dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
+    void dispatchHoverExit(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
+    void dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
+    void dispatchButtonRelease(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
+    void dispatchButtonPress(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
     const BitSet32& findActiveIdBits(const CookedPointerData& cookedPointerData);
     void cookPointerData();
-    void abortTouches(nsecs_t when, uint32_t policyFlags);
+    void abortTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
 
-    void dispatchPointerUsage(nsecs_t when, uint32_t policyFlags, PointerUsage pointerUsage);
-    void abortPointerUsage(nsecs_t when, uint32_t policyFlags);
+    void dispatchPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
+                              PointerUsage pointerUsage);
+    void abortPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
 
-    void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout);
-    void abortPointerGestures(nsecs_t when, uint32_t policyFlags);
+    void dispatchPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
+                                 bool isTimeout);
+    void abortPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
     bool preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture,
                                 bool* outFinishPreviousGesture, bool isTimeout);
 
-    void dispatchPointerStylus(nsecs_t when, uint32_t policyFlags);
-    void abortPointerStylus(nsecs_t when, uint32_t policyFlags);
+    void dispatchPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
+    void abortPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
 
-    void dispatchPointerMouse(nsecs_t when, uint32_t policyFlags);
-    void abortPointerMouse(nsecs_t when, uint32_t policyFlags);
+    void dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
+    void abortPointerMouse(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
 
-    void dispatchPointerSimple(nsecs_t when, uint32_t policyFlags, bool down, bool hovering);
-    void abortPointerSimple(nsecs_t when, uint32_t policyFlags);
+    void dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, bool down,
+                               bool hovering);
+    void abortPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
 
     bool assignExternalStylusId(const RawState& state, bool timeout);
     void applyExternalStylusButtonState(nsecs_t when);
@@ -744,9 +750,9 @@
     // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the
     // method will take care of setting the index and transmuting the action to DOWN or UP
     // it is the first / last pointer to go down / up.
-    void dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source, int32_t action,
-                        int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
-                        int32_t edgeFlags, const PointerProperties* properties,
+    void dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source,
+                        int32_t action, int32_t actionButton, int32_t flags, int32_t metaState,
+                        int32_t buttonState, int32_t edgeFlags, const PointerProperties* properties,
                         const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits,
                         int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime);
 
@@ -766,7 +772,7 @@
     bool isPointInsideSurface(int32_t x, int32_t y);
     const VirtualKey* findVirtualKeyHit(int32_t x, int32_t y);
 
-    static void assignPointerIds(const RawState* last, RawState* current);
+    static void assignPointerIds(const RawState& last, RawState& current);
 
     const char* modeToString(DeviceMode deviceMode);
     void rotateAndScale(float& x, float& y);
diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp
index ef3dd65..17efb5b 100644
--- a/services/inputflinger/tests/FocusResolver_test.cpp
+++ b/services/inputflinger/tests/FocusResolver_test.cpp
@@ -18,6 +18,12 @@
 
 #include "../FocusResolver.h"
 
+#define ASSERT_FOCUS_CHANGE(_changes, _oldFocus, _newFocus) \
+    {                                                       \
+        ASSERT_EQ(_oldFocus, _changes->oldFocus);           \
+        ASSERT_EQ(_newFocus, _changes->newFocus);           \
+    }
+
 // atest inputflinger_tests:FocusResolverTest
 
 namespace android::inputdispatcher {
@@ -56,8 +62,7 @@
     FocusResolver focusResolver;
     std::optional<FocusResolver::FocusChanges> changes =
             focusResolver.setFocusedWindow(request, windows);
-    ASSERT_EQ(nullptr, changes->oldFocus);
-    ASSERT_EQ(focusableWindowToken, changes->newFocus);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken);
     ASSERT_EQ(request.displayId, changes->displayId);
 
     // invisible window cannot get focused
@@ -65,6 +70,7 @@
     changes = focusResolver.setFocusedWindow(request, windows);
     ASSERT_EQ(focusableWindowToken, changes->oldFocus);
     ASSERT_EQ(nullptr, changes->newFocus);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr);
 
     // unfocusableWindowToken window cannot get focused
     request.token = unfocusableWindowToken;
@@ -99,19 +105,17 @@
     FocusResolver focusResolver;
     std::optional<FocusResolver::FocusChanges> changes =
             focusResolver.setFocusedWindow(request, windows);
-    ASSERT_EQ(nullptr, changes->oldFocus);
-    ASSERT_EQ(focusableWindowToken, changes->newFocus);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken);
 
     // mirrored window with one visible window can get focused
     request.token = invisibleWindowToken;
     changes = focusResolver.setFocusedWindow(request, windows);
-    ASSERT_EQ(focusableWindowToken, changes->oldFocus);
-    ASSERT_EQ(invisibleWindowToken, changes->newFocus);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ invisibleWindowToken);
 
     // mirrored window with one or more unfocusable window cannot get focused
     request.token = unfocusableWindowToken;
     changes = focusResolver.setFocusedWindow(request, windows);
-    ASSERT_FALSE(changes);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ invisibleWindowToken, /*to*/ nullptr);
 }
 
 TEST(FocusResolverTest, SetInputWindows) {
@@ -130,11 +134,10 @@
             focusResolver.setFocusedWindow(request, windows);
     ASSERT_EQ(focusableWindowToken, changes->newFocus);
 
-    // Window visibility changes and the window loses focused
+    // Window visibility changes and the window loses focus
     window->setVisible(false);
     changes = focusResolver.setInputWindows(request.displayId, windows);
-    ASSERT_EQ(nullptr, changes->newFocus);
-    ASSERT_EQ(focusableWindowToken, changes->oldFocus);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr);
 }
 
 TEST(FocusResolverTest, FocusRequestsCanBePending) {
@@ -158,8 +161,100 @@
     // Window visibility changes and the window gets focused
     invisibleWindow->setVisible(true);
     changes = focusResolver.setInputWindows(request.displayId, windows);
-    ASSERT_EQ(nullptr, changes->oldFocus);
-    ASSERT_EQ(invisibleWindowToken, changes->newFocus);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ invisibleWindowToken);
+}
+
+TEST(FocusResolverTest, FocusRequestsArePersistent) {
+    sp<IBinder> windowToken = new BBinder();
+    std::vector<sp<InputWindowHandle>> windows;
+
+    sp<FakeWindowHandle> window = new FakeWindowHandle("Test Window", windowToken,
+                                                       false /* focusable */, true /* visible */);
+    windows.push_back(window);
+
+    // non-focusable window cannot get focused
+    FocusRequest request;
+    request.displayId = 42;
+    request.token = windowToken;
+    FocusResolver focusResolver;
+    std::optional<FocusResolver::FocusChanges> changes =
+            focusResolver.setFocusedWindow(request, windows);
+    ASSERT_FALSE(changes);
+
+    // Focusability changes and the window gets focused
+    window->setFocusable(true);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
+
+    // Visibility changes and the window loses focus
+    window->setVisible(false);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr);
+
+    // Visibility changes and the window gets focused
+    window->setVisible(true);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
+
+    // Window is gone and the window loses focus
+    changes = focusResolver.setInputWindows(request.displayId, {});
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr);
+
+    // Window returns and the window gains focus
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
+}
+
+TEST(FocusResolverTest, ConditionalFocusRequestsAreNotPersistent) {
+    sp<IBinder> hostWindowToken = new BBinder();
+    std::vector<sp<InputWindowHandle>> windows;
+
+    sp<FakeWindowHandle> hostWindow =
+            new FakeWindowHandle("Host Window", hostWindowToken, true /* focusable */,
+                                 true /* visible */);
+    windows.push_back(hostWindow);
+    sp<IBinder> embeddedWindowToken = new BBinder();
+    sp<FakeWindowHandle> embeddedWindow =
+            new FakeWindowHandle("Embedded Window", embeddedWindowToken, true /* focusable */,
+                                 true /* visible */);
+    windows.push_back(embeddedWindow);
+
+    FocusRequest request;
+    request.displayId = 42;
+    request.token = hostWindowToken;
+    FocusResolver focusResolver;
+    std::optional<FocusResolver::FocusChanges> changes =
+            focusResolver.setFocusedWindow(request, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ hostWindowToken);
+
+    request.focusedToken = hostWindow->getToken();
+    request.token = embeddedWindowToken;
+    changes = focusResolver.setFocusedWindow(request, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken);
+
+    embeddedWindow->setFocusable(false);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    // The embedded window is no longer focusable, provide focus back to the original focused
+    // window.
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken);
+
+    embeddedWindow->setFocusable(true);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    // The embedded window is focusable again, but we it cannot gain focus unless there is another
+    // focus request.
+    ASSERT_FALSE(changes);
+
+    embeddedWindow->setVisible(false);
+    changes = focusResolver.setFocusedWindow(request, windows);
+    // If the embedded window is not visible/focusable, then we do not grant it focus and the
+    // request is dropped.
+    ASSERT_FALSE(changes);
+
+    embeddedWindow->setVisible(true);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    // If the embedded window becomes visble/focusable, nothing changes since the request has been
+    // dropped.
+    ASSERT_FALSE(changes);
 }
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputClassifierConverter_test.cpp b/services/inputflinger/tests/InputClassifierConverter_test.cpp
index f58b628..c0ada9d 100644
--- a/services/inputflinger/tests/InputClassifierConverter_test.cpp
+++ b/services/inputflinger/tests/InputClassifierConverter_test.cpp
@@ -38,13 +38,13 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 2);
     coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.5);
     static constexpr nsecs_t downTime = 2;
-    NotifyMotionArgs motionArgs(1 /*sequenceNum*/, downTime /*eventTime*/, 3 /*deviceId*/,
-                                AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, 4 /*policyFlags*/,
-                                AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/, 0 /*flags*/,
-                                AMETA_NONE, 0 /*buttonState*/, MotionClassification::NONE,
-                                AMOTION_EVENT_EDGE_FLAG_NONE, 1 /*pointerCount*/, &properties,
-                                &coords, 0 /*xPrecision*/, 0 /*yPrecision*/,
-                                AMOTION_EVENT_INVALID_CURSOR_POSITION,
+    NotifyMotionArgs motionArgs(1 /*sequenceNum*/, downTime /*eventTime*/, 2 /*readTime*/,
+                                3 /*deviceId*/, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
+                                4 /*policyFlags*/, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/,
+                                0 /*flags*/, AMETA_NONE, 0 /*buttonState*/,
+                                MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
+                                1 /*pointerCount*/, &properties, &coords, 0 /*xPrecision*/,
+                                0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                 AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime,
                                 {} /*videoFrames*/);
     return motionArgs;
diff --git a/services/inputflinger/tests/InputClassifier_test.cpp b/services/inputflinger/tests/InputClassifier_test.cpp
index ab74a04..a72df01 100644
--- a/services/inputflinger/tests/InputClassifier_test.cpp
+++ b/services/inputflinger/tests/InputClassifier_test.cpp
@@ -41,13 +41,13 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_X, 1);
     coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1);
     static constexpr nsecs_t downTime = 2;
-    NotifyMotionArgs motionArgs(1 /*sequenceNum*/, downTime /*eventTime*/, 3 /*deviceId*/,
-                                AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, 4 /*policyFlags*/,
-                                AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/, 0 /*flags*/,
-                                AMETA_NONE, 0 /*buttonState*/, MotionClassification::NONE,
-                                AMOTION_EVENT_EDGE_FLAG_NONE, 1 /*pointerCount*/, &properties,
-                                &coords, 0 /*xPrecision*/, 0 /*yPrecision*/,
-                                AMOTION_EVENT_INVALID_CURSOR_POSITION,
+    NotifyMotionArgs motionArgs(1 /*sequenceNum*/, downTime /*eventTime*/, 2 /*readTime*/,
+                                3 /*deviceId*/, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
+                                4 /*policyFlags*/, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/,
+                                0 /*flags*/, AMETA_NONE, 0 /*buttonState*/,
+                                MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
+                                1 /*pointerCount*/, &properties, &coords, 0 /*xPrecision*/,
+                                0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                 AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime,
                                 {} /*videoFrames*/);
     return motionArgs;
@@ -85,9 +85,10 @@
 
 TEST_F(InputClassifierTest, SendToNextStage_NotifyKeyArgs) {
     // Create a basic key event and send to classifier
-    NotifyKeyArgs args(1/*sequenceNum*/, 2/*eventTime*/, 3/*deviceId*/, AINPUT_SOURCE_KEYBOARD,
-            ADISPLAY_ID_DEFAULT, 0/*policyFlags*/, AKEY_EVENT_ACTION_DOWN, 4/*flags*/,
-            AKEYCODE_HOME, 5/*scanCode*/, AMETA_NONE, 6/*downTime*/);
+    NotifyKeyArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 21 /*readTime*/, 3 /*deviceId*/,
+                       AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, 0 /*policyFlags*/,
+                       AKEY_EVENT_ACTION_DOWN, 4 /*flags*/, AKEYCODE_HOME, 5 /*scanCode*/,
+                       AMETA_NONE, 6 /*downTime*/);
 
     mClassifier->notifyKey(&args);
     NotifyKeyArgs outArgs;
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 209639c..51f6f5d 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -377,7 +377,7 @@
         mLastNotifySwitch = NotifySwitchArgs(1 /*id*/, when, policyFlags, switchValues, switchMask);
     }
 
-    void pokeUserActivity(nsecs_t, int32_t) override {}
+    void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
 
     bool checkInjectEventsPermissionNonReentrant(int32_t, int32_t) override { return false; }
 
@@ -898,6 +898,8 @@
         mInfo.touchOcclusionMode = mode;
     }
 
+    void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
+
     void setFrame(const Rect& frame) {
         mInfo.frameLeft = frame.left;
         mInfo.frameTop = frame.top;
@@ -908,6 +910,8 @@
         mInfo.addTouchableRegion(frame);
     }
 
+    void addFlags(Flags<InputWindowInfo::Flag> flags) { mInfo.flags |= flags; }
+
     void setFlags(Flags<InputWindowInfo::Flag> flags) { mInfo.flags = flags; }
 
     void setInputFeatures(InputWindowInfo::Feature features) { mInfo.inputFeatures = features; }
@@ -1232,9 +1236,9 @@
 static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
-    NotifyKeyArgs args(/* id */ 0, currentTime, DEVICE_ID, AINPUT_SOURCE_KEYBOARD, displayId,
-                       POLICY_FLAG_PASS_TO_USER, action, /* flags */ 0, AKEYCODE_A, KEY_A,
-                       AMETA_NONE, currentTime);
+    NotifyKeyArgs args(/* id */ 0, currentTime, 0 /*readTime*/, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                       displayId, POLICY_FLAG_PASS_TO_USER, action, /* flags */ 0, AKEYCODE_A,
+                       KEY_A, AMETA_NONE, currentTime);
 
     return args;
 }
@@ -1261,7 +1265,7 @@
 
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid motion event.
-    NotifyMotionArgs args(/* id */ 0, currentTime, DEVICE_ID, source, displayId,
+    NotifyMotionArgs args(/* id */ 0, currentTime, 0 /*readTime*/, DEVICE_ID, source, displayId,
                           POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0, /* flags */ 0,
                           AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
                           AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
@@ -4264,6 +4268,19 @@
 class InputDispatcherUntrustedTouchesTest : public InputDispatcherTest {
 protected:
     constexpr static const float MAXIMUM_OBSCURING_OPACITY = 0.8;
+
+    constexpr static const float OPACITY_ABOVE_THRESHOLD = 0.9;
+    static_assert(OPACITY_ABOVE_THRESHOLD > MAXIMUM_OBSCURING_OPACITY);
+
+    constexpr static const float OPACITY_BELOW_THRESHOLD = 0.7;
+    static_assert(OPACITY_BELOW_THRESHOLD < MAXIMUM_OBSCURING_OPACITY);
+
+    // When combined twice, ie 1 - (1 - 0.5)*(1 - 0.5) = 0.75 < 8, is still below the threshold
+    constexpr static const float OPACITY_FAR_BELOW_THRESHOLD = 0.5;
+    static_assert(OPACITY_FAR_BELOW_THRESHOLD < MAXIMUM_OBSCURING_OPACITY);
+    static_assert(1 - (1 - OPACITY_FAR_BELOW_THRESHOLD) * (1 - OPACITY_FAR_BELOW_THRESHOLD) <
+                  MAXIMUM_OBSCURING_OPACITY);
+
     static const int32_t TOUCHED_APP_UID = 10001;
     static const int32_t APP_B_UID = 10002;
     static const int32_t APP_C_UID = 10003;
@@ -4318,6 +4335,28 @@
     mTouchWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherUntrustedTouchesTest,
+       WindowWithBlockUntrustedOcclusionModeWithOpacityBelowThreshold_BlocksTouch) {
+    const sp<FakeWindowHandle>& w =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.7f);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+
+    touch();
+
+    mTouchWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherUntrustedTouchesTest,
+       WindowWithBlockUntrustedOcclusionMode_DoesNotReceiveTouch) {
+    const sp<FakeWindowHandle>& w =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+
+    touch();
+
+    w->assertNoEvents();
+}
+
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithAllowOcclusionMode_AllowsTouch) {
     const sp<FakeWindowHandle>& w = getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::ALLOW);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
@@ -4358,9 +4397,52 @@
     mTouchWindow->consumeAnyMotionDown();
 }
 
+TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithZeroOpacity_DoesNotReceiveTouch) {
+    const sp<FakeWindowHandle>& w =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+
+    touch();
+
+    w->assertNoEvents();
+}
+
+/**
+ * This is important to make sure apps can't indirectly learn the position of touches (outside vs
+ * inside) while letting them pass-through. Note that even though touch passes through the occluding
+ * window, the occluding window will still receive ACTION_OUTSIDE event.
+ */
+TEST_F(InputDispatcherUntrustedTouchesTest,
+       WindowWithZeroOpacityAndWatchOutside_ReceivesOutsideEvent) {
+    const sp<FakeWindowHandle>& w =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
+    w->addFlags(InputWindowInfo::Flag::WATCH_OUTSIDE_TOUCH);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+
+    touch();
+
+    w->consumeMotionOutside();
+}
+
+TEST_F(InputDispatcherUntrustedTouchesTest, OutsideEvent_HasZeroCoordinates) {
+    const sp<FakeWindowHandle>& w =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
+    w->addFlags(InputWindowInfo::Flag::WATCH_OUTSIDE_TOUCH);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+
+    touch();
+
+    InputEvent* event = w->consume();
+    ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType());
+    MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
+    EXPECT_EQ(0.0f, motionEvent.getRawPointerCoords(0)->getX());
+    EXPECT_EQ(0.0f, motionEvent.getRawPointerCoords(0)->getY());
+}
+
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithOpacityBelowThreshold_AllowsTouch) {
     const sp<FakeWindowHandle>& w =
-            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY, 0.7f);
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_BELOW_THRESHOLD);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
 
     touch();
@@ -4381,7 +4463,8 @@
 
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithOpacityAboveThreshold_BlocksTouch) {
     const sp<FakeWindowHandle>& w =
-            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY, 0.9f);
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_ABOVE_THRESHOLD);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
 
     touch();
@@ -4392,9 +4475,11 @@
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowsWithCombinedOpacityAboveThreshold_BlocksTouch) {
     // Resulting opacity = 1 - (1 - 0.7)*(1 - 0.7) = .91
     const sp<FakeWindowHandle>& w1 =
-            getOccludingWindow(APP_B_UID, "B1", TouchOcclusionMode::USE_OPACITY, 0.7f);
+            getOccludingWindow(APP_B_UID, "B1", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_BELOW_THRESHOLD);
     const sp<FakeWindowHandle>& w2 =
-            getOccludingWindow(APP_B_UID, "B2", TouchOcclusionMode::USE_OPACITY, 0.7f);
+            getOccludingWindow(APP_B_UID, "B2", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_BELOW_THRESHOLD);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w1, w2, mTouchWindow}}});
 
     touch();
@@ -4405,9 +4490,11 @@
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowsWithCombinedOpacityBelowThreshold_AllowsTouch) {
     // Resulting opacity = 1 - (1 - 0.5)*(1 - 0.5) = .75
     const sp<FakeWindowHandle>& w1 =
-            getOccludingWindow(APP_B_UID, "B1", TouchOcclusionMode::USE_OPACITY, 0.5f);
+            getOccludingWindow(APP_B_UID, "B1", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_FAR_BELOW_THRESHOLD);
     const sp<FakeWindowHandle>& w2 =
-            getOccludingWindow(APP_B_UID, "B2", TouchOcclusionMode::USE_OPACITY, 0.5f);
+            getOccludingWindow(APP_B_UID, "B2", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_FAR_BELOW_THRESHOLD);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w1, w2, mTouchWindow}}});
 
     touch();
@@ -4418,9 +4505,11 @@
 TEST_F(InputDispatcherUntrustedTouchesTest,
        WindowsFromDifferentAppsEachBelowThreshold_AllowsTouch) {
     const sp<FakeWindowHandle>& wB =
-            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY, 0.7f);
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_BELOW_THRESHOLD);
     const sp<FakeWindowHandle>& wC =
-            getOccludingWindow(APP_C_UID, "C", TouchOcclusionMode::USE_OPACITY, 0.7f);
+            getOccludingWindow(APP_C_UID, "C", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_BELOW_THRESHOLD);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wB, wC, mTouchWindow}}});
 
     touch();
@@ -4430,9 +4519,11 @@
 
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowsFromDifferentAppsOneAboveThreshold_BlocksTouch) {
     const sp<FakeWindowHandle>& wB =
-            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY, 0.7f);
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_BELOW_THRESHOLD);
     const sp<FakeWindowHandle>& wC =
-            getOccludingWindow(APP_C_UID, "C", TouchOcclusionMode::USE_OPACITY, 0.9f);
+            getOccludingWindow(APP_C_UID, "C", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_ABOVE_THRESHOLD);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wB, wC, mTouchWindow}}});
 
     touch();
@@ -4443,9 +4534,11 @@
 TEST_F(InputDispatcherUntrustedTouchesTest,
        WindowWithOpacityAboveThresholdAndSelfWindow_BlocksTouch) {
     const sp<FakeWindowHandle>& wA =
-            getOccludingWindow(TOUCHED_APP_UID, "T", TouchOcclusionMode::USE_OPACITY, 0.7f);
+            getOccludingWindow(TOUCHED_APP_UID, "T", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_BELOW_THRESHOLD);
     const sp<FakeWindowHandle>& wB =
-            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY, 0.9f);
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_ABOVE_THRESHOLD);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wA, wB, mTouchWindow}}});
 
     touch();
@@ -4456,9 +4549,11 @@
 TEST_F(InputDispatcherUntrustedTouchesTest,
        WindowWithOpacityBelowThresholdAndSelfWindow_AllowsTouch) {
     const sp<FakeWindowHandle>& wA =
-            getOccludingWindow(TOUCHED_APP_UID, "T", TouchOcclusionMode::USE_OPACITY, 0.9f);
+            getOccludingWindow(TOUCHED_APP_UID, "T", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_ABOVE_THRESHOLD);
     const sp<FakeWindowHandle>& wB =
-            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY, 0.7f);
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_BELOW_THRESHOLD);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wA, wB, mTouchWindow}}});
 
     touch();
@@ -4468,7 +4563,8 @@
 
 TEST_F(InputDispatcherUntrustedTouchesTest, SelfWindowWithOpacityAboveThreshold_AllowsTouch) {
     const sp<FakeWindowHandle>& w =
-            getOccludingWindow(TOUCHED_APP_UID, "T", TouchOcclusionMode::USE_OPACITY, 0.9f);
+            getOccludingWindow(TOUCHED_APP_UID, "T", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_ABOVE_THRESHOLD);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
 
     touch();
@@ -4513,7 +4609,59 @@
        OpacityThresholdIs1AndWindowBelowThreshold_AllowsTouch) {
     mDispatcher->setMaximumObscuringOpacityForTouch(1.0f);
     const sp<FakeWindowHandle>& w =
-            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY, 0.9f);
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_ABOVE_THRESHOLD);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+
+    touch();
+
+    mTouchWindow->consumeAnyMotionDown();
+}
+
+TEST_F(InputDispatcherUntrustedTouchesTest,
+       WindowWithBlockUntrustedModeAndWindowWithOpacityBelowFromSameApp_BlocksTouch) {
+    const sp<FakeWindowHandle>& w1 =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED,
+                               OPACITY_BELOW_THRESHOLD);
+    const sp<FakeWindowHandle>& w2 =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_BELOW_THRESHOLD);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w1, w2, mTouchWindow}}});
+
+    touch();
+
+    mTouchWindow->assertNoEvents();
+}
+
+/**
+ * Window B of BLOCK_UNTRUSTED occlusion mode is enough to block the touch, we're testing that the
+ * addition of another window (C) of USE_OPACITY occlusion mode and opacity below the threshold
+ * (which alone would result in allowing touches) does not affect the blocking behavior.
+ */
+TEST_F(InputDispatcherUntrustedTouchesTest,
+       WindowWithBlockUntrustedModeAndWindowWithOpacityBelowFromDifferentApps_BlocksTouch) {
+    const sp<FakeWindowHandle>& wB =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED,
+                               OPACITY_BELOW_THRESHOLD);
+    const sp<FakeWindowHandle>& wC =
+            getOccludingWindow(APP_C_UID, "C", TouchOcclusionMode::USE_OPACITY,
+                               OPACITY_BELOW_THRESHOLD);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wB, wC, mTouchWindow}}});
+
+    touch();
+
+    mTouchWindow->assertNoEvents();
+}
+
+/**
+ * This test is testing that a window from a different UID but with same application token doesn't
+ * block the touch. Apps can share the application token for close UI collaboration for example.
+ */
+TEST_F(InputDispatcherUntrustedTouchesTest,
+       WindowWithSameApplicationTokenFromDifferentApp_AllowsTouch) {
+    const sp<FakeWindowHandle>& w =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED);
+    w->setApplicationToken(mTouchWindow->getApplicationToken());
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
 
     touch();
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 409c62a..d69bb6a 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -22,6 +22,7 @@
 #include <InputReaderBase.h>
 #include <InputReaderFactory.h>
 #include <KeyboardInputMapper.h>
+#include <LightInputMapper.h>
 #include <MultiTouchInputMapper.h>
 #include <SensorInputMapper.h>
 #include <SingleTouchInputMapper.h>
@@ -36,6 +37,7 @@
 #include <math.h>
 
 #include <memory>
+#include <regex>
 #include "input/DisplayViewport.h"
 #include "input/Input.h"
 
@@ -48,7 +50,8 @@
 static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms;
 
 // An arbitrary time value.
-static const nsecs_t ARBITRARY_TIME = 1234;
+static constexpr nsecs_t ARBITRARY_TIME = 1234;
+static constexpr nsecs_t READ_TIME = 4321;
 
 // Arbitrary display properties.
 static constexpr int32_t DISPLAY_ID = 0;
@@ -70,6 +73,9 @@
 static constexpr int32_t THIRD_TRACKING_ID = 2;
 static constexpr int32_t BATTERY_STATUS = 4;
 static constexpr int32_t BATTERY_CAPACITY = 66;
+static constexpr int32_t LIGHT_BRIGHTNESS = 0x55000000;
+static constexpr int32_t LIGHT_COLOR = 0x7F448866;
+static constexpr int32_t LIGHT_PLAYER_ID = 2;
 
 // Error tolerance for floating point assertions.
 static const float EPSILON = 0.001f;
@@ -83,6 +89,10 @@
     return (x + y) / 2;
 }
 
+// Mapping for light color name and the light color
+const std::unordered_map<std::string, LightColor> LIGHT_COLORS = {{"red", LightColor::RED},
+                                                                  {"green", LightColor::GREEN},
+                                                                  {"blue", LightColor::BLUE}};
 
 // --- FakePointerController ---
 
@@ -412,6 +422,12 @@
     std::vector<RawEvent> mEvents GUARDED_BY(mLock);
     std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> mVideoFrames;
     std::vector<int32_t> mVibrators = {0, 1};
+    std::unordered_map<int32_t, RawLightInfo> mRawLightInfos;
+    // Simulates a device light brightness, from light id to light brightness.
+    std::unordered_map<int32_t /* lightId */, int32_t /* brightness*/> mLightBrightness;
+    // Simulates a device light intensities, from light id to light intensities map.
+    std::unordered_map<int32_t /* lightId */, std::unordered_map<LightColor, int32_t>>
+            mLightIntensities;
 
 public:
     virtual ~FakeEventHub() {
@@ -427,14 +443,14 @@
         device->identifier.name = name;
         mDevices.add(deviceId, device);
 
-        enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0);
+        enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0);
     }
 
     void removeDevice(int32_t deviceId) {
         delete mDevices.valueFor(deviceId);
         mDevices.removeItem(deviceId);
 
-        enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0);
+        enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0);
     }
 
     bool isDeviceEnabled(int32_t deviceId) {
@@ -475,7 +491,7 @@
     }
 
     void finishDeviceScan() {
-        enqueueEvent(ARBITRARY_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0);
+        enqueueEvent(ARBITRARY_TIME, READ_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0);
     }
 
     void addConfigurationProperty(int32_t deviceId, const String8& key, const String8& value) {
@@ -562,6 +578,19 @@
         device->mscBitmask.loadFromBuffer(buffer);
     }
 
+    void addRawLightInfo(int32_t rawId, RawLightInfo&& info) {
+        mRawLightInfos.emplace(rawId, std::move(info));
+    }
+
+    void fakeLightBrightness(int32_t rawId, int32_t brightness) {
+        mLightBrightness.emplace(rawId, brightness);
+    }
+
+    void fakeLightIntensities(int32_t rawId,
+                              const std::unordered_map<LightColor, int32_t> intensities) {
+        mLightIntensities.emplace(rawId, std::move(intensities));
+    }
+
     bool getLedState(int32_t deviceId, int32_t led) {
         Device* device = getDevice(deviceId);
         return device->leds.valueFor(led);
@@ -576,11 +605,12 @@
         device->virtualKeys.push_back(definition);
     }
 
-    void enqueueEvent(nsecs_t when, int32_t deviceId, int32_t type,
-            int32_t code, int32_t value) {
+    void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code,
+                      int32_t value) {
         std::scoped_lock<std::mutex> lock(mLock);
         RawEvent event;
         event.when = when;
+        event.readTime = readTime;
         event.deviceId = deviceId;
         event.type = type;
         event.code = code;
@@ -869,6 +899,48 @@
 
     std::optional<int32_t> getBatteryStatus(int32_t) const override { return BATTERY_STATUS; }
 
+    const std::vector<int32_t> getRawLightIds(int32_t deviceId) override {
+        std::vector<int32_t> ids;
+        for (const auto& [rawId, info] : mRawLightInfos) {
+            ids.push_back(rawId);
+        }
+        return ids;
+    }
+
+    std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) override {
+        auto it = mRawLightInfos.find(lightId);
+        if (it == mRawLightInfos.end()) {
+            return std::nullopt;
+        }
+        return it->second;
+    }
+
+    void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override {
+        mLightBrightness.emplace(lightId, brightness);
+    }
+
+    void setLightIntensities(int32_t deviceId, int32_t lightId,
+                             std::unordered_map<LightColor, int32_t> intensities) override {
+        mLightIntensities.emplace(lightId, intensities);
+    };
+
+    std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) override {
+        auto lightIt = mLightBrightness.find(lightId);
+        if (lightIt == mLightBrightness.end()) {
+            return std::nullopt;
+        }
+        return lightIt->second;
+    }
+
+    std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
+            int32_t deviceId, int32_t lightId) override {
+        auto lightIt = mLightIntensities.find(lightId);
+        if (lightIt == mLightIntensities.end()) {
+            return std::nullopt;
+        }
+        return lightIt->second;
+    };
+
     virtual bool isExternal(int32_t) const {
         return false;
     }
@@ -1704,18 +1776,21 @@
 TEST_F(InputReaderTest, LoopOnce_ForwardsRawEventsToMappers) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
     constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
+    constexpr nsecs_t when = 0;
     constexpr int32_t eventHubId = 1;
+    constexpr nsecs_t readTime = 2;
     FakeInputMapper& mapper =
             addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass,
                                          AINPUT_SOURCE_KEYBOARD, nullptr);
 
-    mFakeEventHub->enqueueEvent(0, eventHubId, EV_KEY, KEY_A, 1);
+    mFakeEventHub->enqueueEvent(when, readTime, eventHubId, EV_KEY, KEY_A, 1);
     mReader->loopOnce();
     ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty());
 
     RawEvent event;
     ASSERT_NO_FATAL_FAILURE(mapper.assertProcessWasCalled(&event));
-    ASSERT_EQ(0, event.when);
+    ASSERT_EQ(when, event.when);
+    ASSERT_EQ(readTime, event.readTime);
     ASSERT_EQ(eventHubId, event.deviceId);
     ASSERT_EQ(EV_KEY, event.type);
     ASSERT_EQ(KEY_A, event.code);
@@ -1976,6 +2051,49 @@
     ASSERT_EQ(mReader->getBatteryStatus(deviceId), BATTERY_STATUS);
 }
 
+class FakeLightInputMapper : public FakeInputMapper {
+public:
+    FakeLightInputMapper(InputDeviceContext& deviceContext, uint32_t sources)
+          : FakeInputMapper(deviceContext, sources) {}
+
+    bool setLightColor(int32_t lightId, int32_t color) override {
+        getDeviceContext().setLightBrightness(lightId, color >> 24);
+        return true;
+    }
+
+    std::optional<int32_t> getLightColor(int32_t lightId) override {
+        std::optional<int32_t> result = getDeviceContext().getLightBrightness(lightId);
+        if (!result.has_value()) {
+            return std::nullopt;
+        }
+        return result.value() << 24;
+    }
+};
+
+TEST_F(InputReaderTest, LightGetColor) {
+    constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+    Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::LIGHT;
+    constexpr int32_t eventHubId = 1;
+    const char* DEVICE_LOCATION = "BLUETOOTH";
+    std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
+    FakeLightInputMapper& mapper =
+            device->addMapper<FakeLightInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
+    mReader->pushNextDevice(device);
+    RawLightInfo info = {.id = 1,
+                         .name = "Mono",
+                         .maxBrightness = 255,
+                         .flags = InputLightClass::BRIGHTNESS,
+                         .path = ""};
+    mFakeEventHub->addRawLightInfo(1 /* rawId */, std::move(info));
+    mFakeEventHub->fakeLightBrightness(1 /* rawId */, 0x55);
+
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
+    ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled());
+
+    ASSERT_TRUE(mReader->setLightColor(deviceId, 1 /* lightId */, LIGHT_BRIGHTNESS));
+    ASSERT_EQ(mReader->getLightColor(deviceId, 1 /* lightId */), LIGHT_BRIGHTNESS);
+}
+
 // --- InputReaderIntegrationTest ---
 
 // These tests create and interact with the InputReader only through its interface.
@@ -2080,12 +2198,14 @@
     ASSERT_NE(prevId, keyArgs.id);
     prevId = keyArgs.id;
     ASSERT_LE(prevTimestamp, keyArgs.eventTime);
+    ASSERT_LE(keyArgs.eventTime, keyArgs.readTime);
     prevTimestamp = keyArgs.eventTime;
 
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
     ASSERT_NE(prevId, keyArgs.id);
     ASSERT_LE(prevTimestamp, keyArgs.eventTime);
+    ASSERT_LE(keyArgs.eventTime, keyArgs.readTime);
 }
 
 /**
@@ -2562,9 +2682,11 @@
         mFakePolicy->clearViewports();
     }
 
-    void process(InputMapper& mapper, nsecs_t when, int32_t type, int32_t code, int32_t value) {
+    void process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, int32_t type, int32_t code,
+                 int32_t value) {
         RawEvent event;
         event.when = when;
+        event.readTime = readTime;
         event.deviceId = mapper.getDeviceContext().getEventHubId();
         event.type = type;
         event.code = code;
@@ -2643,10 +2765,10 @@
 TEST_F(SwitchInputMapperTest, Process) {
     SwitchInputMapper& mapper = addMapperAndConfigure<SwitchInputMapper>();
 
-    process(mapper, ARBITRARY_TIME, EV_SW, SW_LID, 1);
-    process(mapper, ARBITRARY_TIME, EV_SW, SW_JACK_PHYSICAL_INSERT, 1);
-    process(mapper, ARBITRARY_TIME, EV_SW, SW_HEADPHONE_INSERT, 0);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_LID, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_JACK_PHYSICAL_INSERT, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_HEADPHONE_INSERT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
 
     NotifySwitchArgs args;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySwitchWasCalled(&args));
@@ -2816,11 +2938,11 @@
                                     std::chrono::microseconds(10000),
                                     std::chrono::microseconds(0)));
     ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(EVENTHUB_ID));
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_X, 20000);
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_Y, -20000);
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_Z, 40000);
-    process(mapper, ARBITRARY_TIME, EV_MSC, MSC_TIMESTAMP, 1000);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, 20000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, -20000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Z, 40000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_TIMESTAMP, 1000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
 
     NotifySensorArgs args;
     std::vector<float> values = {20000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT,
@@ -2846,11 +2968,11 @@
                                     std::chrono::microseconds(10000),
                                     std::chrono::microseconds(0)));
     ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(EVENTHUB_ID));
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_RX, 20000);
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_RY, -20000);
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_RZ, 40000);
-    process(mapper, ARBITRARY_TIME, EV_MSC, MSC_TIMESTAMP, 1000);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RX, 20000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RY, -20000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RZ, 40000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_TIMESTAMP, 1000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
 
     NotifySensorArgs args;
     std::vector<float> values = {20000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT,
@@ -2883,14 +3005,136 @@
     BatteryInputMapper& mapper = addMapperAndConfigure<BatteryInputMapper>();
 
     ASSERT_TRUE(mapper.getBatteryCapacity());
-    ASSERT_EQ(*mapper.getBatteryCapacity(), BATTERY_CAPACITY);
+    ASSERT_EQ(mapper.getBatteryCapacity().value_or(-1), BATTERY_CAPACITY);
 }
 
 TEST_F(BatteryInputMapperTest, GetBatteryStatus) {
     BatteryInputMapper& mapper = addMapperAndConfigure<BatteryInputMapper>();
 
     ASSERT_TRUE(mapper.getBatteryStatus());
-    ASSERT_EQ(*mapper.getBatteryStatus(), BATTERY_STATUS);
+    ASSERT_EQ(mapper.getBatteryStatus().value_or(-1), BATTERY_STATUS);
+}
+
+// --- LightInputMapperTest ---
+class LightInputMapperTest : public InputMapperTest {
+protected:
+    void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::LIGHT); }
+};
+
+TEST_F(LightInputMapperTest, GetSources) {
+    LightInputMapper& mapper = addMapperAndConfigure<LightInputMapper>();
+
+    ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mapper.getSources());
+}
+
+TEST_F(LightInputMapperTest, SingleLight) {
+    RawLightInfo infoSingle = {.id = 1,
+                               .name = "Mono",
+                               .maxBrightness = 255,
+                               .flags = InputLightClass::BRIGHTNESS,
+                               .path = ""};
+    mFakeEventHub->addRawLightInfo(infoSingle.id, std::move(infoSingle));
+
+    LightInputMapper& mapper = addMapperAndConfigure<LightInputMapper>();
+    InputDeviceInfo info;
+    mapper.populateDeviceInfo(&info);
+    const auto& ids = info.getLightIds();
+    ASSERT_EQ(1UL, ids.size());
+    ASSERT_EQ(InputDeviceLightType::SINGLE, info.getLightInfo(ids[0])->type);
+
+    ASSERT_TRUE(mapper.setLightColor(ids[0], LIGHT_BRIGHTNESS));
+    ASSERT_EQ(mapper.getLightColor(ids[0]).value_or(-1), LIGHT_BRIGHTNESS);
+}
+
+TEST_F(LightInputMapperTest, RGBLight) {
+    RawLightInfo infoRed = {.id = 1,
+                            .name = "red",
+                            .maxBrightness = 255,
+                            .flags = InputLightClass::BRIGHTNESS | InputLightClass::RED,
+                            .path = ""};
+    RawLightInfo infoGreen = {.id = 2,
+                              .name = "green",
+                              .maxBrightness = 255,
+                              .flags = InputLightClass::BRIGHTNESS | InputLightClass::GREEN,
+                              .path = ""};
+    RawLightInfo infoBlue = {.id = 3,
+                             .name = "blue",
+                             .maxBrightness = 255,
+                             .flags = InputLightClass::BRIGHTNESS | InputLightClass::BLUE,
+                             .path = ""};
+    mFakeEventHub->addRawLightInfo(infoRed.id, std::move(infoRed));
+    mFakeEventHub->addRawLightInfo(infoGreen.id, std::move(infoGreen));
+    mFakeEventHub->addRawLightInfo(infoBlue.id, std::move(infoBlue));
+
+    LightInputMapper& mapper = addMapperAndConfigure<LightInputMapper>();
+    InputDeviceInfo info;
+    mapper.populateDeviceInfo(&info);
+    const auto& ids = info.getLightIds();
+    ASSERT_EQ(1UL, ids.size());
+    ASSERT_EQ(InputDeviceLightType::RGB, info.getLightInfo(ids[0])->type);
+
+    ASSERT_TRUE(mapper.setLightColor(ids[0], LIGHT_COLOR));
+    ASSERT_EQ(mapper.getLightColor(ids[0]).value_or(-1), LIGHT_COLOR);
+}
+
+TEST_F(LightInputMapperTest, MultiColorRGBLight) {
+    RawLightInfo infoColor = {.id = 1,
+                              .name = "red",
+                              .maxBrightness = 255,
+                              .flags = InputLightClass::BRIGHTNESS |
+                                      InputLightClass::MULTI_INTENSITY |
+                                      InputLightClass::MULTI_INDEX,
+                              .path = ""};
+
+    mFakeEventHub->addRawLightInfo(infoColor.id, std::move(infoColor));
+
+    LightInputMapper& mapper = addMapperAndConfigure<LightInputMapper>();
+    InputDeviceInfo info;
+    mapper.populateDeviceInfo(&info);
+    const auto& ids = info.getLightIds();
+    ASSERT_EQ(1UL, ids.size());
+    ASSERT_EQ(InputDeviceLightType::MULTI_COLOR, info.getLightInfo(ids[0])->type);
+
+    ASSERT_TRUE(mapper.setLightColor(ids[0], LIGHT_COLOR));
+    ASSERT_EQ(mapper.getLightColor(ids[0]).value_or(-1), LIGHT_COLOR);
+}
+
+TEST_F(LightInputMapperTest, PlayerIdLight) {
+    RawLightInfo info1 = {.id = 1,
+                          .name = "player1",
+                          .maxBrightness = 255,
+                          .flags = InputLightClass::BRIGHTNESS,
+                          .path = ""};
+    RawLightInfo info2 = {.id = 2,
+                          .name = "player2",
+                          .maxBrightness = 255,
+                          .flags = InputLightClass::BRIGHTNESS,
+                          .path = ""};
+    RawLightInfo info3 = {.id = 3,
+                          .name = "player3",
+                          .maxBrightness = 255,
+                          .flags = InputLightClass::BRIGHTNESS,
+                          .path = ""};
+    RawLightInfo info4 = {.id = 4,
+                          .name = "player4",
+                          .maxBrightness = 255,
+                          .flags = InputLightClass::BRIGHTNESS,
+                          .path = ""};
+    mFakeEventHub->addRawLightInfo(info1.id, std::move(info1));
+    mFakeEventHub->addRawLightInfo(info2.id, std::move(info2));
+    mFakeEventHub->addRawLightInfo(info3.id, std::move(info3));
+    mFakeEventHub->addRawLightInfo(info4.id, std::move(info4));
+
+    LightInputMapper& mapper = addMapperAndConfigure<LightInputMapper>();
+    InputDeviceInfo info;
+    mapper.populateDeviceInfo(&info);
+    const auto& ids = info.getLightIds();
+    ASSERT_EQ(1UL, ids.size());
+    ASSERT_EQ(InputDeviceLightType::PLAYER_ID, info.getLightInfo(ids[0])->type);
+
+    ASSERT_FALSE(mapper.setLightColor(ids[0], LIGHT_COLOR));
+    ASSERT_TRUE(mapper.setLightPlayerId(ids[0], LIGHT_PLAYER_ID));
+    ASSERT_EQ(mapper.getLightPlayerId(ids[0]).value_or(-1), LIGHT_PLAYER_ID);
 }
 
 // --- KeyboardInputMapperTest ---
@@ -2919,14 +3163,14 @@
                                                   int32_t rotatedKeyCode, int32_t displayId) {
     NotifyKeyArgs args;
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, originalScanCode, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
     ASSERT_EQ(originalScanCode, args.scanCode);
     ASSERT_EQ(rotatedKeyCode, args.keyCode);
     ASSERT_EQ(displayId, args.displayId);
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, originalScanCode, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
     ASSERT_EQ(originalScanCode, args.scanCode);
@@ -2959,7 +3203,7 @@
     mapper.updateMetaState(AKEYCODE_NUM_LOCK);
 
     // Key down by scan code.
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_HOME, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
     NotifyKeyArgs args;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(DEVICE_ID, args.deviceId);
@@ -2974,7 +3218,7 @@
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
 
     // Key up by scan code.
-    process(mapper, ARBITRARY_TIME + 1, EV_KEY, KEY_HOME, 0);
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(DEVICE_ID, args.deviceId);
     ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
@@ -2988,8 +3232,8 @@
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
 
     // Key down by usage code.
-    process(mapper, ARBITRARY_TIME, EV_MSC, MSC_SCAN, USAGE_A);
-    process(mapper, ARBITRARY_TIME, EV_KEY, 0, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_A);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, 0, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(DEVICE_ID, args.deviceId);
     ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
@@ -3003,8 +3247,8 @@
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
 
     // Key up by usage code.
-    process(mapper, ARBITRARY_TIME, EV_MSC, MSC_SCAN, USAGE_A);
-    process(mapper, ARBITRARY_TIME + 1, EV_KEY, 0, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_A);
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, 0, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(DEVICE_ID, args.deviceId);
     ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
@@ -3018,8 +3262,8 @@
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
 
     // Key down with unknown scan code or usage code.
-    process(mapper, ARBITRARY_TIME, EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_UNKNOWN, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UNKNOWN, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(DEVICE_ID, args.deviceId);
     ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
@@ -3033,8 +3277,8 @@
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
 
     // Key up with unknown scan code or usage code.
-    process(mapper, ARBITRARY_TIME, EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
-    process(mapper, ARBITRARY_TIME + 1, EV_KEY, KEY_UNKNOWN, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_UNKNOWN, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(DEVICE_ID, args.deviceId);
     ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
@@ -3048,6 +3292,28 @@
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
 }
 
+/**
+ * Ensure that the readTime is set to the time when the EV_KEY is received.
+ */
+TEST_F(KeyboardInputMapperTest, Process_SendsReadTime) {
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+
+    KeyboardInputMapper& mapper =
+            addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    NotifyKeyArgs args;
+
+    // Key down
+    process(mapper, ARBITRARY_TIME, 12 /*readTime*/, EV_KEY, KEY_HOME, 1);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(12, args.readTime);
+
+    // Key up
+    process(mapper, ARBITRARY_TIME, 15 /*readTime*/, EV_KEY, KEY_HOME, 1);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(15, args.readTime);
+}
+
 TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) {
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFTSHIFT, 0, AKEYCODE_SHIFT_LEFT, 0);
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0);
@@ -3064,7 +3330,7 @@
     mapper.updateMetaState(AKEYCODE_NUM_LOCK);
 
     // Metakey down.
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_LEFTSHIFT, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_LEFTSHIFT, 1);
     NotifyKeyArgs args;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
@@ -3072,19 +3338,19 @@
     ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertUpdateGlobalMetaStateWasCalled());
 
     // Key down.
-    process(mapper, ARBITRARY_TIME + 1, EV_KEY, KEY_A, 1);
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_A, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper.getMetaState());
 
     // Key up.
-    process(mapper, ARBITRARY_TIME + 2, EV_KEY, KEY_A, 0);
+    process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, KEY_A, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper.getMetaState());
 
     // Metakey up.
-    process(mapper, ARBITRARY_TIME + 3, EV_KEY, KEY_LEFTSHIFT, 0);
+    process(mapper, ARBITRARY_TIME + 3, READ_TIME, EV_KEY, KEY_LEFTSHIFT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AMETA_NONE, args.metaState);
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -3171,7 +3437,7 @@
     NotifyKeyArgs args;
     clearViewports();
     prepareDisplay(DISPLAY_ORIENTATION_270);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_UP, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
     ASSERT_EQ(KEY_UP, args.scanCode);
@@ -3179,7 +3445,7 @@
 
     clearViewports();
     prepareDisplay(DISPLAY_ORIENTATION_180);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_UP, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
     ASSERT_EQ(KEY_UP, args.scanCode);
@@ -3197,16 +3463,16 @@
     NotifyKeyArgs args;
 
     // Display id should be ADISPLAY_ID_NONE without any display configuration.
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_UP, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_UP, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(ADISPLAY_ID_NONE, args.displayId);
 
     prepareDisplay(DISPLAY_ORIENTATION_0);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_UP, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_UP, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(ADISPLAY_ID_NONE, args.displayId);
 }
@@ -3227,9 +3493,9 @@
 
     setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0,
                                  UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_UP, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_UP, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(DISPLAY_ID, args.displayId);
 
@@ -3237,9 +3503,9 @@
     clearViewports();
     setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0,
                                  UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_UP, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_UP, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(newDisplayId, args.displayId);
 }
@@ -3303,48 +3569,48 @@
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
 
     // Toggle caps lock on.
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_CAPSLOCK, 1);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
     ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
     ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState());
 
     // Toggle num lock on.
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_NUMLOCK, 1);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_NUMLOCK, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
     ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
     ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
     ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper.getMetaState());
 
     // Toggle caps lock off.
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_CAPSLOCK, 1);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
     ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
     ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper.getMetaState());
 
     // Toggle scroll lock on.
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
     ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
     ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
     ASSERT_EQ(AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper.getMetaState());
 
     // Toggle num lock off.
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_NUMLOCK, 1);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_NUMLOCK, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
     ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
     ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper.getMetaState());
 
     // Toggle scroll lock off.
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
@@ -3372,7 +3638,7 @@
 
     NotifyKeyArgs args;
     // Press button "A"
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_A, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_A, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AMETA_NONE, args.metaState);
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -3380,7 +3646,7 @@
     ASSERT_EQ(AKEYCODE_BUTTON_A, args.keyCode);
 
     // Button up.
-    process(mapper, ARBITRARY_TIME + 2, EV_KEY, BTN_A, 0);
+    process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, BTN_A, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AMETA_NONE, args.metaState);
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -3487,20 +3753,20 @@
     ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
 
     // Toggle caps lock on.
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_CAPSLOCK, 1);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
     ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
     ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState());
 
     // Toggle num lock on.
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_NUMLOCK, 1);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_NUMLOCK, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
     ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
     ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper.getMetaState());
 
     // Toggle scroll lock on.
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
     ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
     ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper.getMetaState());
 
@@ -3555,28 +3821,28 @@
             addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
                                                        AINPUT_KEYBOARD_TYPE_ALPHABETIC);
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_HOME, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
     NotifyKeyArgs args;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 
-    process(mapper, ARBITRARY_TIME + 1, EV_KEY, KEY_HOME, 0);
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(uint32_t(0), args.policyFlags);
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_PLAY, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(uint32_t(0), args.policyFlags);
 
-    process(mapper, ARBITRARY_TIME + 1, EV_KEY, KEY_PLAY, 0);
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(uint32_t(0), args.policyFlags);
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_PLAYPAUSE, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 
-    process(mapper, ARBITRARY_TIME + 1, EV_KEY, KEY_PLAYPAUSE, 0);
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 }
@@ -3593,28 +3859,28 @@
             addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
                                                        AINPUT_KEYBOARD_TYPE_ALPHABETIC);
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_HOME, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
     NotifyKeyArgs args;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 
-    process(mapper, ARBITRARY_TIME + 1, EV_KEY, KEY_HOME, 0);
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_DOWN, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_DOWN, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(uint32_t(0), args.policyFlags);
 
-    process(mapper, ARBITRARY_TIME + 1, EV_KEY, KEY_DOWN, 0);
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_DOWN, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(uint32_t(0), args.policyFlags);
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, KEY_PLAY, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 
-    process(mapper, ARBITRARY_TIME + 1, EV_KEY, KEY_PLAY, 0);
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 }
@@ -3652,9 +3918,9 @@
                                                int32_t rotatedY) {
     NotifyMotionArgs args;
 
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_X, originalX);
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_Y, originalY);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, originalX);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, originalY);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -3735,8 +4001,8 @@
 
     // Button press.
     // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
     ASSERT_EQ(DEVICE_ID, args.deviceId);
@@ -3776,8 +4042,8 @@
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
 
     // Button release.  Should have same down time.
-    process(mapper, ARBITRARY_TIME + 1, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME + 1, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, BTN_MOUSE, 0);
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
     ASSERT_EQ(DEVICE_ID, args.deviceId);
@@ -3824,16 +4090,16 @@
     NotifyMotionArgs args;
 
     // Motion in X but not Y.
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_X, 1);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
             1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // Motion in Y but not X.
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_Y, -2);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -3847,8 +4113,8 @@
     NotifyMotionArgs args;
 
     // Button press.
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -3860,8 +4126,8 @@
             0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // Button release.
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -3880,10 +4146,10 @@
     NotifyMotionArgs args;
 
     // Combined X, Y and Button.
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_X, 1);
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_Y, -2);
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, -2);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -3897,9 +4163,9 @@
             1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // Move X, Y a bit while pressed.
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_X, 2);
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_Y, 1);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 2);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -3907,8 +4173,8 @@
             1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // Release Button.
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -3993,8 +4259,8 @@
     NotifyKeyArgs keyArgs;
 
     // press BTN_LEFT, release BTN_LEFT
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
@@ -4009,8 +4275,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_LEFT, 0);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
@@ -4033,9 +4299,9 @@
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 1);
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 1);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
@@ -4062,8 +4328,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 0);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
@@ -4078,16 +4344,16 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 0);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 0);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(0, motionArgs.buttonState);
@@ -4104,8 +4370,8 @@
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // press BTN_BACK, release BTN_BACK
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_BACK, 1);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
     ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
@@ -4124,8 +4390,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_BACK, 0);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
@@ -4145,8 +4411,8 @@
     ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
 
     // press BTN_SIDE, release BTN_SIDE
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_SIDE, 1);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
     ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
@@ -4165,8 +4431,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_SIDE, 0);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
@@ -4186,8 +4452,8 @@
     ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
 
     // press BTN_FORWARD, release BTN_FORWARD
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_FORWARD, 1);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
     ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
@@ -4206,8 +4472,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_FORWARD, 0);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
@@ -4227,8 +4493,8 @@
     ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
 
     // press BTN_EXTRA, release BTN_EXTRA
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_EXTRA, 1);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
     ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
@@ -4247,8 +4513,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_EXTRA, 0);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
     ASSERT_EQ(0, motionArgs.buttonState);
@@ -4278,9 +4544,9 @@
 
     NotifyMotionArgs args;
 
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
@@ -4306,9 +4572,9 @@
     NotifyMotionArgs args;
 
     // Move.
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
@@ -4317,8 +4583,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 100.0f, 200.0f));
 
     // Button press.
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
@@ -4331,8 +4597,8 @@
             0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // Button release.
-    process(mapper, ARBITRARY_TIME + 2, EV_KEY, BTN_MOUSE, 0);
-    process(mapper, ARBITRARY_TIME + 2, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, BTN_MOUSE, 0);
+    process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
     ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
@@ -4345,9 +4611,9 @@
             0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // Another move.
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_X, 30);
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_Y, 40);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 30);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 40);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
@@ -4366,9 +4632,9 @@
     ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
     ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
 
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
@@ -4394,9 +4660,9 @@
     mFakePointerController->setButtonState(0);
 
     NotifyMotionArgs args;
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_X, 10);
-    process(mapper, ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
@@ -4619,46 +4885,46 @@
 }
 
 void SingleTouchInputMapperTest::processDown(SingleTouchInputMapper& mapper, int32_t x, int32_t y) {
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1);
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_X, x);
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_Y, y);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, x);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, y);
 }
 
 void SingleTouchInputMapperTest::processMove(SingleTouchInputMapper& mapper, int32_t x, int32_t y) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_X, x);
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_Y, y);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, x);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, y);
 }
 
 void SingleTouchInputMapperTest::processUp(SingleTouchInputMapper& mapper) {
-    process(mapper, ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 0);
 }
 
 void SingleTouchInputMapperTest::processPressure(SingleTouchInputMapper& mapper, int32_t pressure) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_PRESSURE, pressure);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_PRESSURE, pressure);
 }
 
 void SingleTouchInputMapperTest::processToolMajor(SingleTouchInputMapper& mapper,
                                                   int32_t toolMajor) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_TOOL_WIDTH, toolMajor);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TOOL_WIDTH, toolMajor);
 }
 
 void SingleTouchInputMapperTest::processDistance(SingleTouchInputMapper& mapper, int32_t distance) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_DISTANCE, distance);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_DISTANCE, distance);
 }
 
 void SingleTouchInputMapperTest::processTilt(SingleTouchInputMapper& mapper, int32_t tiltX,
                                              int32_t tiltY) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_TILT_X, tiltX);
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_TILT_Y, tiltY);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TILT_X, tiltX);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TILT_Y, tiltY);
 }
 
 void SingleTouchInputMapperTest::processKey(SingleTouchInputMapper& mapper, int32_t code,
                                             int32_t value) {
-    process(mapper, ARBITRARY_TIME, EV_KEY, code, value);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, code, value);
 }
 
 void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper& mapper) {
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
 }
 
 TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) {
@@ -5946,64 +6212,64 @@
 
 void MultiTouchInputMapperTest::processPosition(MultiTouchInputMapper& mapper, int32_t x,
                                                 int32_t y) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, x);
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, y);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_X, x);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_Y, y);
 }
 
 void MultiTouchInputMapperTest::processTouchMajor(MultiTouchInputMapper& mapper,
                                                   int32_t touchMajor) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MAJOR, touchMajor);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_TOUCH_MAJOR, touchMajor);
 }
 
 void MultiTouchInputMapperTest::processTouchMinor(MultiTouchInputMapper& mapper,
                                                   int32_t touchMinor) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MINOR, touchMinor);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_TOUCH_MINOR, touchMinor);
 }
 
 void MultiTouchInputMapperTest::processToolMajor(MultiTouchInputMapper& mapper, int32_t toolMajor) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_MT_WIDTH_MAJOR, toolMajor);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_WIDTH_MAJOR, toolMajor);
 }
 
 void MultiTouchInputMapperTest::processToolMinor(MultiTouchInputMapper& mapper, int32_t toolMinor) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_MT_WIDTH_MINOR, toolMinor);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_WIDTH_MINOR, toolMinor);
 }
 
 void MultiTouchInputMapperTest::processOrientation(MultiTouchInputMapper& mapper,
                                                    int32_t orientation) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_MT_ORIENTATION, orientation);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_ORIENTATION, orientation);
 }
 
 void MultiTouchInputMapperTest::processPressure(MultiTouchInputMapper& mapper, int32_t pressure) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_MT_PRESSURE, pressure);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_PRESSURE, pressure);
 }
 
 void MultiTouchInputMapperTest::processDistance(MultiTouchInputMapper& mapper, int32_t distance) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_MT_DISTANCE, distance);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_DISTANCE, distance);
 }
 
 void MultiTouchInputMapperTest::processId(MultiTouchInputMapper& mapper, int32_t id) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, id);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_TRACKING_ID, id);
 }
 
 void MultiTouchInputMapperTest::processSlot(MultiTouchInputMapper& mapper, int32_t slot) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, slot);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_SLOT, slot);
 }
 
 void MultiTouchInputMapperTest::processToolType(MultiTouchInputMapper& mapper, int32_t toolType) {
-    process(mapper, ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, toolType);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_TOOL_TYPE, toolType);
 }
 
 void MultiTouchInputMapperTest::processKey(MultiTouchInputMapper& mapper, int32_t code,
                                            int32_t value) {
-    process(mapper, ARBITRARY_TIME, EV_KEY, code, value);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, code, value);
 }
 
 void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper& mapper) {
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_MT_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_MT_REPORT, 0);
 }
 
 void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper& mapper) {
-    process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
 }
 
 TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackingIds) {
@@ -7437,6 +7703,32 @@
 }
 
 /**
+ * Ensure that the readTime is set to the SYN_REPORT value when processing touch events.
+ */
+TEST_F(MultiTouchInputMapperTest, Process_SendsReadTime) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareAxes(POSITION);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    process(mapper, 10, 11 /*readTime*/, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    process(mapper, 15, 16 /*readTime*/, EV_ABS, ABS_MT_POSITION_X, 100);
+    process(mapper, 20, 21 /*readTime*/, EV_ABS, ABS_MT_POSITION_Y, 100);
+    process(mapper, 25, 26 /*readTime*/, EV_SYN, SYN_REPORT, 0);
+
+    NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(26, args.readTime);
+
+    process(mapper, 30, 31 /*readTime*/, EV_ABS, ABS_MT_POSITION_X, 110);
+    process(mapper, 30, 32 /*readTime*/, EV_ABS, ABS_MT_POSITION_Y, 220);
+    process(mapper, 30, 33 /*readTime*/, EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(33, args.readTime);
+}
+
+/**
  * When the viewport is not active (isActive=false), the touch mapper should be disabled and the
  * events should not be delivered to the listener.
  */
diff --git a/services/powermanager/benchmarks/Android.bp b/services/powermanager/benchmarks/Android.bp
index ad93a65..a489253 100644
--- a/services/powermanager/benchmarks/Android.bp
+++ b/services/powermanager/benchmarks/Android.bp
@@ -12,6 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
 cc_benchmark {
     name: "libpowermanager_benchmarks",
     srcs: [
diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp
index 0f5037e..69e4041 100644
--- a/services/powermanager/tests/Android.bp
+++ b/services/powermanager/tests/Android.bp
@@ -12,6 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
 cc_test {
     name: "libpowermanager_test",
     test_suites: ["device-tests"],
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index a2e7f23..5225dd7 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -38,12 +38,12 @@
 
 SensorService::SensorEventConnection::SensorEventConnection(
         const sp<SensorService>& service, uid_t uid, String8 packageName, bool isDataInjectionMode,
-        const String16& opPackageName)
+        const String16& opPackageName, const String16& attributionTag)
     : mService(service), mUid(uid), mWakeLockRefCount(0), mHasLooperCallbacks(false),
       mDead(false), mDataInjectionMode(isDataInjectionMode), mEventCache(nullptr),
       mCacheSize(0), mMaxCacheSize(0), mTimeOfLastEventDrop(0), mEventsDropped(0),
-      mPackageName(packageName), mOpPackageName(opPackageName), mTargetSdk(kTargetSdkUnknown),
-      mDestroyed(false) {
+      mPackageName(packageName), mOpPackageName(opPackageName), mAttributionTag(attributionTag),
+      mTargetSdk(kTargetSdkUnknown), mDestroyed(false) {
     mIsRateCappedBasedOnPermission = mService->isRateCappedBasedOnPermission(mOpPackageName);
     mUserId = multiuser_get_user_id(mUid);
     mChannel = new BitTube(mService->mSocketBufferSize);
@@ -493,7 +493,8 @@
             noteMsg.append(String16(mService->getSensorStringType(sensorHandle)));
             noteMsg.append(String16(")"));
             int32_t appOpMode = mService->sAppOpsManager.noteOp(iter->second, mUid,
-                                                                mOpPackageName, {}, noteMsg);
+                                                                mOpPackageName, mAttributionTag,
+                                                                noteMsg);
             success = (appOpMode == AppOpsManager::MODE_ALLOWED);
         }
     }
diff --git a/services/sensorservice/SensorEventConnection.h b/services/sensorservice/SensorEventConnection.h
index 7bd9d47..4e3f120 100644
--- a/services/sensorservice/SensorEventConnection.h
+++ b/services/sensorservice/SensorEventConnection.h
@@ -50,7 +50,8 @@
 
 public:
     SensorEventConnection(const sp<SensorService>& service, uid_t uid, String8 packageName,
-                          bool isDataInjectionMode, const String16& opPackageName);
+                          bool isDataInjectionMode, const String16& opPackageName,
+                          const String16& attributionTag);
 
     status_t sendEvents(sensors_event_t const* buffer, size_t count, sensors_event_t* scratch,
                         wp<const SensorEventConnection> const * mapFlushEventsToConnections = nullptr);
@@ -190,6 +191,7 @@
     int mEventsDropped;
     String8 mPackageName;
     const String16 mOpPackageName;
+    const String16 mAttributionTag;
     int mTargetSdk;
 #if DEBUG_CONNECTIONS
     int mEventsReceived, mEventsSent, mEventsSentFromCache;
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 89d1c42..942b7ae 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -1285,7 +1285,7 @@
 }
 
 sp<ISensorEventConnection> SensorService::createSensorEventConnection(const String8& packageName,
-        int requestedMode, const String16& opPackageName) {
+        int requestedMode, const String16& opPackageName, const String16& attributionTag) {
     // Only 2 modes supported for a SensorEventConnection ... NORMAL and DATA_INJECTION.
     if (requestedMode != NORMAL && requestedMode != DATA_INJECTION) {
         return nullptr;
@@ -1307,7 +1307,7 @@
     String16 connOpPackageName =
             (opPackageName == String16("")) ? String16(connPackageName) : opPackageName;
     sp<SensorEventConnection> result(new SensorEventConnection(this, uid, connPackageName,
-            requestedMode == DATA_INJECTION, connOpPackageName));
+            requestedMode == DATA_INJECTION, connOpPackageName, attributionTag));
     if (requestedMode == DATA_INJECTION) {
         mConnectionHolder.addEventConnectionIfNotPresent(result);
         // Add the associated file descriptor to the Looper for polling whenever there is data to
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index 5b198d8..9c5060a 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -303,7 +303,7 @@
     virtual Vector<Sensor> getDynamicSensorList(const String16& opPackageName);
     virtual sp<ISensorEventConnection> createSensorEventConnection(
             const String8& packageName,
-            int requestedMode, const String16& opPackageName);
+            int requestedMode, const String16& opPackageName, const String16& attributionTag);
     virtual int isDataInjectionEnabled();
     virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName,
             uint32_t size, int32_t type, int32_t format, const native_handle *resource);
diff --git a/services/stats/Android.bp b/services/stats/Android.bp
index 58e5993..a472c5f 100644
--- a/services/stats/Android.bp
+++ b/services/stats/Android.bp
@@ -10,11 +10,14 @@
 cc_library_shared {
     name: "libstatshidl",
     srcs: [
+        "StatsAidl.cpp",
         "StatsHal.cpp",
     ],
     cflags: ["-Wall", "-Werror"],
     shared_libs: [
         "android.frameworks.stats@1.0",
+        "android.frameworks.stats-V1-ndk_platform",
+        "libbinder_ndk",
         "libhidlbase",
         "liblog",
         "libstatslog",
@@ -22,7 +25,11 @@
         "libutils",
     ],
     export_include_dirs: [
-    	"include/",
+        "include/",
+    ],
+    export_shared_lib_headers: [
+        "android.frameworks.stats@1.0",
+        "android.frameworks.stats-V1-ndk_platform",
     ],
     local_include_dirs: [
         "include/stats",
diff --git a/services/stats/StatsAidl.cpp b/services/stats/StatsAidl.cpp
new file mode 100644
index 0000000..a3b68f1
--- /dev/null
+++ b/services/stats/StatsAidl.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG false // STOPSHIP if true
+#define LOG_TAG "StatsAidl"
+
+#include <log/log.h>
+#include <statslog.h>
+
+#include "StatsAidl.h"
+
+namespace aidl {
+namespace android {
+namespace frameworks {
+namespace stats {
+
+StatsHal::StatsHal() {}
+
+ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) {
+    std::string reverseDomainName = (std::string) vendorAtom.reverseDomainName;
+    if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) {
+        ALOGE("Atom ID %ld is not a valid vendor atom ID", (long) vendorAtom.atomId);
+        return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+            -1, "Not a valid vendor atom ID");
+    }
+    if (reverseDomainName.length() > 50) {
+        ALOGE("Vendor atom reverse domain name %s is too long.", reverseDomainName.c_str());
+        return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+            -1, "Vendor atom reverse domain name is too long");
+    }
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, vendorAtom.atomId);
+    AStatsEvent_writeString(event, vendorAtom.reverseDomainName.c_str());
+    for (const auto& atomValue : vendorAtom.values) {
+        switch (atomValue.getTag()) {
+            case VendorAtomValue::intValue:
+                AStatsEvent_writeInt32(event,
+                    atomValue.get<VendorAtomValue::intValue>());
+                break;
+            case VendorAtomValue::longValue:
+                AStatsEvent_writeInt64(event,
+                    atomValue.get<VendorAtomValue::longValue>());
+                break;
+            case VendorAtomValue::floatValue:
+                AStatsEvent_writeFloat(event,
+                    atomValue.get<VendorAtomValue::floatValue>());
+                break;
+            case VendorAtomValue::stringValue:
+                AStatsEvent_writeString(event,
+                    atomValue.get<VendorAtomValue::stringValue>().c_str());
+                break;
+        }
+    }
+    AStatsEvent_build(event);
+    const int ret = AStatsEvent_write(event);
+    AStatsEvent_release(event);
+
+    return ret <= 0 ?
+            ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(ret, "report atom failed") :
+            ndk::ScopedAStatus::ok();
+}
+
+}  // namespace stats
+}  // namespace frameworks
+}  // namespace android
+}  // namespace aidl
diff --git a/services/stats/android.frameworks.stats@1.0-service.xml b/services/stats/android.frameworks.stats@1.0-service.xml
index bb02f66..5fd361c 100644
--- a/services/stats/android.frameworks.stats@1.0-service.xml
+++ b/services/stats/android.frameworks.stats@1.0-service.xml
@@ -8,4 +8,10 @@
             <instance>default</instance>
         </interface>
     </hal>
+
+    <hal format="aidl">
+        <name>android.frameworks.stats</name>
+        <version>1</version>
+        <fqname>IStats/default</fqname>
+    </hal>
 </manifest>
diff --git a/services/stats/include/stats/StatsAidl.h b/services/stats/include/stats/StatsAidl.h
new file mode 100644
index 0000000..219e71e
--- /dev/null
+++ b/services/stats/include/stats/StatsAidl.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/android/frameworks/stats/BnStats.h>
+
+namespace aidl {
+namespace android {
+namespace frameworks {
+namespace stats {
+
+class StatsHal : public BnStats {
+public:
+    StatsHal();
+
+    /**
+     * Binder call to get vendor atom.
+     */
+    virtual ndk::ScopedAStatus reportVendorAtom(
+        const VendorAtom& in_vendorAtom) override;
+};
+
+}  // namespace stats
+}  // namespace frameworks
+}  // namespace android
+}  // namespace aidl
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 625f315..470059a 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -154,6 +154,7 @@
         "DisplayRenderArea.cpp",
         "Effects/Daltonizer.cpp",
         "EventLog/EventLog.cpp",
+        "FpsReporter.cpp",
         "FrameTracer/FrameTracer.cpp",
         "FrameTracker.cpp",
         "Layer.cpp",
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 097f7de..a96dffd 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -414,12 +414,6 @@
         return true;
     }
 
-    // If the next planned present time is not current, return and try again later
-    if (frameIsEarly(expectedPresentTime)) {
-        ATRACE_NAME("frameIsEarly()");
-        return false;
-    }
-
     // If this layer doesn't have a frame is shouldn't be presented
     if (!hasFrameUpdate()) {
         return false;
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index f30e1eb..296085a 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -370,9 +370,7 @@
 
     addFrameEvent(acquireFence, postTime, isAutoTimestamp ? 0 : desiredPresentTime);
 
-    if (info.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) {
-        setFrameTimelineVsyncForBufferTransaction(info, postTime);
-    }
+    setFrameTimelineVsyncForBufferTransaction(info, postTime);
 
     if (dequeueTime && *dequeueTime != 0) {
         const uint64_t bufferId = buffer->getId();
@@ -871,18 +869,13 @@
     return layerSize.width() != bufferWidth || layerSize.height() != bufferHeight;
 }
 
-void BufferStateLayer::incrementPendingBufferCount() {
-    mPendingBufferTransactions++;
-    tracePendingBufferCount();
-}
-
 void BufferStateLayer::decrementPendingBufferCount() {
-    mPendingBufferTransactions--;
-    tracePendingBufferCount();
+    int32_t pendingBuffers = --mPendingBufferTransactions;
+    tracePendingBufferCount(pendingBuffers);
 }
 
-void BufferStateLayer::tracePendingBufferCount() {
-    ATRACE_INT(mBlastTransactionName.c_str(), mPendingBufferTransactions);
+void BufferStateLayer::tracePendingBufferCount(int32_t pendingBuffers) {
+    ATRACE_INT(mBlastTransactionName.c_str(), pendingBuffers);
 }
 
 uint32_t BufferStateLayer::doTransaction(uint32_t flags) {
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index 175a40b..6b422ea 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -113,9 +113,10 @@
     uint32_t getEffectiveScalingMode() const override;
 
     // See mPendingBufferTransactions
-    void incrementPendingBufferCount() override;
     void decrementPendingBufferCount();
     uint32_t doTransaction(uint32_t flags) override;
+    std::atomic<int32_t>* getPendingBufferCounter() override { return &mPendingBufferTransactions; }
+    std::string getPendingBufferCounterName() override { return mBlastTransactionName; }
 
 protected:
     void gatherBufferInfo() override;
@@ -127,7 +128,7 @@
     friend class TransactionFrameTracerTest;
     friend class TransactionSurfaceFrameTest;
 
-    inline void tracePendingBufferCount();
+    inline void tracePendingBufferCount(int32_t pendingBuffers);
 
     bool updateFrameEventHistory(const sp<Fence>& acquireFence, nsecs_t postedTime,
                                  nsecs_t requestedPresentTime);
@@ -184,7 +185,7 @@
     //     - If the integer increases, a buffer arrived at the server.
     //     - If the integer decreases in latchBuffer, that buffer was latched
     //     - If the integer decreases in setBuffer or doTransaction, a buffer was dropped
-    uint64_t mPendingBufferTransactions{0};
+    std::atomic<int32_t> mPendingBufferTransactions{0};
 
     // TODO(marissaw): support sticky transform for LEGACY camera mode
 
diff --git a/services/surfaceflinger/ClientCache.cpp b/services/surfaceflinger/ClientCache.cpp
index a5be01c..2ae49fa 100644
--- a/services/surfaceflinger/ClientCache.cpp
+++ b/services/surfaceflinger/ClientCache.cpp
@@ -25,6 +25,8 @@
 
 namespace android {
 
+using base::StringAppendF;
+
 ANDROID_SINGLETON_STATIC_INSTANCE(ClientCache);
 
 ClientCache::ClientCache() : mDeathRecipient(new CacheDeathRecipient) {}
@@ -203,4 +205,18 @@
     ClientCache::getInstance().removeProcess(who);
 }
 
+void ClientCache::dump(std::string& result) {
+    std::lock_guard lock(mMutex);
+    for (auto i : mBuffers) {
+        const sp<IBinder>& cacheOwner = i.second.first;
+        StringAppendF(&result," Cache owner: %p\n", cacheOwner.get());
+        auto &buffers = i.second.second;
+        for (auto& [id, clientCacheBuffer] : buffers) {
+            StringAppendF(&result, "\t ID: %d, Width/Height: %d,%d\n", (int)id,
+                          (int)clientCacheBuffer.buffer->getWidth(),
+                          (int)clientCacheBuffer.buffer->getHeight());
+        }
+    }
+}
+
 }; // namespace android
diff --git a/services/surfaceflinger/ClientCache.h b/services/surfaceflinger/ClientCache.h
index d7af7c0..0d597c8 100644
--- a/services/surfaceflinger/ClientCache.h
+++ b/services/surfaceflinger/ClientCache.h
@@ -53,6 +53,8 @@
     void unregisterErasedRecipient(const client_cache_t& cacheId,
                                    const wp<ErasedRecipient>& recipient);
 
+    void dump(std::string& result);
+
 private:
     std::mutex mMutex;
 
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 50bc5ed..f9e5b9a 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -29,6 +29,7 @@
         "liblog",
         "libnativewindow",
         "libprotobuf-cpp-lite",
+        "libSurfaceFlingerProp",
         "libtimestats",
         "libui",
         "libutils",
@@ -51,6 +52,11 @@
     name: "libcompositionengine",
     defaults: ["libcompositionengine_defaults"],
     srcs: [
+        "src/planner/CachedSet.cpp",
+        "src/planner/Flattener.cpp",
+        "src/planner/LayerState.cpp",
+        "src/planner/Planner.cpp",
+        "src/planner/Predictor.cpp",
         "src/ClientCompositionRequestCache.cpp",
         "src/CompositionEngine.cpp",
         "src/Display.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h
index 95ba9f0..633668e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h
@@ -21,9 +21,9 @@
 #include <string>
 
 #include <ui/DisplayId.h>
-#include <ui/DisplayInfo.h>
 #include <ui/PixelFormat.h>
 #include <ui/Size.h>
+#include <ui/StaticDisplayInfo.h>
 
 #include "DisplayHardware/DisplayIdentification.h"
 #include "DisplayHardware/PowerAdvisor.h"
@@ -39,7 +39,7 @@
 struct DisplayCreationArgs {
     struct Physical {
         DisplayId id;
-        DisplayConnectionType type;
+        ui::DisplayConnectionType type;
     };
 
     // Required for physical displays. Gives the HWC display id for the existing
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index 018a687..1fd07b0 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -135,6 +135,9 @@
 
     // Gets some kind of identifier for the layer for debug purposes.
     virtual const char* getDebugName() const = 0;
+
+    // Gets the sequence number: a serial number that uniquely identifies a Layer
+    virtual int32_t getSequence() const = 0;
 };
 
 // TODO(b/121291683): Specialize std::hash<> for sp<T> so these and others can
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index 3be1cc4..4976213 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -31,6 +31,7 @@
 #include <ui/Region.h>
 #include <ui/Transform.h>
 #include <utils/StrongPointer.h>
+#include <utils/Vector.h>
 
 #include "DisplayHardware/DisplayIdentification.h"
 
@@ -183,6 +184,9 @@
     // Outputs a string with a state dump
     virtual void dump(std::string&) const = 0;
 
+    // Outputs planner information
+    virtual void dumpPlannerInfo(const Vector<String16>& args, std::string&) const = 0;
+
     // Gets the debug name for the output
     virtual const std::string& getName() const = 0;
 
@@ -264,7 +268,9 @@
     virtual void ensureOutputLayerIfVisible(sp<LayerFE>&, CoverageState&) = 0;
     virtual void setReleasedLayers(const CompositionRefreshArgs&) = 0;
 
-    virtual void updateAndWriteCompositionState(const CompositionRefreshArgs&) = 0;
+    virtual void updateCompositionState(const CompositionRefreshArgs&) = 0;
+    virtual void planComposition() = 0;
+    virtual void writeCompositionState(const CompositionRefreshArgs&) = 0;
     virtual void setColorTransform(const CompositionRefreshArgs&) = 0;
     virtual void updateColorProfile(const CompositionRefreshArgs&) = 0;
     virtual void beginFrame() = 0;
@@ -274,6 +280,7 @@
     virtual std::optional<base::unique_fd> composeSurfaces(
             const Region&, const compositionengine::CompositionRefreshArgs& refreshArgs) = 0;
     virtual void postFramebuffer() = 0;
+    virtual void renderCachedSets() = 0;
     virtual void chooseCompositionStrategy() = 0;
     virtual bool getSkipColorTransform() const = 0;
     virtual FrameFences presentAndGetFrameFences() = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index fb19216..3a84327 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -30,6 +30,8 @@
 #include "DisplayHardware/ComposerHal.h"
 #include "DisplayHardware/DisplayIdentification.h"
 
+#include "LayerFE.h"
+
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
@@ -43,7 +45,6 @@
 
 class CompositionEngine;
 class Output;
-class LayerFE;
 
 namespace impl {
 struct OutputLayerCompositionState;
@@ -88,8 +89,9 @@
 
     // Writes the geometry state to the HWC, or does nothing if this layer does
     // not use the HWC. If includeGeometry is false, the geometry state can be
-    // skipped.
-    virtual void writeStateToHWC(bool includeGeometry) = 0;
+    // skipped. If skipLayer is true, then the alpha of the layer is forced to
+    // 0 so that HWC will ignore it.
+    virtual void writeStateToHWC(bool includeGeometry, bool skipLayer) = 0;
 
     // Updates the cursor position with the HWC
     virtual void writeCursorPositionToHWC() const = 0;
@@ -115,6 +117,10 @@
     // Returns true if the composition settings scale pixels
     virtual bool needsFiltering() const = 0;
 
+    // Returns a composition list to be used by RenderEngine if the layer has been overridden
+    // during the composition process
+    virtual std::vector<LayerFE::LayerSettings> getOverrideCompositionList() const = 0;
+
     // Debugging
     virtual void dump(std::string& result) const = 0;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DumpHelpers.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DumpHelpers.h
index 782c8d7..6b9597b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DumpHelpers.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DumpHelpers.h
@@ -24,6 +24,7 @@
 #include <ui/FloatRect.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
+#include <ui/StretchEffect.h>
 #include <ui/Transform.h>
 
 namespace android::compositionengine::impl {
@@ -58,5 +59,6 @@
 void dumpVal(std::string& out, const char* name, const ui::Size&);
 
 void dumpVal(std::string& out, const char* name, const mat4& tr);
+void dumpVal(std::string& out, const char* name, const StretchEffect&);
 
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 651230c..eeb20fc 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -28,10 +28,15 @@
 
 namespace android::compositionengine::impl {
 
+namespace planner {
+class Planner;
+} // namespace planner
+
 // The implementation class contains the common implementation, but does not
 // actually contain the final output state.
 class Output : public virtual compositionengine::Output {
 public:
+    Output();
     ~Output() override;
 
     // compositionengine::Output overrides
@@ -48,6 +53,7 @@
     void setColorProfile(const ColorProfile&) override;
 
     void dump(std::string&) const override;
+    void dumpPlannerInfo(const Vector<String16>& args, std::string&) const override;
 
     const std::string& getName() const override;
     void setName(const std::string&) override;
@@ -77,7 +83,9 @@
     void setReleasedLayers(const compositionengine::CompositionRefreshArgs&) override;
 
     void updateLayerStateFromFE(const CompositionRefreshArgs&) const override;
-    void updateAndWriteCompositionState(const compositionengine::CompositionRefreshArgs&) override;
+    void updateCompositionState(const compositionengine::CompositionRefreshArgs&) override;
+    void planComposition() override;
+    void writeCompositionState(const compositionengine::CompositionRefreshArgs&) override;
     void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override;
     void beginFrame() override;
     void prepareFrame() override;
@@ -86,12 +94,14 @@
     std::optional<base::unique_fd> composeSurfaces(
             const Region&, const compositionengine::CompositionRefreshArgs& refreshArgs) override;
     void postFramebuffer() override;
+    void renderCachedSets() override;
     void cacheClientCompositionRequests(uint32_t) override;
 
     // Testing
     const ReleasedLayers& getReleasedLayersForTest() const;
     void setDisplayColorProfileForTest(std::unique_ptr<compositionengine::DisplayColorProfile>);
     void setRenderSurfaceForTest(std::unique_ptr<compositionengine::RenderSurface>);
+    bool plannerEnabled() const { return mPlanner != nullptr; }
 
 protected:
     std::unique_ptr<compositionengine::OutputLayer> createOutputLayer(const sp<LayerFE>&) const;
@@ -130,6 +140,7 @@
     ReleasedLayers mReleasedLayers;
     OutputLayer* mLayerRequestingBackgroundBlur = nullptr;
     std::unique_ptr<ClientCompositionRequestCache> mClientCompositionRequestCache;
+    std::unique_ptr<planner::Planner> mPlanner;
 };
 
 // This template factory function standardizes the implementation details of the
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index 8cb5ae8..f113c34 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -19,6 +19,7 @@
 #include <memory>
 #include <string>
 
+#include <compositionengine/LayerFE.h>
 #include <compositionengine/OutputLayer.h>
 #include <ui/FloatRect.h>
 #include <ui/Rect.h>
@@ -41,7 +42,7 @@
 
     void updateCompositionState(bool includeGeometry, bool forceClientComposition,
                                 ui::Transform::RotationFlags) override;
-    void writeStateToHWC(bool) override;
+    void writeStateToHWC(bool includeGeometry, bool skipLayer) override;
     void writeCursorPositionToHWC() const override;
 
     HWC2::Layer* getHwcLayer() const override;
@@ -51,6 +52,7 @@
     void prepareForDeviceLayerRequests() override;
     void applyDeviceLayerRequest(Hwc2::IComposerClient::LayerRequest request) override;
     bool needsFiltering() const override;
+    std::vector<LayerFE::LayerSettings> getOverrideCompositionList() const override;
 
     void dump(std::string&) const override;
 
@@ -66,7 +68,8 @@
 private:
     Rect calculateInitialCrop() const;
     void writeOutputDependentGeometryStateToHWC(HWC2::Layer*, Hwc2::IComposerClient::Composition);
-    void writeOutputIndependentGeometryStateToHWC(HWC2::Layer*, const LayerFECompositionState&);
+    void writeOutputIndependentGeometryStateToHWC(HWC2::Layer*, const LayerFECompositionState&,
+                                                  bool skipLayer);
     void writeOutputDependentPerFrameStateToHWC(HWC2::Layer*);
     void writeOutputIndependentPerFrameStateToHWC(HWC2::Layer*, const LayerFECompositionState&);
     void writeSolidColorStateToHWC(HWC2::Layer*, const LayerFECompositionState&);
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
index 9a118d3..5f834be 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
@@ -84,6 +84,13 @@
     // The Z order index of this layer on this output
     uint32_t z{0};
 
+    // Overrides the buffer, acquire fence, and display frame stored in LayerFECompositionState
+    struct {
+        sp<GraphicBuffer> buffer = nullptr;
+        sp<Fence> acquireFence = nullptr;
+        Rect displayFrame = {};
+    } overrideInfo;
+
     /*
      * HWC state
      */
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
new file mode 100644
index 0000000..00424b2
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <compositionengine/impl/planner/LayerState.h>
+
+#include <chrono>
+
+namespace android {
+
+namespace renderengine {
+class RenderEngine;
+} // namespace renderengine
+
+namespace compositionengine::impl::planner {
+
+std::string durationString(std::chrono::milliseconds duration);
+
+class LayerState;
+
+class CachedSet {
+public:
+    class Layer {
+    public:
+        Layer(const LayerState*, std::chrono::steady_clock::time_point lastUpdate);
+
+        const LayerState* getState() const { return mState; }
+        const std::string& getName() const { return mState->getName(); }
+        Rect getDisplayFrame() const { return mState->getDisplayFrame(); }
+        const sp<GraphicBuffer>& getBuffer() const { return mState->getBuffer(); }
+        int64_t getFramesSinceBufferUpdate() const { return mState->getFramesSinceBufferUpdate(); }
+        NonBufferHash getHash() const { return mHash; }
+        std::chrono::steady_clock::time_point getLastUpdate() const { return mLastUpdate; }
+
+    private:
+        const LayerState* mState;
+        NonBufferHash mHash;
+        std::chrono::steady_clock::time_point mLastUpdate;
+    };
+
+    CachedSet(const LayerState*, std::chrono::steady_clock::time_point lastUpdate);
+    CachedSet(Layer layer);
+
+    void addLayer(const LayerState*, std::chrono::steady_clock::time_point lastUpdate);
+
+    std::chrono::steady_clock::time_point getLastUpdate() const { return mLastUpdate; }
+    NonBufferHash getFingerprint() const { return mFingerprint; }
+    size_t getLayerCount() const { return mLayers.size(); }
+    const Layer& getFirstLayer() const { return mLayers[0]; }
+    const Rect& getBounds() const { return mBounds; }
+    size_t getAge() const { return mAge; }
+    const sp<GraphicBuffer>& getBuffer() const { return mBuffer; }
+    const sp<Fence>& getDrawFence() const { return mDrawFence; }
+
+    NonBufferHash getNonBufferHash() const;
+
+    size_t getComponentDisplayCost() const;
+    size_t getCreationCost() const;
+    size_t getDisplayCost() const;
+
+    bool hasBufferUpdate(std::vector<const LayerState*>::const_iterator layers) const;
+    bool hasReadyBuffer() const;
+
+    // Decomposes this CachedSet into a vector of its layers as individual CachedSets
+    std::vector<CachedSet> decompose() const;
+
+    void updateAge(std::chrono::steady_clock::time_point now);
+
+    void setLastUpdate(std::chrono::steady_clock::time_point now) { mLastUpdate = now; }
+    void append(const CachedSet& other) {
+        mBuffer = nullptr;
+        mDrawFence = nullptr;
+
+        mLayers.insert(mLayers.end(), other.mLayers.cbegin(), other.mLayers.cend());
+        Region boundingRegion;
+        boundingRegion.orSelf(mBounds);
+        boundingRegion.orSelf(other.mBounds);
+        mBounds = boundingRegion.getBounds();
+    }
+    void incrementAge() { ++mAge; }
+
+    void render(renderengine::RenderEngine&);
+
+    void dump(std::string& result) const;
+
+private:
+    CachedSet() = default;
+
+    NonBufferHash mFingerprint = 0;
+    std::chrono::steady_clock::time_point mLastUpdate = std::chrono::steady_clock::now();
+    std::vector<Layer> mLayers;
+    Rect mBounds = Rect::EMPTY_RECT;
+    size_t mAge = 0;
+    sp<GraphicBuffer> mBuffer;
+    sp<Fence> mDrawFence;
+
+    static const bool sDebugHighlighLayers;
+};
+
+} // namespace compositionengine::impl::planner
+} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
new file mode 100644
index 0000000..6c86408
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <compositionengine/impl/planner/CachedSet.h>
+#include <compositionengine/impl/planner/LayerState.h>
+
+#include <vector>
+
+namespace android {
+
+namespace renderengine {
+class RenderEngine;
+} // namespace renderengine
+
+namespace compositionengine::impl::planner {
+using namespace std::chrono_literals;
+
+class LayerState;
+class Predictor;
+
+class Flattener {
+public:
+    Flattener(Predictor& predictor) : mPredictor(predictor) {}
+
+    void setDisplaySize(ui::Size size) { mDisplaySize = size; }
+
+    NonBufferHash flattenLayers(const std::vector<const LayerState*>& layers, NonBufferHash);
+
+    void renderCachedSets(renderengine::RenderEngine&);
+
+    void reset();
+
+    void dump(std::string& result) const;
+
+private:
+    size_t calculateDisplayCost(const std::vector<const LayerState*>& layers) const;
+
+    void resetActivities(NonBufferHash, std::chrono::steady_clock::time_point now);
+
+    void updateLayersHash();
+
+    bool mergeWithCachedSets(const std::vector<const LayerState*>& layers,
+                             std::chrono::steady_clock::time_point now);
+
+    void buildCachedSets(std::chrono::steady_clock::time_point now);
+
+    Predictor& mPredictor;
+
+    ui::Size mDisplaySize;
+
+    NonBufferHash mCurrentGeometry;
+    std::chrono::steady_clock::time_point mLastGeometryUpdate;
+
+    std::vector<CachedSet> mLayers;
+    NonBufferHash mLayersHash = 0;
+    std::optional<CachedSet> mNewCachedSet;
+
+    // Statistics
+    size_t mUnflattenedDisplayCost = 0;
+    size_t mFlattenedDisplayCost = 0;
+    std::unordered_map<size_t, size_t> mInitialLayerCounts;
+    std::unordered_map<size_t, size_t> mFinalLayerCounts;
+    size_t mCachedSetCreationCount = 0;
+    size_t mCachedSetCreationCost = 0;
+    std::unordered_map<size_t, size_t> mInvalidatedCachedSetAges;
+
+    static constexpr auto kActiveLayerTimeout = std::chrono::nanoseconds(150ms);
+};
+
+} // namespace compositionengine::impl::planner
+} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
new file mode 100644
index 0000000..d19ac62
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -0,0 +1,361 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/strings.h>
+#include <compositionengine/LayerFE.h>
+#include <compositionengine/LayerFECompositionState.h>
+#include <compositionengine/OutputLayer.h>
+#include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <input/Flags.h>
+
+#include <string>
+
+#include "DisplayHardware/Hal.h"
+
+namespace std {
+template <typename T>
+struct hash<android::sp<T>> {
+    size_t operator()(const android::sp<T>& p) { return std::hash<void*>()(p.get()); }
+};
+} // namespace std
+
+namespace android::compositionengine::impl::planner {
+
+using LayerId = int32_t;
+
+// clang-format off
+enum class LayerStateField : uint32_t {
+    Id              = 1u << 0,
+    Name            = 1u << 1,
+    DisplayFrame    = 1u << 2,
+    SourceCrop      = 1u << 3,
+    ZOrder          = 1u << 4,
+    BufferTransform = 1u << 5,
+    BlendMode       = 1u << 6,
+    Alpha           = 1u << 7,
+    VisibleRegion   = 1u << 8,
+    Dataspace       = 1u << 9,
+    ColorTransform  = 1u << 10,
+    CompositionType = 1u << 11,
+    SidebandStream  = 1u << 12,
+    Buffer          = 1u << 13,
+    SolidColor      = 1u << 14,
+};
+// clang-format on
+
+std::string to_string(LayerStateField field);
+
+// An abstract interface allows us to iterate over all of the OutputLayerState fields
+// without having to worry about their templated types.
+// See `LayerState::getNonUniqueFields` below.
+class StateInterface {
+public:
+    virtual ~StateInterface() = default;
+
+    virtual Flags<LayerStateField> update(const compositionengine::OutputLayer* layer) = 0;
+
+    virtual size_t getHash(Flags<LayerStateField> skipFields) const = 0;
+
+    virtual LayerStateField getField() const = 0;
+
+    virtual Flags<LayerStateField> getFieldIfDifferent(const StateInterface* other) const = 0;
+
+    virtual bool equals(const StateInterface* other) const = 0;
+
+    virtual std::vector<std::string> toStrings() const = 0;
+};
+
+template <typename T, LayerStateField FIELD>
+class OutputLayerState : public StateInterface {
+public:
+    using ReadFromLayerState = std::function<T(const compositionengine::OutputLayer* layer)>;
+    using ToStrings = std::function<std::vector<std::string>(const T&)>;
+    using Equals = std::function<bool(const T&, const T&)>;
+
+    static ToStrings getDefaultToStrings() {
+        return [](const T& value) {
+            using std::to_string;
+            return std::vector<std::string>{to_string(value)};
+        };
+    }
+
+    static ToStrings getHalToStrings() {
+        return [](const T& value) { return std::vector<std::string>{toString(value)}; };
+    }
+
+    static Equals getDefaultEquals() {
+        return [](const T& lhs, const T& rhs) { return lhs == rhs; };
+    }
+
+    OutputLayerState(ReadFromLayerState reader,
+                     ToStrings toStrings = OutputLayerState::getDefaultToStrings(),
+                     Equals equals = OutputLayerState::getDefaultEquals())
+          : mReader(reader), mToStrings(toStrings), mEquals(equals) {}
+
+    ~OutputLayerState() override = default;
+
+    // Returns this member's field flag if it was changed
+    Flags<LayerStateField> update(const compositionengine::OutputLayer* layer) override {
+        T newValue = mReader(layer);
+        if (!mEquals(mValue, newValue)) {
+            mValue = newValue;
+            mHash = {};
+            return FIELD;
+        }
+        return {};
+    }
+
+    LayerStateField getField() const override { return FIELD; }
+    const T& get() const { return mValue; }
+
+    size_t getHash(Flags<LayerStateField> skipFields) const override {
+        if (skipFields.test(FIELD)) {
+            return 0;
+        }
+        if (!mHash) {
+            mHash = std::hash<T>{}(mValue);
+        }
+        return *mHash;
+    }
+
+    Flags<LayerStateField> getFieldIfDifferent(const StateInterface* other) const override {
+        if (other->getField() != FIELD) {
+            return {};
+        }
+
+        // The early return ensures that this downcast is sound
+        const OutputLayerState* otherState = static_cast<const OutputLayerState*>(other);
+        return *this != *otherState ? FIELD : Flags<LayerStateField>{};
+    }
+
+    bool equals(const StateInterface* other) const override {
+        if (other->getField() != FIELD) {
+            return false;
+        }
+
+        // The early return ensures that this downcast is sound
+        const OutputLayerState* otherState = static_cast<const OutputLayerState*>(other);
+        return *this == *otherState;
+    }
+
+    std::vector<std::string> toStrings() const override { return mToStrings(mValue); }
+
+    bool operator==(const OutputLayerState& other) const { return mEquals(mValue, other.mValue); }
+    bool operator!=(const OutputLayerState& other) const { return !(*this == other); }
+
+private:
+    const ReadFromLayerState mReader;
+    const ToStrings mToStrings;
+    const Equals mEquals;
+    T mValue = {};
+    mutable std::optional<size_t> mHash = {};
+};
+
+class LayerState {
+public:
+    LayerState(compositionengine::OutputLayer* layer);
+
+    // Returns which fields were updated
+    Flags<LayerStateField> update(compositionengine::OutputLayer*);
+
+    size_t getHash(Flags<LayerStateField> skipFields) const;
+
+    Flags<LayerStateField> getDifferingFields(const LayerState& other,
+                                              Flags<LayerStateField> skipFields) const;
+
+    compositionengine::OutputLayer* getOutputLayer() const { return mOutputLayer; }
+    int32_t getId() const { return mId.get(); }
+    const std::string& getName() const { return mName.get(); }
+    Rect getDisplayFrame() const { return mDisplayFrame.get(); }
+    hardware::graphics::composer::hal::Composition getCompositionType() const {
+        return mCompositionType.get();
+    }
+    const sp<GraphicBuffer>& getBuffer() const { return mBuffer.get(); }
+
+    void incrementFramesSinceBufferUpdate() { ++mFramesSinceBufferUpdate; }
+    void resetFramesSinceBufferUpdate() { mFramesSinceBufferUpdate = 0; }
+    int64_t getFramesSinceBufferUpdate() const { return mFramesSinceBufferUpdate; }
+
+    void dump(std::string& result) const;
+    std::optional<std::string> compare(const LayerState& other) const;
+
+    // This makes LayerState's private members accessible to the operator
+    friend bool operator==(const LayerState& lhs, const LayerState& rhs);
+    friend bool operator!=(const LayerState& lhs, const LayerState& rhs) { return !(lhs == rhs); }
+
+private:
+    compositionengine::OutputLayer* mOutputLayer = nullptr;
+
+    OutputLayerState<LayerId, LayerStateField::Id> mId{
+            [](const compositionengine::OutputLayer* layer) {
+                return layer->getLayerFE().getSequence();
+            }};
+
+    OutputLayerState<std::string, LayerStateField::Name>
+            mName{[](auto layer) { return layer->getLayerFE().getDebugName(); },
+                  [](const std::string& name) { return std::vector<std::string>{name}; }};
+
+    // Output-dependent geometry state
+
+    OutputLayerState<Rect, LayerStateField::DisplayFrame>
+            mDisplayFrame{[](auto layer) { return layer->getState().displayFrame; },
+                          [](const Rect& rect) {
+                              return std::vector<std::string>{
+                                      base::StringPrintf("[%d, %d, %d, %d]", rect.left, rect.top,
+                                                         rect.right, rect.bottom)};
+                          }};
+
+    OutputLayerState<FloatRect, LayerStateField::SourceCrop>
+            mSourceCrop{[](auto layer) { return layer->getState().sourceCrop; },
+                        [](const FloatRect& rect) {
+                            return std::vector<std::string>{
+                                    base::StringPrintf("[%.2f, %.2f, %.2f, %.2f]", rect.left,
+                                                       rect.top, rect.right, rect.bottom)};
+                        }};
+
+    OutputLayerState<uint32_t, LayerStateField::ZOrder> mZOrder{
+            [](auto layer) { return layer->getState().z; }};
+
+    using BufferTransformState = OutputLayerState<hardware::graphics::composer::hal::Transform,
+                                                  LayerStateField::BufferTransform>;
+    BufferTransformState mBufferTransform{[](auto layer) {
+                                              return layer->getState().bufferTransform;
+                                          },
+                                          BufferTransformState::getHalToStrings()};
+
+    // Output-independent geometry state
+
+    using BlendModeState = OutputLayerState<hardware::graphics::composer::hal::BlendMode,
+                                            LayerStateField::BlendMode>;
+    BlendModeState mBlendMode{[](auto layer) {
+                                  return layer->getLayerFE().getCompositionState()->blendMode;
+                              },
+                              BlendModeState::getHalToStrings()};
+
+    OutputLayerState<float, LayerStateField::Alpha> mAlpha{
+            [](auto layer) { return layer->getLayerFE().getCompositionState()->alpha; }};
+
+    // TODO(b/180638831): Generic layer metadata
+
+    // Output-dependent per-frame state
+
+    OutputLayerState<Region, LayerStateField::VisibleRegion>
+            mVisibleRegion{[](auto layer) { return layer->getState().visibleRegion; },
+                           [](const Region& region) {
+                               using namespace std::string_literals;
+                               std::string dump;
+                               region.dump(dump, "");
+                               std::vector<std::string> split = base::Split(dump, "\n"s);
+                               split.erase(split.begin()); // Strip the header
+                               split.pop_back();           // Strip the last (empty) line
+                               for (std::string& line : split) {
+                                   line.erase(0, 4); // Strip leading padding before each rect
+                               }
+                               return split;
+                           },
+                           [](const Region& lhs, const Region& rhs) {
+                               return lhs.hasSameRects(rhs);
+                           }};
+
+    using DataspaceState = OutputLayerState<ui::Dataspace, LayerStateField::Dataspace>;
+    DataspaceState mDataspace{[](auto layer) { return layer->getState().dataspace; },
+                              DataspaceState::getHalToStrings()};
+
+    // TODO(b/180638831): Buffer format
+
+    // Output-independent per-frame state
+
+    OutputLayerState<mat4, LayerStateField::ColorTransform>
+            mColorTransform{[](auto layer) {
+                                const auto state = layer->getLayerFE().getCompositionState();
+                                return state->colorTransformIsIdentity ? mat4{}
+                                                                       : state->colorTransform;
+                            },
+                            [](const mat4& mat) {
+                                using namespace std::string_literals;
+                                std::vector<std::string> split =
+                                        base::Split(std::string(mat.asString().string()), "\n"s);
+                                split.pop_back(); // Strip the last (empty) line
+                                return split;
+                            }};
+
+    // TODO(b/180638831): Surface damage
+
+    using CompositionTypeState = OutputLayerState<hardware::graphics::composer::hal::Composition,
+                                                  LayerStateField::CompositionType>;
+    CompositionTypeState
+            mCompositionType{[](auto layer) {
+                                 return layer->getState().forceClientComposition
+                                         ? hardware::graphics::composer::hal::Composition::CLIENT
+                                         : layer->getLayerFE()
+                                                   .getCompositionState()
+                                                   ->compositionType;
+                             },
+                             CompositionTypeState::getHalToStrings()};
+
+    OutputLayerState<void*, LayerStateField::SidebandStream>
+            mSidebandStream{[](auto layer) {
+                                return layer->getLayerFE()
+                                        .getCompositionState()
+                                        ->sidebandStream.get();
+                            },
+                            [](void* p) {
+                                return std::vector<std::string>{base::StringPrintf("%p", p)};
+                            }};
+
+    OutputLayerState<sp<GraphicBuffer>, LayerStateField::Buffer>
+            mBuffer{[](auto layer) { return layer->getLayerFE().getCompositionState()->buffer; },
+                    [](const sp<GraphicBuffer>& buffer) {
+                        return std::vector<std::string>{base::StringPrintf("%p", buffer.get())};
+                    }};
+
+    int64_t mFramesSinceBufferUpdate = 0;
+
+    OutputLayerState<half4, LayerStateField::SolidColor>
+            mSolidColor{[](auto layer) { return layer->getLayerFE().getCompositionState()->color; },
+                        [](const half4& vec) {
+                            std::stringstream stream;
+                            stream << vec;
+                            return std::vector<std::string>{stream.str()};
+                        }};
+
+    std::array<StateInterface*, 13> getNonUniqueFields() {
+        std::array<const StateInterface*, 13> constFields =
+                const_cast<const LayerState*>(this)->getNonUniqueFields();
+        std::array<StateInterface*, 13> fields;
+        std::transform(constFields.cbegin(), constFields.cend(), fields.begin(),
+                       [](const StateInterface* constField) {
+                           return const_cast<StateInterface*>(constField);
+                       });
+        return fields;
+    }
+
+    std::array<const StateInterface*, 13> getNonUniqueFields() const {
+        return {
+                &mDisplayFrame,   &mSourceCrop,      &mZOrder,         &mBufferTransform,
+                &mBlendMode,      &mAlpha,           &mVisibleRegion,  &mDataspace,
+                &mColorTransform, &mCompositionType, &mSidebandStream, &mBuffer,
+                &mSolidColor,
+        };
+    }
+};
+
+using NonBufferHash = size_t;
+NonBufferHash getNonBufferHash(const std::vector<const LayerState*>&);
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
new file mode 100644
index 0000000..e96abb7
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <compositionengine/Output.h>
+#include <compositionengine/impl/planner/Flattener.h>
+#include <compositionengine/impl/planner/LayerState.h>
+#include <compositionengine/impl/planner/Predictor.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+#include <optional>
+#include <string>
+#include <unordered_map>
+
+namespace android {
+
+namespace renderengine {
+class RenderEngine;
+} // namespace renderengine
+
+namespace compositionengine::impl::planner {
+
+// This is the top level class for layer caching. It is responsible for
+// heuristically determining the composition strategy of the current layer stack,
+// and flattens inactive layers into an override buffer so it can be used
+// as a more efficient representation of parts of the layer stack.
+class Planner {
+public:
+    Planner() : mFlattener(mPredictor) {}
+
+    void setDisplaySize(ui::Size);
+
+    // Updates the Planner with the current set of layers before a composition strategy is
+    // determined.
+    // The Planner will call to the Flattener to determine to:
+    // 1. Replace any cached sets with a newly available flattened cached set
+    // 2. Create a new cached set if possible
+    void plan(
+            compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers);
+
+    // Updates the Planner with the current set of layers after a composition strategy is
+    // determined.
+    void reportFinalPlan(
+            compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers);
+
+    // The planner will call to the Flattener to render any pending cached set
+    void renderCachedSets(renderengine::RenderEngine&);
+
+    void dump(const Vector<String16>& args, std::string&);
+
+private:
+    void dumpUsage(std::string&) const;
+
+    std::unordered_map<LayerId, LayerState> mPreviousLayers;
+
+    std::vector<const LayerState*> mCurrentLayers;
+
+    Predictor mPredictor;
+    Flattener mFlattener;
+
+    std::optional<Predictor::PredictedPlan> mPredictedPlan;
+    NonBufferHash mFlattenedHash = 0;
+};
+
+} // namespace compositionengine::impl::planner
+} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
new file mode 100644
index 0000000..422af77
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
@@ -0,0 +1,278 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <compositionengine/impl/planner/LayerState.h>
+
+namespace android::compositionengine::impl::planner {
+
+class LayerStack {
+public:
+    LayerStack(const std::vector<const LayerState*>& layers) : mLayers(copyLayers(layers)) {}
+
+    struct ApproximateMatch {
+        bool operator==(const ApproximateMatch& other) const {
+            return differingIndex == other.differingIndex &&
+                    differingFields == other.differingFields;
+        }
+
+        size_t differingIndex;
+        Flags<LayerStateField> differingFields;
+    };
+
+    std::optional<ApproximateMatch> getApproximateMatch(
+            const std::vector<const LayerState*>& other) const;
+
+    void compare(const LayerStack& other, std::string& result) const {
+        if (mLayers.size() != other.mLayers.size()) {
+            base::StringAppendF(&result, "Cannot compare stacks of different sizes (%zd vs. %zd)\n",
+                                mLayers.size(), other.mLayers.size());
+            return;
+        }
+
+        for (size_t l = 0; l < mLayers.size(); ++l) {
+            const auto& thisLayer = mLayers[l];
+            const auto& otherLayer = other.mLayers[l];
+            base::StringAppendF(&result, "\n+ - - - - - - - - - Layer %d [%s]\n", thisLayer.getId(),
+                                thisLayer.getName().c_str());
+            auto comparisonOpt = thisLayer.compare(otherLayer);
+            base::StringAppendF(&result,
+                                "    %s     + - - - - - - - - - - - - - - - - - - - - - - - "
+                                "- Layer %d [%s]\n",
+                                comparisonOpt ? "         " : "Identical", otherLayer.getId(),
+                                otherLayer.getName().c_str());
+            if (comparisonOpt) {
+                result.append(*comparisonOpt);
+            }
+        }
+    }
+
+    void dump(std::string& result) const {
+        for (const LayerState& layer : mLayers) {
+            base::StringAppendF(&result, "+ - - - - - - - - - Layer %d [%s]\n", layer.getId(),
+                                layer.getName().c_str());
+            layer.dump(result);
+        }
+    }
+
+    void dumpLayerNames(std::string& result, const std::string& prefix = "  ") const {
+        for (const LayerState& layer : mLayers) {
+            result.append(prefix);
+            result.append(layer.getName());
+            result.append("\n");
+        }
+    }
+
+private:
+    std::vector<const LayerState> copyLayers(const std::vector<const LayerState*>& layers) {
+        std::vector<const LayerState> copiedLayers;
+        copiedLayers.reserve(layers.size());
+        std::transform(layers.cbegin(), layers.cend(), std::back_inserter(copiedLayers),
+                       [](const LayerState* layerState) { return *layerState; });
+        return copiedLayers;
+    }
+
+    std::vector<const LayerState> mLayers;
+
+    // TODO(b/180976743): Tune kMaxDifferingFields
+    constexpr static int kMaxDifferingFields = 6;
+};
+
+class Plan {
+public:
+    static std::optional<Plan> fromString(const std::string&);
+
+    void reset() { mLayerTypes.clear(); }
+    void addLayerType(hardware::graphics::composer::hal::Composition type) {
+        mLayerTypes.emplace_back(type);
+    }
+
+    friend std::string to_string(const Plan& plan);
+
+    friend bool operator==(const Plan& lhs, const Plan& rhs) {
+        return lhs.mLayerTypes == rhs.mLayerTypes;
+    }
+    friend bool operator!=(const Plan& lhs, const Plan& rhs) { return !(lhs == rhs); }
+
+private:
+    std::vector<hardware::graphics::composer::hal::Composition> mLayerTypes;
+};
+
+} // namespace android::compositionengine::impl::planner
+
+namespace std {
+template <>
+struct hash<android::compositionengine::impl::planner::Plan> {
+    size_t operator()(const android::compositionengine::impl::planner::Plan& plan) const {
+        return std::hash<std::string>{}(to_string(plan));
+    }
+};
+} // namespace std
+
+namespace android::compositionengine::impl::planner {
+
+class Prediction {
+public:
+    enum class Type {
+        Exact,
+        Approximate,
+        Total,
+    };
+
+    friend std::string to_string(Type type) {
+        using namespace std::string_literals;
+
+        switch (type) {
+            case Type::Exact:
+                return "Exact";
+            case Type::Approximate:
+                return "Approximate";
+            case Type::Total:
+                return "Total";
+        }
+    }
+
+    Prediction(const std::vector<const LayerState*>& layers, Plan plan)
+          : mExampleLayerStack(layers), mPlan(std::move(plan)) {}
+
+    const LayerStack& getExampleLayerStack() const { return mExampleLayerStack; }
+    const Plan& getPlan() const { return mPlan; }
+
+    size_t getHitCount(Type type) const {
+        if (type == Type::Total) {
+            return getHitCount(Type::Exact) + getHitCount(Type::Approximate);
+        }
+        return getStatsForType(type).hitCount;
+    }
+
+    size_t getMissCount(Type type) const {
+        if (type == Type::Total) {
+            return getMissCount(Type::Exact) + getMissCount(Type::Approximate);
+        }
+        return getStatsForType(type).missCount;
+    }
+
+    void recordHit(Type type) { ++getStatsForType(type).hitCount; }
+
+    void recordMiss(Type type) { ++getStatsForType(type).missCount; }
+
+    void dump(std::string&) const;
+
+private:
+    struct Stats {
+        void dump(std::string& result) const {
+            const size_t totalAttempts = hitCount + missCount;
+            base::StringAppendF(&result, "%.2f%% (%zd/%zd)", 100.0f * hitCount / totalAttempts,
+                                hitCount, totalAttempts);
+        }
+
+        size_t hitCount = 0;
+        size_t missCount = 0;
+    };
+
+    const Stats& getStatsForType(Type type) const {
+        return (type == Type::Exact) ? mExactStats : mApproximateStats;
+    }
+
+    Stats& getStatsForType(Type type) {
+        return const_cast<Stats&>(const_cast<const Prediction*>(this)->getStatsForType(type));
+    }
+
+    LayerStack mExampleLayerStack;
+    Plan mPlan;
+
+    Stats mExactStats;
+    Stats mApproximateStats;
+};
+
+class Predictor {
+public:
+    struct PredictedPlan {
+        NonBufferHash hash;
+        Plan plan;
+        Prediction::Type type;
+    };
+
+    std::optional<PredictedPlan> getPredictedPlan(const std::vector<const LayerState*>&,
+                                                  NonBufferHash) const;
+
+    void recordResult(std::optional<PredictedPlan> predictedPlan, NonBufferHash flattenedHash,
+                      const std::vector<const LayerState*>&, bool hasSkippedLayers, Plan result);
+
+    void dump(std::string&) const;
+
+    void compareLayerStacks(NonBufferHash leftHash, NonBufferHash rightHash, std::string&) const;
+    void describeLayerStack(NonBufferHash, std::string&) const;
+    void listSimilarStacks(Plan, std::string&) const;
+
+private:
+    // Retrieves a prediction from either the main prediction list or from the candidate list
+    const Prediction& getPrediction(NonBufferHash) const;
+    Prediction& getPrediction(NonBufferHash);
+
+    std::optional<Plan> getExactMatch(NonBufferHash) const;
+    std::optional<NonBufferHash> getApproximateMatch(
+            const std::vector<const LayerState*>& layers) const;
+
+    void promoteIfCandidate(NonBufferHash);
+    void recordPredictedResult(PredictedPlan, const std::vector<const LayerState*>& layers,
+                               Plan result);
+    bool findSimilarPrediction(const std::vector<const LayerState*>& layers, Plan result);
+
+    void dumpPredictionsByFrequency(std::string&) const;
+
+    struct PromotionCandidate {
+        PromotionCandidate(NonBufferHash hash, Prediction&& prediction)
+              : hash(hash), prediction(std::move(prediction)) {}
+
+        NonBufferHash hash;
+        Prediction prediction;
+    };
+
+    static constexpr const size_t MAX_CANDIDATES = 4;
+    std::deque<PromotionCandidate> mCandidates;
+    decltype(mCandidates)::const_iterator getCandidateEntryByHash(NonBufferHash hash) const {
+        const auto candidateMatches = [&](const PromotionCandidate& candidate) {
+            return candidate.hash == hash;
+        };
+
+        return std::find_if(mCandidates.cbegin(), mCandidates.cend(), candidateMatches);
+    }
+
+    std::unordered_map<NonBufferHash, Prediction> mPredictions;
+    std::unordered_map<Plan, std::vector<NonBufferHash>> mSimilarStacks;
+
+    struct ApproximateStack {
+        ApproximateStack(NonBufferHash hash, LayerStack::ApproximateMatch match)
+              : hash(hash), match(match) {}
+
+        bool operator==(const ApproximateStack& other) const {
+            return hash == other.hash && match == other.match;
+        }
+
+        NonBufferHash hash;
+        LayerStack::ApproximateMatch match;
+    };
+
+    std::vector<ApproximateStack> mApproximateStacks;
+
+    mutable size_t mExactHitCount = 0;
+    mutable size_t mApproximateHitCount = 0;
+    mutable size_t mMissCount = 0;
+};
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 45891a7..dde8999 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -42,6 +42,7 @@
     MOCK_METHOD1(onLayerDisplayed, void(const sp<Fence>&));
 
     MOCK_CONST_METHOD0(getDebugName, const char*());
+    MOCK_CONST_METHOD0(getSequence, int32_t());
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 95db4da..5aa53e5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -45,6 +45,7 @@
     MOCK_METHOD1(setColorProfile, void(const ColorProfile&));
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
+    MOCK_CONST_METHOD2(dumpPlannerInfo, void(const Vector<String16>&, std::string&));
     MOCK_CONST_METHOD0(getName, const std::string&());
     MOCK_METHOD1(setName, void(const std::string&));
 
@@ -85,7 +86,9 @@
     MOCK_METHOD1(setReleasedLayers, void(const compositionengine::CompositionRefreshArgs&));
 
     MOCK_CONST_METHOD1(updateLayerStateFromFE, void(const CompositionRefreshArgs&));
-    MOCK_METHOD1(updateAndWriteCompositionState, void(const CompositionRefreshArgs&));
+    MOCK_METHOD1(updateCompositionState, void(const CompositionRefreshArgs&));
+    MOCK_METHOD0(planComposition, void());
+    MOCK_METHOD1(writeCompositionState, void(const CompositionRefreshArgs&));
     MOCK_METHOD1(updateColorProfile, void(const compositionengine::CompositionRefreshArgs&));
 
     MOCK_METHOD0(beginFrame, void());
@@ -104,6 +107,7 @@
     MOCK_CONST_METHOD0(getSkipColorTransform, bool());
 
     MOCK_METHOD0(postFramebuffer, void());
+    MOCK_METHOD0(renderCachedSets, void());
     MOCK_METHOD0(presentAndGetFrameFences, compositionengine::Output::FrameFences());
 
     MOCK_METHOD3(generateClientCompositionRequests,
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
index 81e1fc7..2454ff7 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
@@ -39,7 +39,7 @@
     MOCK_METHOD0(editState, impl::OutputLayerCompositionState&());
 
     MOCK_METHOD3(updateCompositionState, void(bool, bool, ui::Transform::RotationFlags));
-    MOCK_METHOD1(writeStateToHWC, void(bool));
+    MOCK_METHOD2(writeStateToHWC, void(bool, bool));
     MOCK_CONST_METHOD0(writeCursorPositionToHWC, void());
 
     MOCK_CONST_METHOD0(getHwcLayer, HWC2::Layer*());
@@ -49,6 +49,7 @@
     MOCK_METHOD0(prepareForDeviceLayerRequests, void());
     MOCK_METHOD1(applyDeviceLayerRequest, void(Hwc2::IComposerClient::LayerRequest request));
     MOCK_CONST_METHOD0(needsFiltering, bool());
+    MOCK_CONST_METHOD0(getOverrideCompositionList, std::vector<LayerFE::LayerSettings>());
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 0b0b8d5..a605fe1 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -55,7 +55,8 @@
     editState().isSecure = args.isSecure;
     editState().displaySpace.bounds = Rect(args.pixels);
     setLayerStackFilter(args.layerStackId,
-                        args.physical && args.physical->type == DisplayConnectionType::Internal);
+                        args.physical &&
+                                args.physical->type == ui::DisplayConnectionType::Internal);
     setName(args.name);
     mGpuVirtualDisplayIdGenerator = args.gpuVirtualDisplayIdGenerator;
 
diff --git a/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp b/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
index 9d1bb02..0cc2c6e 100644
--- a/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
@@ -100,4 +100,10 @@
                   ); /* clang-format on */
 }
 
+void dumpVal(std::string& out, const char* name, const StretchEffect& effect) {
+    StringAppendF(&out, "%s={ area=[%f, %f, %f, %f], vec=(%f, %f), max=%f } ", name,
+                  effect.area.left, effect.area.top, effect.area.right, effect.area.bottom,
+                  effect.vectorX, effect.vectorY, effect.maxAmount);
+}
+
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
index 1338538..430945a 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -74,6 +74,9 @@
     dumpVal(out, "blend", toString(blendMode), blendMode);
     dumpVal(out, "alpha", alpha);
     dumpVal(out, "backgroundBlurRadius", backgroundBlurRadius);
+    if (stretchEffect.hasEffect()) {
+        dumpVal(out, "stretchEffect", stretchEffect);
+    }
 
     if (!metadata.empty()) {
         out.append("\n      metadata {");
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 3907ac5..dc1aacc 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -27,6 +27,9 @@
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <compositionengine/impl/planner/Planner.h>
+
+#include <SurfaceFlingerProperties.sysprop.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -38,6 +41,7 @@
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion"
 
+#include <android-base/properties.h>
 #include <ui/DebugUtils.h>
 #include <ui/HdrCapabilities.h>
 #include <utils/Trace.h>
@@ -50,6 +54,18 @@
 
 namespace impl {
 
+Output::Output() {
+    const bool enableLayerCaching = [] {
+        const bool enable =
+                android::sysprop::SurfaceFlingerProperties::enable_layer_caching().value_or(false);
+        return base::GetBoolProperty(std::string("debug.sf.enable_layer_caching"), enable);
+    }();
+
+    if (enableLayerCaching) {
+        mPlanner = std::make_unique<planner::Planner>();
+    }
+}
+
 namespace {
 
 template <typename T>
@@ -182,6 +198,10 @@
     const Rect newOrientedBounds(orientedSize);
     state.orientedDisplaySpace.bounds = newOrientedBounds;
 
+    if (mPlanner) {
+        mPlanner->setDisplaySize(size);
+    }
+
     dirtyEntireOutput();
 }
 
@@ -269,6 +289,15 @@
     }
 }
 
+void Output::dumpPlannerInfo(const Vector<String16>& args, std::string& out) const {
+    if (!mPlanner) {
+        base::StringAppendF(&out, "Planner is disabled\n");
+        return;
+    }
+    base::StringAppendF(&out, "Planner info for display [%s]\n", mName.c_str());
+    mPlanner->dump(args, out);
+}
+
 compositionengine::DisplayColorProfile* Output::getDisplayColorProfile() const {
     return mDisplayColorProfile.get();
 }
@@ -292,7 +321,11 @@
 
 void Output::setRenderSurface(std::unique_ptr<compositionengine::RenderSurface> surface) {
     mRenderSurface = std::move(surface);
-    editState().framebufferSpace.bounds = Rect(mRenderSurface->getSize());
+    const auto size = mRenderSurface->getSize();
+    editState().framebufferSpace.bounds = Rect(size);
+    if (mPlanner) {
+        mPlanner->setDisplaySize(size);
+    }
     dirtyEntireOutput();
 }
 
@@ -368,13 +401,16 @@
     ALOGV(__FUNCTION__);
 
     updateColorProfile(refreshArgs);
-    updateAndWriteCompositionState(refreshArgs);
+    updateCompositionState(refreshArgs);
+    planComposition();
+    writeCompositionState(refreshArgs);
     setColorTransform(refreshArgs);
     beginFrame();
     prepareFrame();
     devOptRepaintFlash(refreshArgs);
     finishFrame(refreshArgs);
     postFramebuffer();
+    renderCachedSets();
 }
 
 void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& refreshArgs,
@@ -632,8 +668,7 @@
     }
 }
 
-void Output::updateAndWriteCompositionState(
-        const compositionengine::CompositionRefreshArgs& refreshArgs) {
+void Output::updateCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) {
     ATRACE_CALL();
     ALOGV(__FUNCTION__);
 
@@ -653,9 +688,45 @@
         if (mLayerRequestingBackgroundBlur == layer) {
             forceClientComposition = false;
         }
+    }
+}
 
-        // Send the updated state to the HWC, if appropriate.
-        layer->writeStateToHWC(refreshArgs.updatingGeometryThisFrame);
+void Output::planComposition() {
+    if (!mPlanner || !getState().isEnabled) {
+        return;
+    }
+
+    ATRACE_CALL();
+    ALOGV(__FUNCTION__);
+
+    mPlanner->plan(getOutputLayersOrderedByZ());
+}
+
+void Output::writeCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) {
+    ATRACE_CALL();
+    ALOGV(__FUNCTION__);
+
+    if (!getState().isEnabled) {
+        return;
+    }
+
+    sp<GraphicBuffer> previousOverride = nullptr;
+    for (auto* layer : getOutputLayersOrderedByZ()) {
+        bool skipLayer = false;
+        if (layer->getState().overrideInfo.buffer != nullptr) {
+            if (previousOverride != nullptr &&
+                layer->getState().overrideInfo.buffer == previousOverride) {
+                ALOGV("Skipping redundant buffer");
+                skipLayer = true;
+            }
+            previousOverride = layer->getState().overrideInfo.buffer;
+        }
+
+        // TODO(b/181172795): We now update geometry for all flattened layers. We should update it
+        // only when the geometry actually changes
+        const bool includeGeometry = refreshArgs.updatingGeometryThisFrame ||
+                layer->getState().overrideInfo.buffer != nullptr || skipLayer;
+        layer->writeStateToHWC(includeGeometry, skipLayer);
     }
 }
 
@@ -826,6 +897,10 @@
 
     chooseCompositionStrategy();
 
+    if (mPlanner) {
+        mPlanner->reportFinalPlan(getOutputLayersOrderedByZ());
+    }
+
     mRenderSurface->prepareFrame(outputState.usesClientComposition,
                                  outputState.usesDeviceComposition);
 }
@@ -1075,10 +1150,16 @@
                                    .realContentIsVisible = realContentIsVisible,
                                    .clearContent = !clientComposition,
                                    .disableBlurs = disableBlurs};
-            std::vector<LayerFE::LayerSettings> results =
-                    layerFE.prepareClientCompositionList(targetSettings);
-            if (realContentIsVisible && !results.empty()) {
-                layer->editState().clientCompositionTimestamp = systemTime();
+
+            std::vector<LayerFE::LayerSettings> results;
+            if (layer->getState().overrideInfo.buffer != nullptr) {
+                results = layer->getOverrideCompositionList();
+                ALOGV("Replacing [%s] with override in RE", layer->getLayerFE().getDebugName());
+            } else {
+                results = layerFE.prepareClientCompositionList(targetSettings);
+                if (realContentIsVisible && !results.empty()) {
+                    layer->editState().clientCompositionTimestamp = systemTime();
+                }
             }
 
             clientCompositionLayers.insert(clientCompositionLayers.end(),
@@ -1169,6 +1250,12 @@
     mReleasedLayers.clear();
 }
 
+void Output::renderCachedSets() {
+    if (mPlanner) {
+        mPlanner->renderCachedSets(getCompositionEngine().getRenderEngine());
+    }
+}
+
 void Output::dirtyEntireOutput() {
     auto& outputState = editState();
     outputState.dirtyRegion.set(outputState.displaySpace.bounds);
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 0faab6f..54784a2 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -16,7 +16,6 @@
 
 #include <android-base/stringprintf.h>
 #include <compositionengine/DisplayColorProfile.h>
-#include <compositionengine/LayerFE.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/Output.h>
 #include <compositionengine/impl/OutputCompositionState.h>
@@ -313,7 +312,7 @@
     }
 }
 
-void OutputLayer::writeStateToHWC(bool includeGeometry) {
+void OutputLayer::writeStateToHWC(bool includeGeometry, bool skipLayer) {
     const auto& state = getState();
     // Skip doing this if there is no HWC interface
     if (!state.hwc) {
@@ -336,7 +335,8 @@
 
     if (includeGeometry) {
         writeOutputDependentGeometryStateToHWC(hwcLayer.get(), requestedCompositionType);
-        writeOutputIndependentGeometryStateToHWC(hwcLayer.get(), *outputIndependentState);
+        writeOutputIndependentGeometryStateToHWC(hwcLayer.get(), *outputIndependentState,
+                                                 skipLayer);
     }
 
     writeOutputDependentPerFrameStateToHWC(hwcLayer.get());
@@ -352,23 +352,27 @@
         HWC2::Layer* hwcLayer, hal::Composition requestedCompositionType) {
     const auto& outputDependentState = getState();
 
-    if (auto error = hwcLayer->setDisplayFrame(outputDependentState.displayFrame);
-        error != hal::Error::NONE) {
-        ALOGE("[%s] Failed to set display frame [%d, %d, %d, %d]: %s (%d)",
-              getLayerFE().getDebugName(), outputDependentState.displayFrame.left,
-              outputDependentState.displayFrame.top, outputDependentState.displayFrame.right,
-              outputDependentState.displayFrame.bottom, to_string(error).c_str(),
-              static_cast<int32_t>(error));
+    Rect displayFrame = outputDependentState.displayFrame;
+    FloatRect sourceCrop = outputDependentState.sourceCrop;
+    if (outputDependentState.overrideInfo.buffer != nullptr) { // adyabr
+        displayFrame = outputDependentState.overrideInfo.displayFrame;
+        sourceCrop = displayFrame.toFloatRect();
     }
 
-    if (auto error = hwcLayer->setSourceCrop(outputDependentState.sourceCrop);
-        error != hal::Error::NONE) {
+    ALOGV("Writing display frame [%d, %d, %d, %d]", displayFrame.left, displayFrame.top,
+          displayFrame.right, displayFrame.bottom);
+
+    if (auto error = hwcLayer->setDisplayFrame(displayFrame); error != hal::Error::NONE) {
+        ALOGE("[%s] Failed to set display frame [%d, %d, %d, %d]: %s (%d)",
+              getLayerFE().getDebugName(), displayFrame.left, displayFrame.top, displayFrame.right,
+              displayFrame.bottom, to_string(error).c_str(), static_cast<int32_t>(error));
+    }
+
+    if (auto error = hwcLayer->setSourceCrop(sourceCrop); error != hal::Error::NONE) {
         ALOGE("[%s] Failed to set source crop [%.3f, %.3f, %.3f, %.3f]: "
               "%s (%d)",
-              getLayerFE().getDebugName(), outputDependentState.sourceCrop.left,
-              outputDependentState.sourceCrop.top, outputDependentState.sourceCrop.right,
-              outputDependentState.sourceCrop.bottom, to_string(error).c_str(),
-              static_cast<int32_t>(error));
+              getLayerFE().getDebugName(), sourceCrop.left, sourceCrop.top, sourceCrop.right,
+              sourceCrop.bottom, to_string(error).c_str(), static_cast<int32_t>(error));
     }
 
     if (auto error = hwcLayer->setZOrder(outputDependentState.z); error != hal::Error::NONE) {
@@ -389,7 +393,8 @@
 }
 
 void OutputLayer::writeOutputIndependentGeometryStateToHWC(
-        HWC2::Layer* hwcLayer, const LayerFECompositionState& outputIndependentState) {
+        HWC2::Layer* hwcLayer, const LayerFECompositionState& outputIndependentState,
+        bool skipLayer) {
     if (auto error = hwcLayer->setBlendMode(outputIndependentState.blendMode);
         error != hal::Error::NONE) {
         ALOGE("[%s] Failed to set blend mode %s: %s (%d)", getLayerFE().getDebugName(),
@@ -397,10 +402,12 @@
               static_cast<int32_t>(error));
     }
 
-    if (auto error = hwcLayer->setPlaneAlpha(outputIndependentState.alpha);
-        error != hal::Error::NONE) {
-        ALOGE("[%s] Failed to set plane alpha %.3f: %s (%d)", getLayerFE().getDebugName(),
-              outputIndependentState.alpha, to_string(error).c_str(), static_cast<int32_t>(error));
+    const float alpha = skipLayer ? 0.0f : outputIndependentState.alpha;
+    ALOGV("Writing alpha %f", alpha);
+
+    if (auto error = hwcLayer->setPlaneAlpha(alpha); error != hal::Error::NONE) {
+        ALOGE("[%s] Failed to set plane alpha %.3f: %s (%d)", getLayerFE().getDebugName(), alpha,
+              to_string(error).c_str(), static_cast<int32_t>(error));
     }
 
     for (const auto& [name, entry] : outputIndependentState.metadata) {
@@ -509,19 +516,26 @@
               to_string(error).c_str(), static_cast<int32_t>(error));
     }
 
+    sp<GraphicBuffer> buffer = outputIndependentState.buffer;
+    sp<Fence> acquireFence = outputIndependentState.acquireFence;
+    if (getState().overrideInfo.buffer != nullptr) {
+        buffer = getState().overrideInfo.buffer;
+        acquireFence = getState().overrideInfo.acquireFence;
+    }
+
+    ALOGV("Writing buffer %p", buffer.get());
+
     uint32_t hwcSlot = 0;
     sp<GraphicBuffer> hwcBuffer;
     // We need access to the output-dependent state for the buffer cache there,
     // though otherwise the buffer is not output-dependent.
-    editState().hwc->hwcBufferCache.getHwcBuffer(outputIndependentState.bufferSlot,
-                                                 outputIndependentState.buffer, &hwcSlot,
-                                                 &hwcBuffer);
+    editState().hwc->hwcBufferCache.getHwcBuffer(outputIndependentState.bufferSlot, buffer,
+                                                 &hwcSlot, &hwcBuffer);
 
-    if (auto error = hwcLayer->setBuffer(hwcSlot, hwcBuffer, outputIndependentState.acquireFence);
+    if (auto error = hwcLayer->setBuffer(hwcSlot, hwcBuffer, acquireFence);
         error != hal::Error::NONE) {
-        ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(),
-              outputIndependentState.buffer->handle, to_string(error).c_str(),
-              static_cast<int32_t>(error));
+        ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(), buffer->handle,
+              to_string(error).c_str(), static_cast<int32_t>(error));
     }
 }
 
@@ -652,6 +666,26 @@
             sourceCrop.getWidth() != displayFrame.getWidth();
 }
 
+std::vector<LayerFE::LayerSettings> OutputLayer::getOverrideCompositionList() const {
+    if (getState().overrideInfo.buffer == nullptr) {
+        return {};
+    }
+
+    LayerFE::LayerSettings settings;
+    settings.geometry = renderengine::Geometry{
+            .boundaries = getState().overrideInfo.displayFrame.toFloatRect(),
+    };
+    settings.bufferId = getState().overrideInfo.buffer->getId();
+    settings.source =
+            renderengine::PixelSource{.buffer = renderengine::Buffer{
+                                              .buffer = getState().overrideInfo.buffer,
+                                              .fence = getState().overrideInfo.acquireFence,
+                                      }};
+    settings.alpha = 1.0f;
+
+    return {static_cast<LayerFE::LayerSettings>(settings)};
+}
+
 void OutputLayer::dump(std::string& out) const {
     using android::base::StringAppendF;
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
new file mode 100644
index 0000000..ab3fe9e
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -0,0 +1,243 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "Planner"
+// #define LOG_NDEBUG 0
+
+#include <android-base/properties.h>
+#include <compositionengine/impl/planner/CachedSet.h>
+#include <math/HashCombine.h>
+#include <renderengine/DisplaySettings.h>
+#include <renderengine/RenderEngine.h>
+
+namespace android::compositionengine::impl::planner {
+
+const bool CachedSet::sDebugHighlighLayers =
+        base::GetBoolProperty(std::string("debug.sf.layer_caching_highlight"), false);
+
+std::string durationString(std::chrono::milliseconds duration) {
+    using namespace std::chrono_literals;
+
+    std::string result;
+
+    if (duration >= 1h) {
+        const auto hours = std::chrono::duration_cast<std::chrono::hours>(duration);
+        base::StringAppendF(&result, "%d hr ", static_cast<int>(hours.count()));
+        duration -= hours;
+    }
+    if (duration >= 1min) {
+        const auto minutes = std::chrono::duration_cast<std::chrono::minutes>(duration);
+        base::StringAppendF(&result, "%d min ", static_cast<int>(minutes.count()));
+        duration -= minutes;
+    }
+    base::StringAppendF(&result, "%.3f sec ", duration.count() / 1000.0f);
+
+    return result;
+}
+
+CachedSet::Layer::Layer(const LayerState* state, std::chrono::steady_clock::time_point lastUpdate)
+      : mState(state), mHash(state->getHash(LayerStateField::Buffer)), mLastUpdate(lastUpdate) {}
+
+CachedSet::CachedSet(const LayerState* layer, std::chrono::steady_clock::time_point lastUpdate)
+      : mFingerprint(layer->getHash(LayerStateField::Buffer)), mLastUpdate(lastUpdate) {
+    addLayer(layer, lastUpdate);
+}
+
+CachedSet::CachedSet(Layer layer)
+      : mFingerprint(layer.getHash()),
+        mLastUpdate(layer.getLastUpdate()),
+        mBounds(layer.getDisplayFrame()) {
+    mLayers.emplace_back(std::move(layer));
+}
+
+void CachedSet::addLayer(const LayerState* layer,
+                         std::chrono::steady_clock::time_point lastUpdate) {
+    mLayers.emplace_back(layer, lastUpdate);
+
+    Region boundingRegion;
+    boundingRegion.orSelf(mBounds);
+    boundingRegion.orSelf(layer->getDisplayFrame());
+    mBounds = boundingRegion.getBounds();
+}
+
+NonBufferHash CachedSet::getNonBufferHash() const {
+    if (mLayers.size() == 1) {
+        return mFingerprint;
+    }
+
+    // TODO(b/181192080): Add all fields which contribute to geometry of override layer (e.g.,
+    // dataspace)
+    size_t hash = 0;
+    android::hashCombineSingle(hash, mBounds);
+    return hash;
+}
+
+size_t CachedSet::getComponentDisplayCost() const {
+    size_t displayCost = 0;
+
+    for (const Layer& layer : mLayers) {
+        displayCost += static_cast<size_t>(layer.getDisplayFrame().width() *
+                                           layer.getDisplayFrame().height());
+    }
+
+    return displayCost;
+}
+
+size_t CachedSet::getCreationCost() const {
+    if (mLayers.size() == 1) {
+        return 0;
+    }
+
+    // Reads
+    size_t creationCost = getComponentDisplayCost();
+
+    // Write - assumes that the output buffer only gets written once per pixel
+    creationCost += static_cast<size_t>(mBounds.width() * mBounds.height());
+
+    return creationCost;
+}
+
+size_t CachedSet::getDisplayCost() const {
+    return static_cast<size_t>(mBounds.width() * mBounds.height());
+}
+
+bool CachedSet::hasBufferUpdate(std::vector<const LayerState*>::const_iterator layers) const {
+    for (const Layer& layer : mLayers) {
+        if (layer.getFramesSinceBufferUpdate() == 0) {
+            return true;
+        }
+        ++layers;
+    }
+    return false;
+}
+
+bool CachedSet::hasReadyBuffer() const {
+    return mBuffer != nullptr && mDrawFence->getStatus() == Fence::Status::Signaled;
+}
+
+std::vector<CachedSet> CachedSet::decompose() const {
+    std::vector<CachedSet> layers;
+
+    std::transform(mLayers.begin(), mLayers.end(), std::back_inserter(layers),
+                   [](Layer layer) { return CachedSet(std::move(layer)); });
+
+    return layers;
+}
+
+void CachedSet::updateAge(std::chrono::steady_clock::time_point now) {
+    LOG_ALWAYS_FATAL_IF(mLayers.size() > 1, "[%s] This should only be called on single-layer sets",
+                        __func__);
+
+    if (mLayers[0].getFramesSinceBufferUpdate() == 0) {
+        mLastUpdate = now;
+        mAge = 0;
+    }
+}
+
+void CachedSet::render(renderengine::RenderEngine& renderEngine) {
+    renderengine::DisplaySettings displaySettings{
+            .physicalDisplay = Rect(0, 0, mBounds.getWidth(), mBounds.getHeight()),
+            .clip = mBounds,
+    };
+
+    Region clearRegion = Region::INVALID_REGION;
+    Rect viewport = mBounds;
+    LayerFE::ClientCompositionTargetSettings targetSettings{
+            .clip = Region(mBounds),
+            .needsFiltering = false,
+            .isSecure = true,
+            .supportsProtectedContent = false,
+            .clearRegion = clearRegion,
+            .viewport = viewport,
+            // TODO(181192086): Propagate the Output's dataspace instead of using UNKNOWN
+            .dataspace = ui::Dataspace::UNKNOWN,
+            .realContentIsVisible = true,
+            .clearContent = false,
+            .disableBlurs = false,
+    };
+
+    std::vector<renderengine::LayerSettings> layerSettings;
+    for (const auto& layer : mLayers) {
+        const auto clientCompositionList =
+                layer.getState()->getOutputLayer()->getLayerFE().prepareClientCompositionList(
+                        targetSettings);
+        layerSettings.insert(layerSettings.end(), clientCompositionList.cbegin(),
+                             clientCompositionList.cend());
+    }
+
+    std::vector<const renderengine::LayerSettings*> layerSettingsPointers;
+    std::transform(layerSettings.cbegin(), layerSettings.cend(),
+                   std::back_inserter(layerSettingsPointers),
+                   [](const renderengine::LayerSettings& settings) { return &settings; });
+
+    if (sDebugHighlighLayers) {
+        renderengine::LayerSettings highlight{
+                .geometry =
+                        renderengine::Geometry{
+                                .boundaries = FloatRect(0.0f, 0.0f,
+                                                        static_cast<float>(mBounds.getWidth()),
+                                                        static_cast<float>(mBounds.getHeight())),
+                        },
+                .source =
+                        renderengine::PixelSource{
+                                .solidColor = half3(0.25f, 0.0f, 0.5f),
+                        },
+                .alpha = half(0.05f),
+        };
+
+        layerSettingsPointers.emplace_back(&highlight);
+    }
+
+    const uint64_t usageFlags = GraphicBuffer::USAGE_HW_RENDER | GraphicBuffer::USAGE_HW_COMPOSER |
+            GraphicBuffer::USAGE_HW_TEXTURE;
+    sp<GraphicBuffer> buffer = new GraphicBuffer(static_cast<uint32_t>(mBounds.getWidth()),
+                                                 static_cast<uint32_t>(mBounds.getHeight()),
+                                                 HAL_PIXEL_FORMAT_RGBA_8888, 1, usageFlags);
+    LOG_ALWAYS_FATAL_IF(buffer->initCheck() != OK);
+    base::unique_fd drawFence;
+    status_t result = renderEngine.drawLayers(displaySettings, layerSettingsPointers, buffer, false,
+                                              base::unique_fd(), &drawFence);
+
+    if (result == NO_ERROR) {
+        mBuffer = buffer;
+        mDrawFence = new Fence(drawFence.release());
+    }
+}
+
+void CachedSet::dump(std::string& result) const {
+    const auto now = std::chrono::steady_clock::now();
+
+    const auto lastUpdate =
+            std::chrono::duration_cast<std::chrono::milliseconds>(now - mLastUpdate);
+    base::StringAppendF(&result, "  + Fingerprint %016zx, last update %sago, age %zd\n",
+                        mFingerprint, durationString(lastUpdate).c_str(), mAge);
+
+    if (mLayers.size() == 1) {
+        base::StringAppendF(&result, "    Layer [%s]\n", mLayers[0].getName().c_str());
+        base::StringAppendF(&result, "    Buffer %p", mLayers[0].getBuffer().get());
+    } else {
+        result.append("    Cached set of:");
+        for (const Layer& layer : mLayers) {
+            base::StringAppendF(&result, "\n      Layer [%s]", layer.getName().c_str());
+        }
+    }
+
+    base::StringAppendF(&result, "\n    Creation cost: %zd", getCreationCost());
+    base::StringAppendF(&result, "\n    Display cost: %zd\n", getDisplayCost());
+}
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
new file mode 100644
index 0000000..0c09714
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -0,0 +1,355 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "Planner"
+// #define LOG_NDEBUG 0
+
+#include <compositionengine/impl/planner/Flattener.h>
+#include <compositionengine/impl/planner/LayerState.h>
+#include <compositionengine/impl/planner/Predictor.h>
+
+using time_point = std::chrono::steady_clock::time_point;
+using namespace std::chrono_literals;
+
+namespace android::compositionengine::impl::planner {
+
+NonBufferHash Flattener::flattenLayers(const std::vector<const LayerState*>& layers,
+                                       NonBufferHash hash) {
+    const auto now = std::chrono::steady_clock::now();
+
+    const size_t unflattenedDisplayCost = calculateDisplayCost(layers);
+    mUnflattenedDisplayCost += unflattenedDisplayCost;
+
+    if (mCurrentGeometry != hash) {
+        resetActivities(hash, now);
+        mFlattenedDisplayCost += unflattenedDisplayCost;
+        return hash;
+    }
+
+    ++mInitialLayerCounts[layers.size()];
+
+    if (mergeWithCachedSets(layers, now)) {
+        hash = mLayersHash;
+    }
+
+    ++mFinalLayerCounts[mLayers.size()];
+
+    buildCachedSets(now);
+
+    return hash;
+}
+
+void Flattener::renderCachedSets(renderengine::RenderEngine& renderEngine) {
+    if (!mNewCachedSet) {
+        return;
+    }
+
+    mNewCachedSet->render(renderEngine);
+}
+
+void Flattener::reset() {
+    resetActivities(0, std::chrono::steady_clock::now());
+
+    mUnflattenedDisplayCost = 0;
+    mFlattenedDisplayCost = 0;
+    mInitialLayerCounts.clear();
+    mFinalLayerCounts.clear();
+    mCachedSetCreationCount = 0;
+    mCachedSetCreationCost = 0;
+    mInvalidatedCachedSetAges.clear();
+}
+
+void Flattener::dump(std::string& result) const {
+    const auto now = std::chrono::steady_clock::now();
+
+    base::StringAppendF(&result, "Flattener state:\n");
+
+    result.append("\n  Statistics:\n");
+
+    result.append("    Display cost (in screen-size buffers):\n");
+    const size_t displayArea = static_cast<size_t>(mDisplaySize.width * mDisplaySize.height);
+    base::StringAppendF(&result, "      Unflattened: %.2f\n",
+                        static_cast<float>(mUnflattenedDisplayCost) / displayArea);
+    base::StringAppendF(&result, "      Flattened:   %.2f\n",
+                        static_cast<float>(mFlattenedDisplayCost) / displayArea);
+
+    const auto compareLayerCounts = [](const std::pair<size_t, size_t>& left,
+                                       const std::pair<size_t, size_t>& right) {
+        return left.first < right.first;
+    };
+
+    const size_t maxLayerCount = std::max_element(mInitialLayerCounts.cbegin(),
+                                                  mInitialLayerCounts.cend(), compareLayerCounts)
+                                         ->first;
+
+    result.append("\n    Initial counts:\n");
+    for (size_t count = 1; count < maxLayerCount; ++count) {
+        size_t initial = mInitialLayerCounts.count(count) > 0 ? mInitialLayerCounts.at(count) : 0;
+        base::StringAppendF(&result, "      % 2zd: %zd\n", count, initial);
+    }
+
+    result.append("\n    Final counts:\n");
+    for (size_t count = 1; count < maxLayerCount; ++count) {
+        size_t final = mFinalLayerCounts.count(count) > 0 ? mFinalLayerCounts.at(count) : 0;
+        base::StringAppendF(&result, "      % 2zd: %zd\n", count, final);
+    }
+
+    base::StringAppendF(&result, "\n    Cached sets created: %zd\n", mCachedSetCreationCount);
+    base::StringAppendF(&result, "    Cost: %.2f\n",
+                        static_cast<float>(mCachedSetCreationCost) / displayArea);
+
+    const auto lastUpdate =
+            std::chrono::duration_cast<std::chrono::milliseconds>(now - mLastGeometryUpdate);
+    base::StringAppendF(&result, "\n  Current hash %016zx, last update %sago\n\n", mCurrentGeometry,
+                        durationString(lastUpdate).c_str());
+
+    result.append("  Current layers:");
+    for (const CachedSet& layer : mLayers) {
+        result.append("\n");
+        layer.dump(result);
+    }
+}
+
+size_t Flattener::calculateDisplayCost(const std::vector<const LayerState*>& layers) const {
+    Region coveredRegion;
+    size_t displayCost = 0;
+    bool hasClientComposition = false;
+
+    for (const LayerState* layer : layers) {
+        coveredRegion.orSelf(layer->getDisplayFrame());
+
+        // Regardless of composition type, we always have to read each input once
+        displayCost += static_cast<size_t>(layer->getDisplayFrame().width() *
+                                           layer->getDisplayFrame().height());
+
+        hasClientComposition |= layer->getCompositionType() == hal::Composition::CLIENT;
+    }
+
+    if (hasClientComposition) {
+        // If there is client composition, the client target buffer has to be both written by the
+        // GPU and read by the DPU, so we pay its cost twice
+        displayCost += 2 *
+                static_cast<size_t>(coveredRegion.bounds().width() *
+                                    coveredRegion.bounds().height());
+    }
+
+    return displayCost;
+}
+
+void Flattener::resetActivities(NonBufferHash hash, time_point now) {
+    ALOGV("[%s]", __func__);
+
+    mCurrentGeometry = hash;
+    mLastGeometryUpdate = now;
+
+    for (const CachedSet& cachedSet : mLayers) {
+        if (cachedSet.getLayerCount() > 1) {
+            ++mInvalidatedCachedSetAges[cachedSet.getAge()];
+        }
+    }
+
+    mLayers.clear();
+
+    if (mNewCachedSet) {
+        ++mInvalidatedCachedSetAges[mNewCachedSet->getAge()];
+        mNewCachedSet = std::nullopt;
+    }
+}
+
+void Flattener::updateLayersHash() {
+    size_t hash = 0;
+    for (const auto& layer : mLayers) {
+        android::hashCombineSingleHashed(hash, layer.getNonBufferHash());
+    }
+    mLayersHash = hash;
+}
+
+bool Flattener::mergeWithCachedSets(const std::vector<const LayerState*>& layers, time_point now) {
+    std::vector<CachedSet> merged;
+
+    if (mLayers.empty()) {
+        merged.reserve(layers.size());
+        for (const LayerState* layer : layers) {
+            merged.emplace_back(layer, now);
+            mFlattenedDisplayCost += merged.back().getDisplayCost();
+        }
+        mLayers = std::move(merged);
+        return false;
+    }
+
+    ALOGV("[%s] Incoming layers:", __func__);
+    for (const LayerState* layer : layers) {
+        ALOGV("%s", layer->getName().c_str());
+    }
+
+    ALOGV("[%s] Current layers:", __func__);
+    for (const CachedSet& layer : mLayers) {
+        std::string dump;
+        layer.dump(dump);
+        ALOGV("%s", dump.c_str());
+    }
+
+    auto currentLayerIter = mLayers.begin();
+    auto incomingLayerIter = layers.begin();
+    while (incomingLayerIter != layers.end()) {
+        if (mNewCachedSet &&
+            mNewCachedSet->getFingerprint() ==
+                    (*incomingLayerIter)->getHash(LayerStateField::Buffer)) {
+            if (mNewCachedSet->hasBufferUpdate(incomingLayerIter)) {
+                ALOGV("[%s] Dropping new cached set", __func__);
+                ++mInvalidatedCachedSetAges[0];
+                mNewCachedSet = std::nullopt;
+            } else if (mNewCachedSet->hasReadyBuffer()) {
+                ALOGV("[%s] Found ready buffer", __func__);
+                size_t skipCount = mNewCachedSet->getLayerCount();
+                while (skipCount != 0) {
+                    const size_t layerCount = currentLayerIter->getLayerCount();
+                    for (size_t i = 0; i < layerCount; ++i) {
+                        OutputLayer::CompositionState& state =
+                                (*incomingLayerIter)->getOutputLayer()->editState();
+                        state.overrideInfo = {
+                                .buffer = mNewCachedSet->getBuffer(),
+                                .acquireFence = mNewCachedSet->getDrawFence(),
+                                .displayFrame = mNewCachedSet->getBounds(),
+                        };
+                        ++incomingLayerIter;
+                    }
+
+                    if (currentLayerIter->getLayerCount() > 1) {
+                        ++mInvalidatedCachedSetAges[currentLayerIter->getAge()];
+                    }
+                    ++currentLayerIter;
+
+                    skipCount -= layerCount;
+                }
+                merged.emplace_back(std::move(*mNewCachedSet));
+                mNewCachedSet = std::nullopt;
+                continue;
+            }
+        }
+
+        if (!currentLayerIter->hasBufferUpdate(incomingLayerIter)) {
+            currentLayerIter->incrementAge();
+            merged.emplace_back(*currentLayerIter);
+
+            // Skip the incoming layers corresponding to this valid current layer
+            const size_t layerCount = currentLayerIter->getLayerCount();
+            for (size_t i = 0; i < layerCount; ++i) {
+                OutputLayer::CompositionState& state =
+                        (*incomingLayerIter)->getOutputLayer()->editState();
+                state.overrideInfo = {
+                        .buffer = currentLayerIter->getBuffer(),
+                        .acquireFence = currentLayerIter->getDrawFence(),
+                        .displayFrame = currentLayerIter->getBounds(),
+                };
+                ++incomingLayerIter;
+            }
+        } else if (currentLayerIter->getLayerCount() > 1) {
+            // Break the current layer into its constituent layers
+            ++mInvalidatedCachedSetAges[currentLayerIter->getAge()];
+            for (CachedSet& layer : currentLayerIter->decompose()) {
+                layer.updateAge(now);
+                merged.emplace_back(layer);
+                ++incomingLayerIter;
+            }
+        } else {
+            currentLayerIter->updateAge(now);
+            merged.emplace_back(*currentLayerIter);
+            ++incomingLayerIter;
+        }
+        ++currentLayerIter;
+    }
+
+    for (const CachedSet& layer : merged) {
+        mFlattenedDisplayCost += layer.getDisplayCost();
+    }
+
+    mLayers = std::move(merged);
+    updateLayersHash();
+    return true;
+}
+
+void Flattener::buildCachedSets(time_point now) {
+    struct Run {
+        Run(std::vector<CachedSet>::const_iterator start, size_t length)
+              : start(start), length(length) {}
+
+        std::vector<CachedSet>::const_iterator start;
+        size_t length;
+    };
+
+    if (mLayers.empty()) {
+        ALOGV("[%s] No layers found, returning", __func__);
+        return;
+    }
+
+    std::vector<Run> runs;
+    bool isPartOfRun = false;
+    for (auto currentSet = mLayers.cbegin(); currentSet != mLayers.cend(); ++currentSet) {
+        if (now - currentSet->getLastUpdate() > kActiveLayerTimeout) {
+            // Layer is inactive
+            if (isPartOfRun) {
+                runs.back().length += currentSet->getLayerCount();
+            } else {
+                // Runs can't start with a non-buffer layer
+                if (currentSet->getFirstLayer().getBuffer() == nullptr) {
+                    ALOGV("[%s] Skipping initial non-buffer layer", __func__);
+                } else {
+                    runs.emplace_back(currentSet, currentSet->getLayerCount());
+                    isPartOfRun = true;
+                }
+            }
+        } else {
+            // Runs must be at least 2 sets long or there's nothing to combine
+            if (isPartOfRun && runs.back().start->getLayerCount() == runs.back().length) {
+                runs.pop_back();
+            }
+
+            isPartOfRun = false;
+        }
+    }
+
+    // Check for at least 2 sets one more time in case the set includes the last layer
+    if (isPartOfRun && runs.back().start->getLayerCount() == runs.back().length) {
+        runs.pop_back();
+    }
+
+    ALOGV("[%s] Found %zu candidate runs", __func__, runs.size());
+
+    if (runs.empty()) {
+        return;
+    }
+
+    mNewCachedSet.emplace(*runs[0].start);
+    mNewCachedSet->setLastUpdate(now);
+    auto currentSet = runs[0].start;
+    while (mNewCachedSet->getLayerCount() < runs[0].length) {
+        ++currentSet;
+        mNewCachedSet->append(*currentSet);
+    }
+
+    // TODO(b/181192467): Actually compute new LayerState vector and corresponding hash for each run
+    mPredictor.getPredictedPlan({}, 0);
+
+    ++mCachedSetCreationCount;
+    mCachedSetCreationCost += mNewCachedSet->getCreationCost();
+    std::string setDump;
+    mNewCachedSet->dump(setDump);
+    ALOGV("[%s] Added new cached set:\n%s", __func__, setDump.c_str());
+}
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
new file mode 100644
index 0000000..7cf4819
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
@@ -0,0 +1,158 @@
+/*
+ * 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 <compositionengine/impl/planner/LayerState.h>
+
+namespace android::compositionengine::impl::planner {
+
+LayerState::LayerState(compositionengine::OutputLayer* layer) : mOutputLayer(layer) {
+    update(layer);
+}
+
+Flags<LayerStateField> LayerState::update(compositionengine::OutputLayer* layer) {
+    ALOGE_IF(layer != mOutputLayer, "[%s] Expected mOutputLayer to never change", __func__);
+
+    Flags<LayerStateField> differences;
+
+    // Update the unique fields as well, since we have to set them at least
+    // once from the OutputLayer
+    differences |= mId.update(layer);
+    differences |= mName.update(layer);
+
+    for (StateInterface* field : getNonUniqueFields()) {
+        differences |= field->update(layer);
+    }
+
+    return differences;
+}
+
+size_t LayerState::getHash(
+        Flags<LayerStateField> skipFields = static_cast<LayerStateField>(0)) const {
+    size_t hash = 0;
+    for (const StateInterface* field : getNonUniqueFields()) {
+        android::hashCombineSingleHashed(hash, field->getHash(skipFields));
+    }
+
+    return hash;
+}
+
+Flags<LayerStateField> LayerState::getDifferingFields(
+        const LayerState& other,
+        Flags<LayerStateField> skipFields = static_cast<LayerStateField>(0)) const {
+    Flags<LayerStateField> differences;
+    auto myFields = getNonUniqueFields();
+    auto otherFields = other.getNonUniqueFields();
+    for (size_t i = 0; i < myFields.size(); ++i) {
+        if (skipFields.test(myFields[i]->getField())) {
+            continue;
+        }
+
+        differences |= myFields[i]->getFieldIfDifferent(otherFields[i]);
+    }
+
+    return differences;
+}
+
+void LayerState::dump(std::string& result) const {
+    for (const StateInterface* field : getNonUniqueFields()) {
+        if (auto viewOpt = flag_name(field->getField()); viewOpt) {
+            base::StringAppendF(&result, "  %16s: ", std::string(*viewOpt).c_str());
+        } else {
+            result.append("<UNKNOWN FIELD>:\n");
+        }
+
+        bool first = true;
+        for (const std::string& line : field->toStrings()) {
+            base::StringAppendF(&result, "%s%s\n", first ? "" : "                    ",
+                                line.c_str());
+            first = false;
+        }
+    }
+    result.append("\n");
+}
+
+std::optional<std::string> LayerState::compare(const LayerState& other) const {
+    std::string result;
+
+    const auto& thisFields = getNonUniqueFields();
+    const auto& otherFields = other.getNonUniqueFields();
+    for (size_t f = 0; f < thisFields.size(); ++f) {
+        const auto& thisField = thisFields[f];
+        const auto& otherField = otherFields[f];
+        // Skip comparing buffers
+        if (thisField->getField() == LayerStateField::Buffer) {
+            continue;
+        }
+
+        if (thisField->equals(otherField)) {
+            continue;
+        }
+
+        if (auto viewOpt = flag_name(thisField->getField()); viewOpt) {
+            base::StringAppendF(&result, "  %16s: ", std::string(*viewOpt).c_str());
+        } else {
+            result.append("<UNKNOWN FIELD>:\n");
+        }
+
+        const auto& thisStrings = thisField->toStrings();
+        const auto& otherStrings = otherField->toStrings();
+        bool first = true;
+        for (size_t line = 0; line < std::max(thisStrings.size(), otherStrings.size()); ++line) {
+            if (!first) {
+                result.append("                    ");
+            }
+            first = false;
+
+            if (line < thisStrings.size()) {
+                base::StringAppendF(&result, "%-48.48s", thisStrings[line].c_str());
+            } else {
+                result.append("                                                ");
+            }
+
+            if (line < otherStrings.size()) {
+                base::StringAppendF(&result, "%-48.48s", otherStrings[line].c_str());
+            } else {
+                result.append("                                                ");
+            }
+            result.append("\n");
+        }
+    }
+
+    return result.empty() ? std::nullopt : std::make_optional(result);
+}
+
+bool operator==(const LayerState& lhs, const LayerState& rhs) {
+    return lhs.mId == rhs.mId && lhs.mName == rhs.mName && lhs.mDisplayFrame == rhs.mDisplayFrame &&
+            lhs.mSourceCrop == rhs.mSourceCrop && lhs.mZOrder == rhs.mZOrder &&
+            lhs.mBufferTransform == rhs.mBufferTransform && lhs.mBlendMode == rhs.mBlendMode &&
+            lhs.mAlpha == rhs.mAlpha && lhs.mVisibleRegion == rhs.mVisibleRegion &&
+            lhs.mDataspace == rhs.mDataspace && lhs.mColorTransform == rhs.mColorTransform &&
+            lhs.mCompositionType == rhs.mCompositionType &&
+            lhs.mSidebandStream == rhs.mSidebandStream && lhs.mBuffer == rhs.mBuffer &&
+            (lhs.mCompositionType.get() != hal::Composition::SOLID_COLOR ||
+             lhs.mSolidColor == rhs.mSolidColor);
+}
+
+NonBufferHash getNonBufferHash(const std::vector<const LayerState*>& layers) {
+    size_t hash = 0;
+    for (const auto layer : layers) {
+        android::hashCombineSingleHashed(hash, layer->getHash(LayerStateField::Buffer));
+    }
+
+    return hash;
+}
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
new file mode 100644
index 0000000..52efff5
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -0,0 +1,261 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+
+#undef LOG_TAG
+#define LOG_TAG "Planner"
+
+#include <compositionengine/LayerFECompositionState.h>
+#include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <compositionengine/impl/planner/Planner.h>
+
+namespace android::compositionengine::impl::planner {
+
+void Planner::setDisplaySize(ui::Size size) {
+    mFlattener.setDisplaySize(size);
+}
+
+void Planner::plan(
+        compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) {
+    std::unordered_set<LayerId> removedLayers;
+    removedLayers.reserve(mPreviousLayers.size());
+
+    std::transform(mPreviousLayers.begin(), mPreviousLayers.end(),
+                   std::inserter(removedLayers, removedLayers.begin()),
+                   [](const auto& layer) { return layer.first; });
+
+    std::vector<LayerId> currentLayerIds;
+    for (auto layer : layers) {
+        LayerId id = layer->getLayerFE().getSequence();
+        if (const auto layerEntry = mPreviousLayers.find(id); layerEntry != mPreviousLayers.end()) {
+            // Track changes from previous info
+            LayerState& state = layerEntry->second;
+            Flags<LayerStateField> differences = state.update(layer);
+            if (differences.get() == 0) {
+                state.incrementFramesSinceBufferUpdate();
+            } else {
+                ALOGV("Layer %s changed: %s", state.getName().c_str(),
+                      differences.string().c_str());
+
+                if (differences.test(LayerStateField::Buffer)) {
+                    state.resetFramesSinceBufferUpdate();
+                } else {
+                    state.incrementFramesSinceBufferUpdate();
+                }
+            }
+        } else {
+            LayerState state(layer);
+            ALOGV("Added layer %s", state.getName().c_str());
+            mPreviousLayers.emplace(std::make_pair(id, std::move(state)));
+        }
+
+        currentLayerIds.emplace_back(id);
+
+        if (const auto found = removedLayers.find(id); found != removedLayers.end()) {
+            removedLayers.erase(found);
+        }
+    }
+
+    for (LayerId removedLayer : removedLayers) {
+        if (const auto layerEntry = mPreviousLayers.find(removedLayer);
+            layerEntry != mPreviousLayers.end()) {
+            const auto& [id, state] = *layerEntry;
+            ALOGV("Removed layer %s", state.getName().c_str());
+            mPreviousLayers.erase(removedLayer);
+        }
+    }
+
+    mCurrentLayers.clear();
+    mCurrentLayers.reserve(currentLayerIds.size());
+    std::transform(currentLayerIds.cbegin(), currentLayerIds.cend(),
+                   std::back_inserter(mCurrentLayers), [this](LayerId id) {
+                       LayerState* state = &mPreviousLayers.at(id);
+                       state->getOutputLayer()->editState().overrideInfo = {};
+                       return state;
+                   });
+
+    const NonBufferHash hash = getNonBufferHash(mCurrentLayers);
+    mFlattenedHash = mFlattener.flattenLayers(mCurrentLayers, hash);
+    const bool layersWereFlattened = hash != mFlattenedHash;
+    ALOGV("[%s] Initial hash %zx flattened hash %zx", __func__, hash, mFlattenedHash);
+
+    mPredictedPlan =
+            mPredictor.getPredictedPlan(layersWereFlattened ? std::vector<const LayerState*>()
+                                                            : mCurrentLayers,
+                                        mFlattenedHash);
+    if (mPredictedPlan) {
+        ALOGV("[%s] Predicting plan %s", __func__, to_string(mPredictedPlan->plan).c_str());
+    } else {
+        ALOGV("[%s] No prediction found\n", __func__);
+    }
+}
+
+void Planner::reportFinalPlan(
+        compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) {
+    Plan finalPlan;
+    const GraphicBuffer* currentOverrideBuffer = nullptr;
+    bool hasSkippedLayers = false;
+    for (auto layer : layers) {
+        const GraphicBuffer* overrideBuffer = layer->getState().overrideInfo.buffer.get();
+        if (overrideBuffer != nullptr && overrideBuffer == currentOverrideBuffer) {
+            // Skip this layer since it is part of a previous cached set
+            hasSkippedLayers = true;
+            continue;
+        }
+
+        currentOverrideBuffer = overrideBuffer;
+
+        const bool forcedOrRequestedClient =
+                layer->getState().forceClientComposition || layer->requiresClientComposition();
+
+        finalPlan.addLayerType(
+                forcedOrRequestedClient
+                        ? hardware::graphics::composer::hal::Composition::CLIENT
+                        : layer->getLayerFE().getCompositionState()->compositionType);
+    }
+
+    mPredictor.recordResult(mPredictedPlan, mFlattenedHash, mCurrentLayers, hasSkippedLayers,
+                            finalPlan);
+}
+
+void Planner::renderCachedSets(renderengine::RenderEngine& renderEngine) {
+    mFlattener.renderCachedSets(renderEngine);
+}
+
+void Planner::dump(const Vector<String16>& args, std::string& result) {
+    if (args.size() > 1) {
+        const String8 command(args[1]);
+        if (command == "--compare" || command == "-c") {
+            if (args.size() < 4) {
+                base::StringAppendF(&result,
+                                    "Expected two layer stack hashes, e.g. '--planner %s "
+                                    "<left_hash> <right_hash>'\n",
+                                    command.string());
+                return;
+            }
+            if (args.size() > 4) {
+                base::StringAppendF(&result,
+                                    "Too many arguments found, expected '--planner %s <left_hash> "
+                                    "<right_hash>'\n",
+                                    command.string());
+                return;
+            }
+
+            const String8 leftHashString(args[2]);
+            size_t leftHash = 0;
+            int fieldsRead = sscanf(leftHashString.string(), "%zx", &leftHash);
+            if (fieldsRead != 1) {
+                base::StringAppendF(&result, "Failed to parse %s as a size_t\n",
+                                    leftHashString.string());
+                return;
+            }
+
+            const String8 rightHashString(args[3]);
+            size_t rightHash = 0;
+            fieldsRead = sscanf(rightHashString.string(), "%zx", &rightHash);
+            if (fieldsRead != 1) {
+                base::StringAppendF(&result, "Failed to parse %s as a size_t\n",
+                                    rightHashString.string());
+                return;
+            }
+
+            mPredictor.compareLayerStacks(leftHash, rightHash, result);
+        } else if (command == "--describe" || command == "-d") {
+            if (args.size() < 3) {
+                base::StringAppendF(&result,
+                                    "Expected a layer stack hash, e.g. '--planner %s <hash>'\n",
+                                    command.string());
+                return;
+            }
+            if (args.size() > 3) {
+                base::StringAppendF(&result,
+                                    "Too many arguments found, expected '--planner %s <hash>'\n",
+                                    command.string());
+                return;
+            }
+
+            const String8 hashString(args[2]);
+            size_t hash = 0;
+            const int fieldsRead = sscanf(hashString.string(), "%zx", &hash);
+            if (fieldsRead != 1) {
+                base::StringAppendF(&result, "Failed to parse %s as a size_t\n",
+                                    hashString.string());
+                return;
+            }
+
+            mPredictor.describeLayerStack(hash, result);
+        } else if (command == "--help" || command == "-h") {
+            dumpUsage(result);
+        } else if (command == "--similar" || command == "-s") {
+            if (args.size() < 3) {
+                base::StringAppendF(&result, "Expected a plan string, e.g. '--planner %s <plan>'\n",
+                                    command.string());
+                return;
+            }
+            if (args.size() > 3) {
+                base::StringAppendF(&result,
+                                    "Too many arguments found, expected '--planner %s <plan>'\n",
+                                    command.string());
+                return;
+            }
+
+            const String8 planString(args[2]);
+            std::optional<Plan> plan = Plan::fromString(std::string(planString.string()));
+            if (!plan) {
+                base::StringAppendF(&result, "Failed to parse %s as a Plan\n", planString.string());
+                return;
+            }
+
+            mPredictor.listSimilarStacks(*plan, result);
+        } else {
+            base::StringAppendF(&result, "Unknown command '%s'\n\n", command.string());
+            dumpUsage(result);
+        }
+        return;
+    }
+
+    // If there are no specific commands, dump the usual state
+
+    mFlattener.dump(result);
+    result.append("\n");
+
+    mPredictor.dump(result);
+}
+
+void Planner::dumpUsage(std::string& result) const {
+    result.append("Planner command line interface usage\n");
+    result.append("  dumpsys SurfaceFlinger --planner <command> [arguments]\n\n");
+
+    result.append("If run without a command, dumps current Planner state\n\n");
+
+    result.append("Commands:\n");
+
+    result.append("[--compare|-c] <left_hash> <right_hash>\n");
+    result.append("  Compares the predictions <left_hash> and <right_hash> by showing differences"
+                  " in their example layer stacks\n");
+
+    result.append("[--describe|-d] <hash>\n");
+    result.append("  Prints the example layer stack and prediction statistics for <hash>\n");
+
+    result.append("[--help|-h]\n");
+    result.append("  Shows this message\n");
+
+    result.append("[--similar|-s] <plan>\n");
+    result.append("  Prints the example layer names for similar stacks matching <plan>\n");
+}
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
new file mode 100644
index 0000000..ba5e64d
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
@@ -0,0 +1,479 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+
+#undef LOG_TAG
+#define LOG_TAG "Planner"
+
+#include <compositionengine/impl/planner/Predictor.h>
+
+namespace android::compositionengine::impl::planner {
+
+std::optional<LayerStack::ApproximateMatch> LayerStack::getApproximateMatch(
+        const std::vector<const LayerState*>& other) const {
+    // Differing numbers of layers are never an approximate match
+    if (mLayers.size() != other.size()) {
+        return std::nullopt;
+    }
+
+    std::optional<ApproximateMatch> approximateMatch = {};
+    for (size_t i = 0; i < mLayers.size(); ++i) {
+        // Skip identical layers
+        if (mLayers[i].getHash(LayerStateField::Buffer) ==
+            other[i]->getHash(LayerStateField::Buffer)) {
+            continue;
+        }
+
+        // Skip layers where both are client-composited, since that doesn't change the
+        // composition plan
+        if (mLayers[i].getCompositionType() == hal::Composition::CLIENT &&
+            other[i]->getCompositionType() == hal::Composition::CLIENT) {
+            continue;
+        }
+
+        // If layers differ in composition type, their stacks are too different
+        if (mLayers[i].getCompositionType() != other[i]->getCompositionType()) {
+            return std::nullopt;
+        }
+
+        // If layers are not identical, but we already have a prior approximate match,
+        // the LayerStacks differ by too much, so return nothing
+        if (approximateMatch) {
+            return std::nullopt;
+        }
+
+        Flags<LayerStateField> differingFields =
+                mLayers[i].getDifferingFields(*other[i], LayerStateField::Buffer);
+
+        // If we don't find an approximate match on this layer, then the LayerStacks differ
+        // by too much, so return nothing
+        const int differingFieldCount = __builtin_popcount(differingFields.get());
+        if (differingFieldCount <= kMaxDifferingFields) {
+            approximateMatch = ApproximateMatch{
+                    .differingIndex = i,
+                    .differingFields = differingFields,
+            };
+        } else {
+            return std::nullopt;
+        }
+    }
+
+    // If we make it through the layer-by-layer comparison without an approximate match,
+    // it means that all layers were either identical or had client-composited layers in common,
+    // which don't affect the composition strategy, so return a successful result with
+    // no differences.
+    return ApproximateMatch{
+            .differingIndex = 0,
+            .differingFields = {},
+    };
+}
+
+std::optional<Plan> Plan::fromString(const std::string& string) {
+    Plan plan;
+    for (char c : string) {
+        switch (c) {
+            case 'C':
+                plan.addLayerType(hal::Composition::CLIENT);
+                continue;
+            case 'U':
+                plan.addLayerType(hal::Composition::CURSOR);
+                continue;
+            case 'D':
+                plan.addLayerType(hal::Composition::DEVICE);
+                continue;
+            case 'I':
+                plan.addLayerType(hal::Composition::INVALID);
+                continue;
+            case 'B':
+                plan.addLayerType(hal::Composition::SIDEBAND);
+                continue;
+            case 'S':
+                plan.addLayerType(hal::Composition::SOLID_COLOR);
+                continue;
+            default:
+                return std::nullopt;
+        }
+    }
+    return plan;
+}
+
+std::string to_string(const Plan& plan) {
+    std::string result;
+    for (auto type : plan.mLayerTypes) {
+        switch (type) {
+            case hal::Composition::CLIENT:
+                result.append("C");
+                break;
+            case hal::Composition::CURSOR:
+                result.append("U");
+                break;
+            case hal::Composition::DEVICE:
+                result.append("D");
+                break;
+            case hal::Composition::INVALID:
+                result.append("I");
+                break;
+            case hal::Composition::SIDEBAND:
+                result.append("B");
+                break;
+            case hal::Composition::SOLID_COLOR:
+                result.append("S");
+                break;
+        }
+    }
+    return result;
+}
+
+void Prediction::dump(std::string& result) const {
+    result.append(to_string(mPlan));
+    result.append(" [Exact ");
+    mExactStats.dump(result);
+    result.append("] [Approximate ");
+    mApproximateStats.dump(result);
+    result.append("]");
+}
+
+std::optional<Predictor::PredictedPlan> Predictor::getPredictedPlan(
+        const std::vector<const LayerState*>& layers, NonBufferHash hash) const {
+    // First check for an exact match
+    if (std::optional<Plan> exactMatch = getExactMatch(hash); exactMatch) {
+        ALOGV("[%s] Found an exact match for %zx", __func__, hash);
+        return PredictedPlan{.hash = hash, .plan = *exactMatch, .type = Prediction::Type::Exact};
+    }
+
+    // If only a hash was passed in for a layer stack with a cached set, don't perform
+    // approximate matches and return early
+    if (layers.empty()) {
+        ALOGV("[%s] Only hash was passed, but no exact match was found", __func__);
+        return std::nullopt;
+    }
+
+    // Then check for approximate matches
+    if (std::optional<NonBufferHash> approximateMatch = getApproximateMatch(layers);
+        approximateMatch) {
+        ALOGV("[%s] Found an approximate match for %zx", __func__, *approximateMatch);
+        const Prediction& prediction = getPrediction(*approximateMatch);
+        return PredictedPlan{.hash = *approximateMatch,
+                             .plan = prediction.getPlan(),
+                             .type = Prediction::Type::Approximate};
+    }
+
+    return std::nullopt;
+}
+
+void Predictor::recordResult(std::optional<PredictedPlan> predictedPlan,
+                             NonBufferHash flattenedHash,
+                             const std::vector<const LayerState*>& layers, bool hasSkippedLayers,
+                             Plan result) {
+    if (predictedPlan) {
+        recordPredictedResult(*predictedPlan, layers, std::move(result));
+        return;
+    }
+
+    ++mMissCount;
+
+    if (!hasSkippedLayers && findSimilarPrediction(layers, result)) {
+        return;
+    }
+
+    ALOGV("[%s] Adding novel candidate %zx", __func__, flattenedHash);
+    mCandidates.emplace_front(flattenedHash, Prediction(layers, result));
+    if (mCandidates.size() > MAX_CANDIDATES) {
+        mCandidates.pop_back();
+    }
+}
+
+void Predictor::dump(std::string& result) const {
+    result.append("Predictor state:\n");
+
+    const size_t hitCount = mExactHitCount + mApproximateHitCount;
+    const size_t totalAttempts = hitCount + mMissCount;
+    base::StringAppendF(&result, "Global non-skipped hit rate: %.2f%% (%zd/%zd)\n",
+                        100.0f * hitCount / totalAttempts, hitCount, totalAttempts);
+    base::StringAppendF(&result, "  Exact hits: %zd\n", mExactHitCount);
+    base::StringAppendF(&result, "  Approximate hits: %zd\n", mApproximateHitCount);
+    base::StringAppendF(&result, "  Misses: %zd\n\n", mMissCount);
+
+    dumpPredictionsByFrequency(result);
+}
+
+void Predictor::compareLayerStacks(NonBufferHash leftHash, NonBufferHash rightHash,
+                                   std::string& result) const {
+    const auto& [leftPredictionEntry, rightPredictionEntry] =
+            std::make_tuple(mPredictions.find(leftHash), mPredictions.find(rightHash));
+    if (leftPredictionEntry == mPredictions.end()) {
+        base::StringAppendF(&result, "No prediction found for %zx\n", leftHash);
+        return;
+    }
+    if (rightPredictionEntry == mPredictions.end()) {
+        base::StringAppendF(&result, "No prediction found for %zx\n", rightHash);
+        return;
+    }
+
+    base::StringAppendF(&result,
+                        "Comparing           %-16zx                                %-16zx\n",
+                        leftHash, rightHash);
+
+    const auto& [leftPrediction, rightPrediction] =
+            std::make_tuple(leftPredictionEntry->second, rightPredictionEntry->second);
+    const auto& [leftStack, rightStack] = std::make_tuple(leftPrediction.getExampleLayerStack(),
+                                                          rightPrediction.getExampleLayerStack());
+    leftStack.compare(rightStack, result);
+}
+
+void Predictor::describeLayerStack(NonBufferHash hash, std::string& result) const {
+    base::StringAppendF(&result, "Describing %zx:\n\n", hash);
+
+    if (const auto predictionsEntry = mPredictions.find(hash);
+        predictionsEntry != mPredictions.cend()) {
+        const auto& [hash, prediction] = *predictionsEntry;
+
+        prediction.getExampleLayerStack().dump(result);
+
+        result.append("Prediction: ");
+        prediction.dump(result);
+        result.append("\n");
+    } else {
+        result.append("No predictions found\n");
+    }
+}
+
+void Predictor::listSimilarStacks(Plan plan, std::string& result) const {
+    base::StringAppendF(&result, "Similar stacks for plan %s:\n", to_string(plan).c_str());
+
+    if (const auto similarStacksEntry = mSimilarStacks.find(plan);
+        similarStacksEntry != mSimilarStacks.end()) {
+        const auto& [_, similarStacks] = *similarStacksEntry;
+        for (NonBufferHash hash : similarStacks) {
+            base::StringAppendF(&result, "\nPrediction hash %zx:\n", hash);
+            const Prediction& prediction = mPredictions.at(hash);
+            prediction.getExampleLayerStack().dumpLayerNames(result);
+        }
+    } else {
+        result.append("No similar stacks found\n");
+    }
+}
+
+const Prediction& Predictor::getPrediction(NonBufferHash hash) const {
+    if (const auto predictionEntry = mPredictions.find(hash);
+        predictionEntry != mPredictions.end()) {
+        const auto& [_, prediction] = *predictionEntry;
+        return prediction;
+    } else {
+        const auto candidateEntry = getCandidateEntryByHash(hash);
+        ALOGE_IF(candidateEntry == mCandidates.cend(),
+                 "Hash should have been found in either predictions or candidates");
+        const auto& [_, prediction] = *candidateEntry;
+        return prediction;
+    }
+}
+
+Prediction& Predictor::getPrediction(NonBufferHash hash) {
+    return const_cast<Prediction&>(const_cast<const Predictor*>(this)->getPrediction(hash));
+}
+
+std::optional<Plan> Predictor::getExactMatch(NonBufferHash hash) const {
+    const Prediction* match = nullptr;
+    if (const auto predictionEntry = mPredictions.find(hash);
+        predictionEntry != mPredictions.end()) {
+        const auto& [hash, prediction] = *predictionEntry;
+        match = &prediction;
+    } else if (const auto candidateEntry = getCandidateEntryByHash(hash);
+               candidateEntry != mCandidates.cend()) {
+        match = &(candidateEntry->prediction);
+    }
+
+    if (match == nullptr) {
+        return std::nullopt;
+    }
+
+    if (match->getMissCount(Prediction::Type::Exact) != 0) {
+        ALOGV("[%s] Skipping exact match for %zx because of prior miss", __func__, hash);
+        return std::nullopt;
+    }
+
+    return match->getPlan();
+}
+
+std::optional<NonBufferHash> Predictor::getApproximateMatch(
+        const std::vector<const LayerState*>& layers) const {
+    const auto approximateStackMatches = [&](const ApproximateStack& approximateStack) {
+        const auto& exampleStack = mPredictions.at(approximateStack.hash).getExampleLayerStack();
+        if (const auto approximateMatchOpt = exampleStack.getApproximateMatch(layers);
+            approximateMatchOpt) {
+            return *approximateMatchOpt == approximateStack.match;
+        }
+        return false;
+    };
+
+    const auto candidateMatches = [&](const PromotionCandidate& candidate) {
+        ALOGV("[getApproximateMatch] checking against %zx", candidate.hash);
+        return candidate.prediction.getExampleLayerStack().getApproximateMatch(layers) !=
+                std::nullopt;
+    };
+
+    const Prediction* match = nullptr;
+    NonBufferHash hash;
+    if (const auto approximateStackIter =
+                std::find_if(mApproximateStacks.cbegin(), mApproximateStacks.cend(),
+                             approximateStackMatches);
+        approximateStackIter != mApproximateStacks.cend()) {
+        match = &mPredictions.at(approximateStackIter->hash);
+        hash = approximateStackIter->hash;
+    } else if (const auto candidateEntry =
+                       std::find_if(mCandidates.cbegin(), mCandidates.cend(), candidateMatches);
+               candidateEntry != mCandidates.cend()) {
+        match = &(candidateEntry->prediction);
+        hash = candidateEntry->hash;
+    }
+
+    if (match == nullptr) {
+        return std::nullopt;
+    }
+
+    if (match->getMissCount(Prediction::Type::Approximate) != 0) {
+        ALOGV("[%s] Skipping approximate match for %zx because of prior miss", __func__, hash);
+        return std::nullopt;
+    }
+
+    return hash;
+}
+
+void Predictor::promoteIfCandidate(NonBufferHash predictionHash) {
+    // Return if the candidate has already been promoted
+    if (mPredictions.count(predictionHash) != 0) {
+        return;
+    }
+
+    ALOGV("[%s] Promoting %zx from candidate to prediction", __func__, predictionHash);
+
+    auto candidateEntry = getCandidateEntryByHash(predictionHash);
+    ALOGE_IF(candidateEntry == mCandidates.end(), "Expected to find candidate");
+
+    mSimilarStacks[candidateEntry->prediction.getPlan()].push_back(predictionHash);
+    mPredictions.emplace(predictionHash, std::move(candidateEntry->prediction));
+    mCandidates.erase(candidateEntry);
+}
+
+void Predictor::recordPredictedResult(PredictedPlan predictedPlan,
+                                      const std::vector<const LayerState*>& layers, Plan result) {
+    Prediction& prediction = getPrediction(predictedPlan.hash);
+    if (prediction.getPlan() != result) {
+        ALOGV("[%s] %s prediction missed, expected %s, found %s", __func__,
+              to_string(predictedPlan.type).c_str(), to_string(prediction.getPlan()).c_str(),
+              to_string(result).c_str());
+        prediction.recordMiss(predictedPlan.type);
+        ++mMissCount;
+        return;
+    }
+
+    switch (predictedPlan.type) {
+        case Prediction::Type::Approximate:
+            ++mApproximateHitCount;
+            break;
+        case Prediction::Type::Exact:
+            ++mExactHitCount;
+            break;
+        default:
+            break;
+    }
+
+    ALOGV("[%s] %s prediction hit", __func__, to_string(predictedPlan.type).c_str());
+    ALOGV("[%s] Plan: %s", __func__, to_string(result).c_str());
+    prediction.recordHit(predictedPlan.type);
+
+    const auto stackMatchesHash = [hash = predictedPlan.hash](const ApproximateStack& stack) {
+        return stack.hash == hash;
+    };
+
+    if (predictedPlan.type == Prediction::Type::Approximate) {
+        // If this approximate match is not already in the list of approximate stacks, add it
+        if (std::find_if(mApproximateStacks.cbegin(), mApproximateStacks.cend(),
+                         stackMatchesHash) == mApproximateStacks.cend()) {
+            ALOGV("[%s] Adding approximate match to list", __func__);
+            const auto approximateMatchOpt =
+                    prediction.getExampleLayerStack().getApproximateMatch(layers);
+            ALOGE_IF(!approximateMatchOpt, "Expected an approximate match");
+            mApproximateStacks.emplace_back(predictedPlan.hash, *approximateMatchOpt);
+        }
+    }
+
+    promoteIfCandidate(predictedPlan.hash);
+}
+
+bool Predictor::findSimilarPrediction(const std::vector<const LayerState*>& layers, Plan result) {
+    const auto stacksEntry = mSimilarStacks.find(result);
+    if (stacksEntry == mSimilarStacks.end()) {
+        return false;
+    }
+
+    std::optional<ApproximateStack> bestMatch;
+    const auto& [plan, similarStacks] = *stacksEntry;
+    for (NonBufferHash hash : similarStacks) {
+        const Prediction& prediction = mPredictions.at(hash);
+        auto approximateMatch = prediction.getExampleLayerStack().getApproximateMatch(layers);
+        if (!approximateMatch) {
+            continue;
+        }
+
+        const int differingFieldCount = __builtin_popcount(approximateMatch->differingFields.get());
+        if (!bestMatch ||
+            differingFieldCount < __builtin_popcount(bestMatch->match.differingFields.get())) {
+            bestMatch = {hash, *approximateMatch};
+        }
+    }
+
+    if (!bestMatch) {
+        return false;
+    }
+
+    ALOGV("[%s] Adding %zx to approximate stacks", __func__, bestMatch->hash);
+
+    mApproximateStacks.emplace_back(*bestMatch);
+    return true;
+}
+
+void Predictor::dumpPredictionsByFrequency(std::string& result) const {
+    struct HashFrequency {
+        HashFrequency(NonBufferHash hash, size_t totalAttempts)
+              : hash(hash), totalAttempts(totalAttempts) {}
+
+        NonBufferHash hash;
+        size_t totalAttempts;
+    };
+
+    std::vector<HashFrequency> hashFrequencies;
+    for (const auto& [hash, prediction] : mPredictions) {
+        hashFrequencies.emplace_back(hash,
+                                     prediction.getHitCount(Prediction::Type::Total) +
+                                             prediction.getMissCount(Prediction::Type::Total));
+    }
+
+    std::sort(hashFrequencies.begin(), hashFrequencies.end(),
+              [](const HashFrequency& lhs, const HashFrequency& rhs) {
+                  return lhs.totalAttempts > rhs.totalAttempts;
+              });
+
+    result.append("Predictions:\n");
+    for (const auto& [hash, totalAttempts] : hashFrequencies) {
+        base::StringAppendF(&result, "  %016zx ", hash);
+        mPredictions.at(hash).dump(result);
+        result.append("\n");
+    }
+}
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index d5bf569..325361b 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -39,9 +39,6 @@
 using ::testing::StrictMock;
 
 struct CompositionEngineTest : public testing::Test {
-    android::mock::HWComposer* mHwc = new StrictMock<android::mock::HWComposer>();
-    renderengine::mock::RenderEngine* mRenderEngine =
-            new StrictMock<renderengine::mock::RenderEngine>();
     std::shared_ptr<TimeStats> mTimeStats;
 
     impl::CompositionEngine mEngine;
@@ -58,15 +55,18 @@
 }
 
 TEST_F(CompositionEngineTest, canSetHWComposer) {
-    mEngine.setHwComposer(std::unique_ptr<android::HWComposer>(mHwc));
+    android::mock::HWComposer* hwc = new StrictMock<android::mock::HWComposer>();
+    mEngine.setHwComposer(std::unique_ptr<android::HWComposer>(hwc));
 
-    EXPECT_EQ(mHwc, &mEngine.getHwComposer());
+    EXPECT_EQ(hwc, &mEngine.getHwComposer());
 }
 
 TEST_F(CompositionEngineTest, canSetRenderEngine) {
-    mEngine.setRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    renderengine::mock::RenderEngine* renderEngine =
+            new StrictMock<renderengine::mock::RenderEngine>();
+    mEngine.setRenderEngine(std::unique_ptr<renderengine::RenderEngine>(renderEngine));
 
-    EXPECT_EQ(mRenderEngine, &mEngine.getRenderEngine());
+    EXPECT_EQ(renderEngine, &mEngine.getRenderEngine());
 }
 
 TEST_F(CompositionEngineTest, canSetTimeStats) {
@@ -130,10 +130,10 @@
 struct CompositionEngineUpdateCursorAsyncTest : public CompositionEngineTest {
 public:
     struct Layer {
-        Layer() { EXPECT_CALL(outputLayer, getLayerFE()).WillRepeatedly(ReturnRef(layerFE)); }
+        Layer() { EXPECT_CALL(outputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*layerFE)); }
 
         StrictMock<mock::OutputLayer> outputLayer;
-        StrictMock<mock::LayerFE> layerFE;
+        sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
         LayerFECompositionState layerFEState;
     };
 
@@ -175,21 +175,21 @@
     {
         InSequence seq;
         EXPECT_CALL(mOutput2Layer1.outputLayer, isHardwareCursor()).WillRepeatedly(Return(true));
-        EXPECT_CALL(mOutput2Layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor));
+        EXPECT_CALL(*mOutput2Layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor));
         EXPECT_CALL(mOutput2Layer1.outputLayer, writeCursorPositionToHWC());
     }
 
     {
         InSequence seq;
         EXPECT_CALL(mOutput3Layer1.outputLayer, isHardwareCursor()).WillRepeatedly(Return(true));
-        EXPECT_CALL(mOutput3Layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor));
+        EXPECT_CALL(*mOutput3Layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor));
         EXPECT_CALL(mOutput3Layer1.outputLayer, writeCursorPositionToHWC());
     }
 
     {
         InSequence seq;
         EXPECT_CALL(mOutput3Layer2.outputLayer, isHardwareCursor()).WillRepeatedly(Return(true));
-        EXPECT_CALL(mOutput3Layer2.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor));
+        EXPECT_CALL(*mOutput3Layer2.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor));
         EXPECT_CALL(mOutput3Layer2.outputLayer, writeCursorPositionToHWC());
     }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 348ec39..8a83639 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -35,8 +35,8 @@
 #include <compositionengine/mock/RenderSurface.h>
 #include <gtest/gtest.h>
 #include <renderengine/mock/RenderEngine.h>
-#include <ui/DisplayInfo.h>
 #include <ui/Rect.h>
+#include <ui/StaticDisplayInfo.h>
 
 #include "MockHWC2.h"
 #include "MockHWComposer.h"
@@ -169,7 +169,7 @@
 
     DisplayCreationArgs getDisplayCreationArgsForPhysicalHWCDisplay() {
         return DisplayCreationArgsBuilder()
-                .setPhysical({DEFAULT_DISPLAY_ID, DisplayConnectionType::Internal})
+                .setPhysical({DEFAULT_DISPLAY_ID, ui::DisplayConnectionType::Internal})
                 .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
                 .setPixelFormat(static_cast<ui::PixelFormat>(PIXEL_FORMAT_RGBA_8888))
                 .setIsSecure(true)
@@ -265,7 +265,7 @@
     mDisplay->setConfiguration(
             DisplayCreationArgsBuilder()
                     .setUseHwcVirtualDisplays(true)
-                    .setPhysical({DEFAULT_DISPLAY_ID, DisplayConnectionType::Internal})
+                    .setPhysical({DEFAULT_DISPLAY_ID, ui::DisplayConnectionType::Internal})
                     .setPixels(ui::Size(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_WIDTH))
                     .setPixelFormat(static_cast<ui::PixelFormat>(PIXEL_FORMAT_RGBA_8888))
                     .setIsSecure(true)
@@ -286,7 +286,7 @@
     mDisplay->setConfiguration(
             DisplayCreationArgsBuilder()
                     .setUseHwcVirtualDisplays(true)
-                    .setPhysical({DEFAULT_DISPLAY_ID, DisplayConnectionType::External})
+                    .setPhysical({DEFAULT_DISPLAY_ID, ui::DisplayConnectionType::External})
                     .setPixels(ui::Size(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_WIDTH))
                     .setPixelFormat(static_cast<ui::PixelFormat>(PIXEL_FORMAT_RGBA_8888))
                     .setIsSecure(false)
@@ -1018,7 +1018,7 @@
     std::shared_ptr<Display> mDisplay = impl::createDisplayTemplated<
             Display>(mCompositionEngine,
                      DisplayCreationArgsBuilder()
-                             .setPhysical({DEFAULT_DISPLAY_ID, DisplayConnectionType::Internal})
+                             .setPhysical({DEFAULT_DISPLAY_ID, ui::DisplayConnectionType::Internal})
                              .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
                              .setPixelFormat(static_cast<ui::PixelFormat>(PIXEL_FORMAT_RGBA_8888))
                              .setIsSecure(true)
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index ab00385..bac894a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -91,7 +91,7 @@
     MOCK_CONST_METHOD1(getColorModes, std::vector<ui::ColorMode>(PhysicalDisplayId));
     MOCK_METHOD3(setActiveColorMode, status_t(PhysicalDisplayId, ui::ColorMode, ui::RenderIntent));
     MOCK_CONST_METHOD0(isUsingVrComposer, bool());
-    MOCK_CONST_METHOD1(getDisplayConnectionType, DisplayConnectionType(PhysicalDisplayId));
+    MOCK_CONST_METHOD1(getDisplayConnectionType, ui::DisplayConnectionType(PhysicalDisplayId));
     MOCK_CONST_METHOD1(isVsyncPeriodSwitchSupported, bool(PhysicalDisplayId));
     MOCK_CONST_METHOD2(getDisplayVsyncPeriod, status_t(PhysicalDisplayId, nsecs_t*));
     MOCK_METHOD4(setActiveModeWithConstraints,
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index dcfc162..9dd199d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -840,19 +840,19 @@
 TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoFECompositionState) {
     EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
 
-    mOutputLayer.writeStateToHWC(true);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoHWCState) {
     mOutputLayer.editState().hwc.reset();
 
-    mOutputLayer.writeStateToHWC(true);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoHWCLayer) {
     mOutputLayer.editState().hwc = impl::OutputLayerCompositionState::Hwc(nullptr);
 
-    mOutputLayer.writeStateToHWC(true);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetAllState) {
@@ -861,7 +861,7 @@
 
     expectNoSetCompositionTypeCall();
 
-    mOutputLayer.writeStateToHWC(true);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerTest, displayInstallOrientationBufferTransformSetTo90) {
@@ -891,7 +891,7 @@
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::SOLID_COLOR);
     expectSetColorCall();
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSideband) {
@@ -901,7 +901,7 @@
     expectSetSidebandHandleCall();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::SIDEBAND);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForCursor) {
@@ -911,7 +911,7 @@
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CURSOR);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForDevice) {
@@ -921,7 +921,7 @@
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsNotSetIfUnchanged) {
@@ -934,7 +934,7 @@
     expectSetColorCall();
     expectNoSetCompositionTypeCall();
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfColorTransformNotSupported) {
@@ -944,7 +944,7 @@
     expectSetColorCall();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfClientCompositionForced) {
@@ -956,7 +956,7 @@
     expectSetColorCall();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, allStateIncludesMetadataIfPresent) {
@@ -969,7 +969,7 @@
     expectGenericLayerMetadataCalls();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
 
-    mOutputLayer.writeStateToHWC(true);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, perFrameStateDoesNotIncludeMetadataIfPresent) {
@@ -980,7 +980,7 @@
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 /*
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index c4ae3a7..5f0b0ee 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -96,6 +96,8 @@
         EXPECT_CALL(*outputLayer, editState()).WillRepeatedly(ReturnRef(outputLayerState));
 
         EXPECT_CALL(*layerFE, getCompositionState()).WillRepeatedly(Return(&layerFEState));
+        EXPECT_CALL(*layerFE, getSequence()).WillRepeatedly(Return(0));
+        EXPECT_CALL(*layerFE, getDebugName()).WillRepeatedly(Return("InjectedLayer"));
     }
 
     mock::OutputLayer* outputLayer = {new StrictMock<mock::OutputLayer>};
@@ -111,6 +113,8 @@
         EXPECT_CALL(outputLayer, editState()).WillRepeatedly(ReturnRef(outputLayerState));
 
         EXPECT_CALL(*layerFE, getCompositionState()).WillRepeatedly(Return(&layerFEState));
+        EXPECT_CALL(*layerFE, getSequence()).WillRepeatedly(Return(0));
+        EXPECT_CALL(*layerFE, getDebugName()).WillRepeatedly(Return("NonInjectedLayer"));
     }
 
     mock::OutputLayer outputLayer;
@@ -751,7 +755,9 @@
     mOutput->editState().isEnabled = true;
 
     CompositionRefreshArgs args;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 TEST_F(OutputUpdateAndWriteCompositionStateTest, doesNothingIfOutputNotEnabled) {
@@ -766,7 +772,9 @@
     injectOutputLayer(layer3);
 
     CompositionRefreshArgs args;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 TEST_F(OutputUpdateAndWriteCompositionStateTest, updatesLayerContentForAllLayers) {
@@ -775,11 +783,14 @@
     InjectedLayer layer3;
 
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
 
     injectOutputLayer(layer1);
     injectOutputLayer(layer2);
@@ -791,7 +802,9 @@
     args.updatingGeometryThisFrame = false;
     args.devOptForceClientComposition = false;
     args.internalDisplayRotationFlags = ui::Transform::ROT_180;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 TEST_F(OutputUpdateAndWriteCompositionStateTest, updatesLayerGeometryAndContentForAllLayers) {
@@ -800,11 +813,14 @@
     InjectedLayer layer3;
 
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(true));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(true));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(true));
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false));
 
     injectOutputLayer(layer1);
     injectOutputLayer(layer2);
@@ -815,7 +831,9 @@
     CompositionRefreshArgs args;
     args.updatingGeometryThisFrame = true;
     args.devOptForceClientComposition = false;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 TEST_F(OutputUpdateAndWriteCompositionStateTest, forcesClientCompositionForAllLayers) {
@@ -824,11 +842,14 @@
     InjectedLayer layer3;
 
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
 
     injectOutputLayer(layer1);
     injectOutputLayer(layer2);
@@ -839,7 +860,9 @@
     CompositionRefreshArgs args;
     args.updatingGeometryThisFrame = false;
     args.devOptForceClientComposition = true;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 /*
@@ -877,6 +900,9 @@
     mOutput.editState().usesDeviceComposition = true;
 
     EXPECT_CALL(mOutput, chooseCompositionStrategy()).Times(1);
+    if (mOutput.plannerEnabled()) {
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    }
     EXPECT_CALL(*mRenderSurface, prepareFrame(false, true));
 
     mOutput.prepareFrame();
@@ -1621,14 +1647,17 @@
         // Sets up the helper functions called by the function under test to use
         // mock implementations.
         MOCK_METHOD1(updateColorProfile, void(const compositionengine::CompositionRefreshArgs&));
-        MOCK_METHOD1(updateAndWriteCompositionState,
+        MOCK_METHOD1(updateCompositionState,
                      void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD0(planComposition, void());
+        MOCK_METHOD1(writeCompositionState, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD1(setColorTransform, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD0(beginFrame, void());
         MOCK_METHOD0(prepareFrame, void());
         MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD1(finishFrame, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD0(postFramebuffer, void());
+        MOCK_METHOD0(renderCachedSets, void());
     };
 
     StrictMock<OutputPartialMock> mOutput;
@@ -1639,13 +1668,16 @@
 
     InSequence seq;
     EXPECT_CALL(mOutput, updateColorProfile(Ref(args)));
-    EXPECT_CALL(mOutput, updateAndWriteCompositionState(Ref(args)));
+    EXPECT_CALL(mOutput, updateCompositionState(Ref(args)));
+    EXPECT_CALL(mOutput, planComposition());
+    EXPECT_CALL(mOutput, writeCompositionState(Ref(args)));
     EXPECT_CALL(mOutput, setColorTransform(Ref(args)));
     EXPECT_CALL(mOutput, beginFrame());
     EXPECT_CALL(mOutput, prepareFrame());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
     EXPECT_CALL(mOutput, finishFrame(Ref(args)));
     EXPECT_CALL(mOutput, postFramebuffer());
+    EXPECT_CALL(mOutput, renderCachedSets());
 
     mOutput.present(args);
 }
@@ -1665,12 +1697,12 @@
 
     struct Layer {
         Layer() {
-            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(mLayerFE));
-            EXPECT_CALL(mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
+            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*mLayerFE));
+            EXPECT_CALL(*mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
         }
 
         StrictMock<mock::OutputLayer> mOutputLayer;
-        StrictMock<mock::LayerFE> mLayerFE;
+        sp<StrictMock<mock::LayerFE>> mLayerFE = sp<StrictMock<mock::LayerFE>>::make();
         LayerFECompositionState mLayerFEState;
     };
 
@@ -2680,12 +2712,12 @@
 
     struct Layer {
         Layer() {
-            EXPECT_CALL(outputLayer, getLayerFE()).WillRepeatedly(ReturnRef(layerFE));
+            EXPECT_CALL(outputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*layerFE));
             EXPECT_CALL(outputLayer, getHwcLayer()).WillRepeatedly(Return(&hwc2Layer));
         }
 
         StrictMock<mock::OutputLayer> outputLayer;
-        StrictMock<mock::LayerFE> layerFE;
+        sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
         StrictMock<HWC2::mock::Layer> hwc2Layer;
     };
 
@@ -2761,11 +2793,11 @@
     // are passed. This happens to work with the current implementation, but
     // would not survive certain calls like Fence::merge() which would return a
     // new instance.
-    EXPECT_CALL(mLayer1.layerFE,
+    EXPECT_CALL(*mLayer1.layerFE,
                 onLayerDisplayed(Property(&sp<Fence>::get, Eq(layer1Fence.get()))));
-    EXPECT_CALL(mLayer2.layerFE,
+    EXPECT_CALL(*mLayer2.layerFE,
                 onLayerDisplayed(Property(&sp<Fence>::get, Eq(layer2Fence.get()))));
-    EXPECT_CALL(mLayer3.layerFE,
+    EXPECT_CALL(*mLayer3.layerFE,
                 onLayerDisplayed(Property(&sp<Fence>::get, Eq(layer3Fence.get()))));
 
     mOutput.postFramebuffer();
@@ -2792,9 +2824,9 @@
     // Fence::merge is called, and since none of the fences are actually valid,
     // Fence::NO_FENCE is returned and passed to each onLayerDisplayed() call.
     // This is the best we can do without creating a real kernel fence object.
-    EXPECT_CALL(mLayer1.layerFE, onLayerDisplayed(Fence::NO_FENCE));
-    EXPECT_CALL(mLayer2.layerFE, onLayerDisplayed(Fence::NO_FENCE));
-    EXPECT_CALL(mLayer3.layerFE, onLayerDisplayed(Fence::NO_FENCE));
+    EXPECT_CALL(*mLayer1.layerFE, onLayerDisplayed(Fence::NO_FENCE));
+    EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed(Fence::NO_FENCE));
+    EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed(Fence::NO_FENCE));
 
     mOutput.postFramebuffer();
 }
@@ -3298,12 +3330,12 @@
 struct OutputComposeSurfacesTest_HandlesProtectedContent : public OutputComposeSurfacesTest {
     struct Layer {
         Layer() {
-            EXPECT_CALL(mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
-            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(mLayerFE));
+            EXPECT_CALL(*mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
+            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*mLayerFE));
         }
 
         StrictMock<mock::OutputLayer> mOutputLayer;
-        StrictMock<mock::LayerFE> mLayerFE;
+        sp<StrictMock<mock::LayerFE>> mLayerFE = sp<StrictMock<mock::LayerFE>>::make();
         LayerFECompositionState mLayerFEState;
     };
 
@@ -3461,7 +3493,8 @@
         mOutput.editState().isEnabled = true;
 
         EXPECT_CALL(mLayer.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-        EXPECT_CALL(mLayer.outputLayer, writeStateToHWC(false));
+        EXPECT_CALL(mLayer.outputLayer,
+                    writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
         EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
                 .WillOnce(Return(std::vector<LayerFE::LayerSettings>{}));
         EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _, _)).WillOnce(Return(NO_ERROR));
@@ -3476,7 +3509,9 @@
 
 TEST_F(OutputComposeSurfacesTest_SetsExpensiveRendering_ForBlur, IfBlursAreExpensive) {
     mRefreshArgs.blursAreExpensive = true;
-    mOutput.updateAndWriteCompositionState(mRefreshArgs);
+    mOutput.updateCompositionState(mRefreshArgs);
+    mOutput.planComposition();
+    mOutput.writeCompositionState(mRefreshArgs);
 
     EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true));
     mOutput.composeSurfaces(kDebugRegion, mRefreshArgs);
@@ -3484,7 +3519,9 @@
 
 TEST_F(OutputComposeSurfacesTest_SetsExpensiveRendering_ForBlur, IfBlursAreNotExpensive) {
     mRefreshArgs.blursAreExpensive = false;
-    mOutput.updateAndWriteCompositionState(mRefreshArgs);
+    mOutput.updateCompositionState(mRefreshArgs);
+    mOutput.planComposition();
+    mOutput.writeCompositionState(mRefreshArgs);
 
     EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true)).Times(0);
     mOutput.composeSurfaces(kDebugRegion, mRefreshArgs);
@@ -3509,12 +3546,12 @@
         Layer() {
             EXPECT_CALL(mOutputLayer, getState()).WillRepeatedly(ReturnRef(mOutputLayerState));
             EXPECT_CALL(mOutputLayer, editState()).WillRepeatedly(ReturnRef(mOutputLayerState));
-            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(mLayerFE));
-            EXPECT_CALL(mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
+            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*mLayerFE));
+            EXPECT_CALL(*mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
         }
 
         StrictMock<mock::OutputLayer> mOutputLayer;
-        StrictMock<mock::LayerFE> mLayerFE;
+        sp<StrictMock<mock::LayerFE>> mLayerFE = sp<StrictMock<mock::LayerFE>>::make();
         LayerFECompositionState mLayerFEState;
         impl::OutputLayerCompositionState mOutputLayerState;
         LayerFE::LayerSettings mLayerSettings;
@@ -3608,11 +3645,11 @@
     LayerFE::LayerSettings mShadowSettings;
     mShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f};
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(_))
+    EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(_))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(_))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(_))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[1].mLayerSettings})));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(_))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(_))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>(
                     {mShadowSettings, mLayers[2].mLayerSettings})));
 
@@ -3646,7 +3683,7 @@
     mLayers[1].mLayerFEState.isOpaque = true;
     mLayers[2].mLayerFEState.isOpaque = true;
 
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(_))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(_))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
 
     Region accumClearRegion(Rect(10, 11, 12, 13));
@@ -3672,7 +3709,7 @@
     mLayers[1].mLayerFEState.isOpaque = false;
     mLayers[2].mLayerFEState.isOpaque = false;
 
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(_))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(_))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
 
     Region accumClearRegion(Rect(10, 11, 12, 13));
@@ -3736,9 +3773,9 @@
     mBlackoutSettings.alpha = 0.f;
     mBlackoutSettings.disableBlending = true;
 
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mBlackoutSettings})));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
 
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
@@ -3798,11 +3835,11 @@
             false /* disabledBlurs */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+    EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
@@ -3854,11 +3891,11 @@
             false /* disabledBlurs */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+    EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
@@ -3911,11 +3948,11 @@
             false /* disabledBlurs */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+    EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
@@ -3966,11 +4003,11 @@
             false /* disabledBlurs */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+    EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
@@ -4019,11 +4056,11 @@
             false /* disabledBlurs */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+    EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+    EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(mOutput.generateClientCompositionRequests(true /* supportsProtectedContent */,
@@ -4038,11 +4075,14 @@
 
     // Layer requesting blur, or below, should request client composition.
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
 
     layer2.layerFEState.backgroundBlurRadius = 10;
 
@@ -4055,7 +4095,9 @@
     CompositionRefreshArgs args;
     args.updatingGeometryThisFrame = false;
     args.devOptForceClientComposition = false;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 TEST_F(OutputUpdateAndWriteCompositionStateTest, handlesBlurRegionRequests) {
@@ -4065,11 +4107,14 @@
 
     // Layer requesting blur, or below, should request client composition.
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
 
     BlurRegion region;
     layer2.layerFEState.blurRegions.push_back(region);
@@ -4083,7 +4128,9 @@
     CompositionRefreshArgs args;
     args.updatingGeometryThisFrame = false;
     args.devOptForceClientComposition = false;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 TEST_F(GenerateClientCompositionRequestsTest, handlesLandscapeModeSplitScreenRequests) {
@@ -4141,7 +4188,7 @@
 
     EXPECT_CALL(leftLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
     EXPECT_CALL(leftLayer.mOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
-    EXPECT_CALL(leftLayer.mLayerFE, prepareClientCompositionList(Eq(ByRef(leftLayerSettings))))
+    EXPECT_CALL(*leftLayer.mLayerFE, prepareClientCompositionList(Eq(ByRef(leftLayerSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({leftLayer.mLayerSettings})));
 
     compositionengine::LayerFE::ClientCompositionTargetSettings rightLayerSettings{
@@ -4159,7 +4206,7 @@
 
     EXPECT_CALL(rightLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
     EXPECT_CALL(rightLayer.mOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
-    EXPECT_CALL(rightLayer.mLayerFE, prepareClientCompositionList(Eq(ByRef(rightLayerSettings))))
+    EXPECT_CALL(*rightLayer.mLayerFE, prepareClientCompositionList(Eq(ByRef(rightLayerSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({rightLayer.mLayerSettings})));
 
     constexpr bool supportsProtectedContent = true;
@@ -4199,7 +4246,7 @@
 
     EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mShadowSettings})));
 
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
@@ -4239,7 +4286,7 @@
 
     EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings))))
+    EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>(
                     {mShadowSettings, mLayers[2].mLayerSettings})));
 
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index c751f22..f4a2a3f 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -145,9 +145,9 @@
 }
 
 void DisplayDevice::setActiveMode(DisplayModeId id) {
-    LOG_FATAL_IF(id.value() >= mSupportedModes.size(),
-                 "Cannot set active mode which is not supported.");
-    mActiveModeId = id;
+    const auto mode = getMode(id);
+    LOG_FATAL_IF(!mode, "Cannot set active mode which is not supported.");
+    mActiveMode = mode;
 }
 
 status_t DisplayDevice::initiateModeChange(DisplayModeId modeId,
@@ -164,7 +164,7 @@
 }
 
 const DisplayModePtr& DisplayDevice::getActiveMode() const {
-    return mSupportedModes[mActiveModeId.value()];
+    return mActiveMode;
 }
 
 const DisplayModes& DisplayDevice::getSupportedModes() const {
@@ -172,9 +172,10 @@
 }
 
 DisplayModePtr DisplayDevice::getMode(DisplayModeId modeId) const {
-    const auto id = modeId.value();
-    if (id < mSupportedModes.size()) {
-        return mSupportedModes[id];
+    const auto it = std::find_if(mSupportedModes.begin(), mSupportedModes.end(),
+                                 [&](DisplayModePtr mode) { return mode->getId() == modeId; });
+    if (it != mSupportedModes.end()) {
+        return *it;
     }
     return nullptr;
 }
@@ -254,7 +255,7 @@
 std::string DisplayDevice::getDebugName() const {
     const char* type = "virtual";
     if (mConnectionType) {
-        type = *mConnectionType == DisplayConnectionType::Internal ? "internal" : "external";
+        type = *mConnectionType == ui::DisplayConnectionType::Internal ? "internal" : "external";
     }
 
     return base::StringPrintf("DisplayDevice{%s, %s%s, \"%s\"}", to_string(getId()).c_str(), type,
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index b4db933..7156613 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -28,11 +28,11 @@
 #include <renderengine/RenderEngine.h>
 #include <system/window.h>
 #include <ui/DisplayId.h>
-#include <ui/DisplayInfo.h>
 #include <ui/DisplayState.h>
 #include <ui/GraphicTypes.h>
 #include <ui/HdrCapabilities.h>
 #include <ui/Region.h>
+#include <ui/StaticDisplayInfo.h>
 #include <ui/Transform.h>
 #include <utils/Errors.h>
 #include <utils/Mutex.h>
@@ -74,7 +74,7 @@
         return mCompositionDisplay;
     }
 
-    std::optional<DisplayConnectionType> getConnectionType() const { return mConnectionType; }
+    std::optional<ui::DisplayConnectionType> getConnectionType() const { return mConnectionType; }
 
     bool isVirtual() const { return !mConnectionType; }
     bool isPrimary() const { return mIsPrimary; }
@@ -195,7 +195,7 @@
     HWComposer& mHwComposer;
     const wp<IBinder> mDisplayToken;
     const int32_t mSequenceId;
-    const std::optional<DisplayConnectionType> mConnectionType;
+    const std::optional<ui::DisplayConnectionType> mConnectionType;
 
     const std::shared_ptr<compositionengine::Display> mCompositionDisplay;
 
@@ -208,7 +208,7 @@
 
     hardware::graphics::composer::hal::PowerMode mPowerMode =
             hardware::graphics::composer::hal::PowerMode::OFF;
-    DisplayModeId mActiveModeId;
+    DisplayModePtr mActiveMode;
     const DisplayModes mSupportedModes;
 
     std::atomic<nsecs_t> mLastHwVsync = 0;
@@ -222,7 +222,7 @@
 struct DisplayDeviceState {
     struct Physical {
         PhysicalDisplayId id;
-        DisplayConnectionType type;
+        ui::DisplayConnectionType type;
         hardware::graphics::composer::hal::HWDisplayId hwcDisplayId;
         std::optional<DeviceProductInfo> deviceProductInfo;
         DisplayModes supportedModes;
@@ -263,7 +263,7 @@
     const std::shared_ptr<compositionengine::Display> compositionDisplay;
 
     int32_t sequenceId{0};
-    std::optional<DisplayConnectionType> connectionType;
+    std::optional<ui::DisplayConnectionType> connectionType;
     bool isSecure{false};
     sp<ANativeWindow> nativeWindow;
     sp<compositionengine::DisplaySurface> displaySurface;
diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h
index 1f0f3c3..85cc993 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayMode.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h
@@ -22,6 +22,7 @@
 
 #include <android-base/stringprintf.h>
 #include <android/configuration.h>
+#include <ui/DisplayMode.h>
 #include <ui/Size.h>
 #include <utils/Timers.h>
 
@@ -36,7 +37,7 @@
 class DisplayMode;
 using DisplayModePtr = std::shared_ptr<const DisplayMode>;
 using DisplayModes = std::vector<DisplayModePtr>;
-using DisplayModeId = StrongTyping<size_t, struct DisplayModeIdTag, Compare, Hash>;
+using DisplayModeId = StrongTyping<ui::DisplayModeId, struct DisplayModeIdTag, Compare, Hash>;
 
 class DisplayMode {
 public:
@@ -124,6 +125,12 @@
     // without visual interruptions such as a black screen.
     int32_t getGroup() const { return mGroup; }
 
+    bool equalsExceptDisplayModeId(const DisplayModePtr& other) const {
+        return mHwcId == other->mHwcId && mWidth == other->mWidth && mHeight == other->mHeight &&
+                getVsyncPeriod() == other->getVsyncPeriod() && mDpiX == other->mDpiX &&
+                mDpiY == other->mDpiY && mGroup == other->mGroup;
+    }
+
 private:
     explicit DisplayMode(hal::HWConfigId id) : mHwcId(id) {}
 
@@ -139,7 +146,7 @@
 };
 
 inline std::string to_string(const DisplayMode& mode) {
-    return base::StringPrintf("{id=%zu, hwcId=%d, width=%d, height=%d, refreshRate=%s, "
+    return base::StringPrintf("{id=%d, hwcId=%d, width=%d, height=%d, refreshRate=%s, "
                               "dpiX=%.2f, dpiY=%.2f, group=%d}",
                               mode.getId().value(), mode.getHwcId(), mode.getWidth(),
                               mode.getHeight(), to_string(mode.getFps()).c_str(), mode.getDpiX(),
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 71a3276..d04b5f7 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -264,7 +264,7 @@
     return Error::NONE;
 }
 
-Error Display::getConnectionType(android::DisplayConnectionType* outType) const {
+Error Display::getConnectionType(ui::DisplayConnectionType* outType) const {
     if (mType != DisplayType::PHYSICAL) return Error::BAD_DISPLAY;
 
     using ConnectionType = Hwc2::IComposerClient::DisplayConnectionType;
@@ -274,9 +274,8 @@
         return error;
     }
 
-    *outType = connectionType == ConnectionType::INTERNAL
-            ? android::DisplayConnectionType::Internal
-            : android::DisplayConnectionType::External;
+    *outType = connectionType == ConnectionType::INTERNAL ? ui::DisplayConnectionType::Internal
+                                                          : ui::DisplayConnectionType::External;
     return Error::NONE;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 4c7f284..e7bf286 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -18,9 +18,9 @@
 
 #include <gui/HdrMetadata.h>
 #include <math/mat4.h>
-#include <ui/DisplayInfo.h>
 #include <ui/HdrCapabilities.h>
 #include <ui/Region.h>
+#include <ui/StaticDisplayInfo.h>
 #include <utils/Log.h>
 #include <utils/StrongPointer.h>
 #include <utils/Timers.h>
@@ -104,7 +104,7 @@
             hal::DisplayRequest* outDisplayRequests,
             std::unordered_map<Layer*, hal::LayerRequest>* outLayerRequests) = 0;
     [[clang::warn_unused_result]] virtual hal::Error getConnectionType(
-            android::DisplayConnectionType*) const = 0;
+            ui::DisplayConnectionType*) const = 0;
     [[clang::warn_unused_result]] virtual hal::Error supportsDoze(bool* outSupport) const = 0;
     [[clang::warn_unused_result]] virtual hal::Error getHdrCapabilities(
             android::HdrCapabilities* outCapabilities) const = 0;
@@ -175,7 +175,7 @@
     hal::Error getRequests(
             hal::DisplayRequest* outDisplayRequests,
             std::unordered_map<Layer*, hal::LayerRequest>* outLayerRequests) override;
-    hal::Error getConnectionType(android::DisplayConnectionType*) const override;
+    hal::Error getConnectionType(ui::DisplayConnectionType*) const override;
     hal::Error supportsDoze(bool* outSupport) const override;
     hal::Error getHdrCapabilities(android::HdrCapabilities* outCapabilities) const override;
     hal::Error getDisplayedContentSamplingAttributes(hal::PixelFormat* outFormat,
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index b9a8e4b..ccfaa76 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -369,16 +369,16 @@
 
 // Composer 2.4
 
-DisplayConnectionType HWComposer::getDisplayConnectionType(PhysicalDisplayId displayId) const {
-    RETURN_IF_INVALID_DISPLAY(displayId, DisplayConnectionType::Internal);
+ui::DisplayConnectionType HWComposer::getDisplayConnectionType(PhysicalDisplayId displayId) const {
+    RETURN_IF_INVALID_DISPLAY(displayId, ui::DisplayConnectionType::Internal);
     const auto& hwcDisplay = mDisplayData.at(displayId).hwcDisplay;
 
-    DisplayConnectionType type;
+    ui::DisplayConnectionType type;
     const auto error = hwcDisplay->getConnectionType(&type);
 
     const auto FALLBACK_TYPE = hwcDisplay->getId() == mInternalHwcDisplayId
-            ? DisplayConnectionType::Internal
-            : DisplayConnectionType::External;
+            ? ui::DisplayConnectionType::Internal
+            : ui::DisplayConnectionType::External;
 
     RETURN_IF_HWC_ERROR(error, displayId, FALLBACK_TYPE);
     return type;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index f9c8e2e..f532e50 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -91,6 +91,12 @@
         int32_t dpiX = -1;
         int32_t dpiY = -1;
         int32_t configGroup = -1;
+
+        friend std::ostream& operator<<(std::ostream& os, const HWCDisplayMode& mode) {
+            return os << "id=" << mode.hwcId << " res=" << mode.width << "x" << mode.height
+                      << " vsyncPeriod=" << mode.vsyncPeriod << " dpi=" << mode.dpiX << "x"
+                      << mode.dpiY << " group=" << mode.configGroup;
+        }
     };
 
     virtual ~HWComposer();
@@ -204,7 +210,7 @@
                                         ui::RenderIntent) = 0;
 
     // Composer 2.4
-    virtual DisplayConnectionType getDisplayConnectionType(PhysicalDisplayId) const = 0;
+    virtual ui::DisplayConnectionType getDisplayConnectionType(PhysicalDisplayId) const = 0;
     virtual bool isVsyncPeriodSwitchSupported(PhysicalDisplayId) const = 0;
     virtual status_t getDisplayVsyncPeriod(PhysicalDisplayId displayId,
                                            nsecs_t* outVsyncPeriod) const = 0;
@@ -335,7 +341,7 @@
     status_t setActiveColorMode(PhysicalDisplayId, ui::ColorMode, ui::RenderIntent) override;
 
     // Composer 2.4
-    DisplayConnectionType getDisplayConnectionType(PhysicalDisplayId) const override;
+    ui::DisplayConnectionType getDisplayConnectionType(PhysicalDisplayId) const override;
     bool isVsyncPeriodSwitchSupported(PhysicalDisplayId) const override;
     status_t getDisplayVsyncPeriod(PhysicalDisplayId displayId,
                                    nsecs_t* outVsyncPeriod) const override;
diff --git a/services/surfaceflinger/FpsReporter.cpp b/services/surfaceflinger/FpsReporter.cpp
new file mode 100644
index 0000000..0bc2d3e
--- /dev/null
+++ b/services/surfaceflinger/FpsReporter.cpp
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "FpsReporter"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <algorithm>
+
+#include "FpsReporter.h"
+#include "Layer.h"
+#include "SurfaceFlinger.h"
+
+namespace android {
+
+FpsReporter::FpsReporter(frametimeline::FrameTimeline& frameTimeline, SurfaceFlinger& flinger)
+      : mFrameTimeline(frameTimeline), mFlinger(flinger) {}
+
+void FpsReporter::dispatchLayerFps() const {
+    std::vector<TrackedListener> localListeners;
+    {
+        std::scoped_lock lock(mMutex);
+        if (mListeners.empty()) {
+            return;
+        }
+
+        std::transform(mListeners.begin(), mListeners.end(), std::back_inserter(localListeners),
+                       [](const std::pair<wp<IBinder>, TrackedListener>& entry) {
+                           return entry.second;
+                       });
+    }
+
+    std::unordered_set<int32_t> seenTasks;
+    std::vector<std::pair<TrackedListener, sp<Layer>>> listenersAndLayersToReport;
+
+    mFlinger.mCurrentState.traverse([&](Layer* layer) {
+        auto& currentState = layer->getCurrentState();
+        if (currentState.metadata.has(METADATA_TASK_ID)) {
+            int32_t taskId = currentState.metadata.getInt32(METADATA_TASK_ID, 0);
+            if (seenTasks.count(taskId) == 0) {
+                // localListeners is expected to be tiny
+                for (TrackedListener& listener : localListeners) {
+                    if (listener.taskId == taskId) {
+                        seenTasks.insert(taskId);
+                        listenersAndLayersToReport.push_back({listener, sp<Layer>(layer)});
+                        break;
+                    }
+                }
+            }
+        }
+    });
+
+    for (const auto& [listener, layer] : listenersAndLayersToReport) {
+        std::unordered_set<int32_t> layerIds;
+
+        layer->traverse(LayerVector::StateSet::Current,
+                        [&](Layer* layer) { layerIds.insert(layer->getSequence()); });
+
+        listener.listener->onFpsReported(mFrameTimeline.computeFps(layerIds));
+    }
+}
+
+void FpsReporter::binderDied(const wp<IBinder>& who) {
+    std::scoped_lock lock(mMutex);
+    mListeners.erase(who);
+}
+
+void FpsReporter::addListener(const sp<gui::IFpsListener>& listener, int32_t taskId) {
+    sp<IBinder> asBinder = IInterface::asBinder(listener);
+    asBinder->linkToDeath(this);
+    std::lock_guard lock(mMutex);
+    mListeners.emplace(wp<IBinder>(asBinder), TrackedListener{listener, taskId});
+}
+
+void FpsReporter::removeListener(const sp<gui::IFpsListener>& listener) {
+    std::lock_guard lock(mMutex);
+    mListeners.erase(wp<IBinder>(IInterface::asBinder(listener)));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/FpsReporter.h b/services/surfaceflinger/FpsReporter.h
new file mode 100644
index 0000000..1cec295
--- /dev/null
+++ b/services/surfaceflinger/FpsReporter.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/thread_annotations.h>
+#include <android/gui/IFpsListener.h>
+#include <binder/IBinder.h>
+
+#include <unordered_map>
+
+#include "FrameTimeline/FrameTimeline.h"
+
+namespace android {
+
+class Layer;
+class SurfaceFlinger;
+
+class FpsReporter : public IBinder::DeathRecipient {
+public:
+    FpsReporter(frametimeline::FrameTimeline& frameTimeline, SurfaceFlinger& flinger);
+
+    // Dispatches updated layer fps values for the registered listeners
+    // This method promotes Layer weak pointers and performs layer stack traversals, so mStateLock
+    // must be held when calling this method.
+    void dispatchLayerFps() const EXCLUDES(mMutex);
+
+    // Override for IBinder::DeathRecipient
+    void binderDied(const wp<IBinder>&) override;
+
+    // Registers an Fps listener that listens to fps updates for the provided layer
+    void addListener(const sp<gui::IFpsListener>& listener, int32_t taskId);
+    // Deregisters an Fps listener
+    void removeListener(const sp<gui::IFpsListener>& listener);
+
+private:
+    mutable std::mutex mMutex;
+    struct WpHash {
+        size_t operator()(const wp<IBinder>& p) const {
+            return std::hash<IBinder*>()(p.unsafe_get());
+        }
+    };
+
+    struct TrackedListener {
+        sp<gui::IFpsListener> listener;
+        int32_t taskId;
+    };
+
+    frametimeline::FrameTimeline& mFrameTimeline;
+    SurfaceFlinger& mFlinger;
+    std::unordered_map<wp<IBinder>, TrackedListener, WpHash> mListeners GUARDED_BY(mMutex);
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/FrameTimeline/Android.bp b/services/surfaceflinger/FrameTimeline/Android.bp
index 1e6d21e..10a5833 100644
--- a/services/surfaceflinger/FrameTimeline/Android.bp
+++ b/services/surfaceflinger/FrameTimeline/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
 cc_library_static {
     name: "libframetimeline",
     defaults: ["surfaceflinger_defaults"],
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index da04202..03e38f3 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -19,12 +19,15 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include "FrameTimeline.h"
+
 #include <android-base/stringprintf.h>
 #include <utils/Log.h>
 #include <utils/Trace.h>
+
 #include <chrono>
 #include <cinttypes>
 #include <numeric>
+#include <unordered_set>
 
 namespace android::frametimeline {
 
@@ -204,6 +207,17 @@
     }
 }
 
+FrameTimelineEvent::PredictionType toProto(PredictionState predictionState) {
+    switch (predictionState) {
+        case PredictionState::Valid:
+            return FrameTimelineEvent::PREDICTION_VALID;
+        case PredictionState::Expired:
+            return FrameTimelineEvent::PREDICTION_EXPIRED;
+        case PredictionState::None:
+            return FrameTimelineEvent::PREDICTION_UNKNOWN;
+    }
+}
+
 int32_t jankTypeBitmaskToProto(int32_t jankType) {
     if (jankType == JankType::None) {
         return FrameTimelineEvent::JANK_NONE;
@@ -277,8 +291,8 @@
 }
 
 SurfaceFrame::SurfaceFrame(const FrameTimelineInfo& frameTimelineInfo, pid_t ownerPid,
-                           uid_t ownerUid, std::string layerName, std::string debugName,
-                           PredictionState predictionState,
+                           uid_t ownerUid, int32_t layerId, std::string layerName,
+                           std::string debugName, PredictionState predictionState,
                            frametimeline::TimelineItem&& predictions,
                            std::shared_ptr<TimeStats> timeStats,
                            JankClassificationThresholds thresholds,
@@ -289,6 +303,7 @@
         mOwnerUid(ownerUid),
         mLayerName(std::move(layerName)),
         mDebugName(std::move(debugName)),
+        mLayerId(layerId),
         mPresentState(PresentState::Unknown),
         mPredictionState(predictionState),
         mPredictions(predictions),
@@ -397,6 +412,8 @@
     StringAppendF(&result, "Scheduled rendering rate: %d fps\n",
                   mRenderRate ? mRenderRate->getIntValue() : 0);
     StringAppendF(&result, "%s", indent.c_str());
+    StringAppendF(&result, "Layer ID : %d\n", mLayerId);
+    StringAppendF(&result, "%s", indent.c_str());
     StringAppendF(&result, "Present State : %s\n", toString(mPresentState).c_str());
     StringAppendF(&result, "%s", indent.c_str());
     if (mPresentState == PresentState::Dropped) {
@@ -458,7 +475,9 @@
 
     const nsecs_t presentDelta = mActuals.presentTime - mPredictions.presentTime;
     const nsecs_t deadlineDelta = mActuals.endTime - mPredictions.endTime;
-    const nsecs_t deltaToVsync = std::abs(presentDelta) % refreshRate.getPeriodNsecs();
+    const nsecs_t deltaToVsync = refreshRate.getPeriodNsecs() > 0
+            ? std::abs(presentDelta) % refreshRate.getPeriodNsecs()
+            : 0;
 
     if (deadlineDelta > mJankClassificationThresholds.deadlineThreshold) {
         mFrameReadyMetadata = FrameReadyMetadata::LateFinish;
@@ -496,6 +515,17 @@
         if (mLastLatchTime != 0 && mPredictions.endTime <= mLastLatchTime) {
             // Buffer Stuffing.
             mJankType |= JankType::BufferStuffing;
+            // In a stuffed state, the frame could be stuck on a dequeue wait for quite some time.
+            // Because of this dequeue wait, it can be hard to tell if a frame was genuinely late.
+            // We try to do this by moving the deadline. Since the queue could be stuffed by more
+            // than one buffer, we take the last latch time as reference and give one vsync
+            // worth of time for the frame to be ready.
+            nsecs_t adjustedDeadline = mLastLatchTime + refreshRate.getPeriodNsecs();
+            if (adjustedDeadline > mActuals.endTime) {
+                mFrameReadyMetadata = FrameReadyMetadata::OnTimeFinish;
+            } else {
+                mFrameReadyMetadata = FrameReadyMetadata::LateFinish;
+            }
         }
         if (mFrameReadyMetadata == FrameReadyMetadata::OnTimeFinish) {
             // Finish on time, Present late
@@ -503,14 +533,18 @@
                 // Propagate displayFrame's jank if it exists
                 mJankType |= displayFrameJankType;
             } else {
-                if (deltaToVsync < mJankClassificationThresholds.presentThreshold ||
-                    deltaToVsync >= refreshRate.getPeriodNsecs() -
-                                    mJankClassificationThresholds.presentThreshold) {
-                    // Delta factor of vsync
-                    mJankType |= JankType::SurfaceFlingerScheduling;
-                } else {
-                    // Delta not a factor of vsync
-                    mJankType |= JankType::PredictionError;
+                if (!(mJankType & JankType::BufferStuffing)) {
+                    // In a stuffed state, if the app finishes on time and there is no display frame
+                    // jank, only buffer stuffing is the root cause of the jank.
+                    if (deltaToVsync < mJankClassificationThresholds.presentThreshold ||
+                        deltaToVsync >= refreshRate.getPeriodNsecs() -
+                                        mJankClassificationThresholds.presentThreshold) {
+                        // Delta factor of vsync
+                        mJankType |= JankType::SurfaceFlingerScheduling;
+                    } else {
+                        // Delta not a factor of vsync
+                        mJankType |= JankType::PredictionError;
+                    }
                 }
             }
         } else if (mFrameReadyMetadata == FrameReadyMetadata::LateFinish) {
@@ -605,6 +639,7 @@
                                                          FrameReadyMetadata::OnTimeFinish);
         actualSurfaceFrameStartEvent->set_gpu_composition(mGpuComposition);
         actualSurfaceFrameStartEvent->set_jank_type(jankTypeBitmaskToProto(mJankType));
+        actualSurfaceFrameStartEvent->set_prediction_type(toProto(mPredictionState));
     });
 
     // Actual timeline end
@@ -675,13 +710,14 @@
 }
 
 FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
-                             JankClassificationThresholds thresholds)
+                             JankClassificationThresholds thresholds, nsecs_t hwcDuration)
       : mMaxDisplayFrames(kDefaultMaxDisplayFrames),
         mTimeStats(std::move(timeStats)),
         mSurfaceFlingerPid(surfaceFlingerPid),
-        mJankClassificationThresholds(thresholds) {
-    mCurrentDisplayFrame =
-            std::make_shared<DisplayFrame>(mTimeStats, thresholds, &mTraceCookieCounter);
+        mJankClassificationThresholds(thresholds),
+        mHwcDuration(hwcDuration) {
+    mCurrentDisplayFrame = std::make_shared<DisplayFrame>(mTimeStats, thresholds, hwcDuration,
+                                                          &mTraceCookieCounter);
 }
 
 void FrameTimeline::onBootFinished() {
@@ -698,11 +734,11 @@
 }
 
 std::shared_ptr<SurfaceFrame> FrameTimeline::createSurfaceFrameForToken(
-        const FrameTimelineInfo& frameTimelineInfo, pid_t ownerPid, uid_t ownerUid,
+        const FrameTimelineInfo& frameTimelineInfo, pid_t ownerPid, uid_t ownerUid, int32_t layerId,
         std::string layerName, std::string debugName) {
     ATRACE_CALL();
     if (frameTimelineInfo.vsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) {
-        return std::make_shared<SurfaceFrame>(frameTimelineInfo, ownerPid, ownerUid,
+        return std::make_shared<SurfaceFrame>(frameTimelineInfo, ownerPid, ownerUid, layerId,
                                               std::move(layerName), std::move(debugName),
                                               PredictionState::None, TimelineItem(), mTimeStats,
                                               mJankClassificationThresholds, &mTraceCookieCounter);
@@ -710,13 +746,13 @@
     std::optional<TimelineItem> predictions =
             mTokenManager.getPredictionsForToken(frameTimelineInfo.vsyncId);
     if (predictions) {
-        return std::make_shared<SurfaceFrame>(frameTimelineInfo, ownerPid, ownerUid,
+        return std::make_shared<SurfaceFrame>(frameTimelineInfo, ownerPid, ownerUid, layerId,
                                               std::move(layerName), std::move(debugName),
                                               PredictionState::Valid, std::move(*predictions),
                                               mTimeStats, mJankClassificationThresholds,
                                               &mTraceCookieCounter);
     }
-    return std::make_shared<SurfaceFrame>(frameTimelineInfo, ownerPid, ownerUid,
+    return std::make_shared<SurfaceFrame>(frameTimelineInfo, ownerPid, ownerUid, layerId,
                                           std::move(layerName), std::move(debugName),
                                           PredictionState::Expired, TimelineItem(), mTimeStats,
                                           mJankClassificationThresholds, &mTraceCookieCounter);
@@ -724,11 +760,13 @@
 
 FrameTimeline::DisplayFrame::DisplayFrame(std::shared_ptr<TimeStats> timeStats,
                                           JankClassificationThresholds thresholds,
+                                          nsecs_t hwcDuration,
                                           TraceCookieCounter* traceCookieCounter)
       : mSurfaceFlingerPredictions(TimelineItem()),
         mSurfaceFlingerActuals(TimelineItem()),
         mTimeStats(timeStats),
         mJankClassificationThresholds(thresholds),
+        mHwcDuration(hwcDuration),
         mTraceCookieCounter(*traceCookieCounter) {
     mSurfaceFrames.reserve(kNumSurfaceFramesInitial);
 }
@@ -800,11 +838,13 @@
     const nsecs_t presentDelta =
             mSurfaceFlingerActuals.presentTime - mSurfaceFlingerPredictions.presentTime;
     const nsecs_t deadlineDelta =
-            mSurfaceFlingerActuals.endTime - mSurfaceFlingerPredictions.endTime;
+            mSurfaceFlingerActuals.endTime - (mSurfaceFlingerPredictions.endTime - mHwcDuration);
 
     // How far off was the presentDelta when compared to the vsyncPeriod. Used in checking if there
     // was a prediction error or not.
-    nsecs_t deltaToVsync = std::abs(presentDelta) % mRefreshRate.getPeriodNsecs();
+    nsecs_t deltaToVsync = mRefreshRate.getPeriodNsecs() > 0
+            ? std::abs(presentDelta) % mRefreshRate.getPeriodNsecs()
+            : 0;
     if (std::abs(presentDelta) > mJankClassificationThresholds.presentThreshold) {
         mFramePresentMetadata = presentDelta > 0 ? FramePresentMetadata::LatePresent
                                                  : FramePresentMetadata::EarlyPresent;
@@ -812,8 +852,9 @@
         mFramePresentMetadata = FramePresentMetadata::OnTimePresent;
     }
 
-    if (mSurfaceFlingerActuals.endTime - mSurfaceFlingerPredictions.endTime >
-        mJankClassificationThresholds.deadlineThreshold) {
+    if (mSurfaceFlingerActuals.endTime > mSurfaceFlingerPredictions.endTime - mHwcDuration) {
+        // SF needs to have finished at least mHwcDuration ahead of the deadline for it to be
+        // on time.
         mFrameReadyMetadata = FrameReadyMetadata::LateFinish;
     } else {
         mFrameReadyMetadata = FrameReadyMetadata::OnTimeFinish;
@@ -865,8 +906,13 @@
                     mJankType = JankType::PredictionError;
                 }
             } else if (mFrameReadyMetadata == FrameReadyMetadata::LateFinish) {
-                // Finish late, Present late
-                mJankType = JankType::SurfaceFlingerCpuDeadlineMissed;
+                if (mFrameStartMetadata == FrameStartMetadata::LateStart) {
+                    // Late start, Late finish, Late Present
+                    mJankType = JankType::SurfaceFlingerScheduling;
+                } else {
+                    // OnTime start, Finish late, Present late
+                    mJankType = JankType::SurfaceFlingerCpuDeadlineMissed;
+                }
             } else {
                 // Finish time unknown
                 mJankType = JankType::Unknown;
@@ -934,13 +980,14 @@
                                                          FrameReadyMetadata::OnTimeFinish);
         actualDisplayFrameStartEvent->set_gpu_composition(mGpuComposition);
         actualDisplayFrameStartEvent->set_jank_type(jankTypeBitmaskToProto(mJankType));
+        actualDisplayFrameStartEvent->set_prediction_type(toProto(mPredictionState));
     });
 
     // Actual timeline end
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
         auto packet = ctx.NewTracePacket();
         packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
-        packet->set_timestamp(static_cast<uint64_t>(mSurfaceFlingerActuals.endTime));
+        packet->set_timestamp(static_cast<uint64_t>(mSurfaceFlingerActuals.presentTime));
 
         auto* event = packet->set_frame_timeline_event();
         auto* actualDisplayFrameEndEvent = event->set_frame_end();
@@ -968,6 +1015,65 @@
     }
 }
 
+float FrameTimeline::computeFps(const std::unordered_set<int32_t>& layerIds) {
+    if (layerIds.empty()) {
+        return 0.0f;
+    }
+
+    std::vector<nsecs_t> presentTimes;
+    {
+        std::scoped_lock lock(mMutex);
+        presentTimes.reserve(mDisplayFrames.size());
+        for (size_t i = 0; i < mDisplayFrames.size(); i++) {
+            const auto& displayFrame = mDisplayFrames[i];
+            if (displayFrame->getActuals().presentTime <= 0) {
+                continue;
+            }
+            for (const auto& surfaceFrame : displayFrame->getSurfaceFrames()) {
+                if (surfaceFrame->getPresentState() == SurfaceFrame::PresentState::Presented &&
+                    layerIds.count(surfaceFrame->getLayerId()) > 0) {
+                    // We're looking for DisplayFrames that presents at least one layer from
+                    // layerIds, so push the present time and skip looking through the rest of the
+                    // SurfaceFrames.
+                    presentTimes.push_back(displayFrame->getActuals().presentTime);
+                    break;
+                }
+            }
+        }
+    }
+
+    // FPS can't be computed when there's fewer than 2 presented frames.
+    if (presentTimes.size() <= 1) {
+        return 0.0f;
+    }
+
+    nsecs_t priorPresentTime = -1;
+    nsecs_t totalPresentToPresentWalls = 0;
+
+    for (const nsecs_t presentTime : presentTimes) {
+        if (priorPresentTime == -1) {
+            priorPresentTime = presentTime;
+            continue;
+        }
+
+        totalPresentToPresentWalls += (presentTime - priorPresentTime);
+        priorPresentTime = presentTime;
+    }
+
+    if (CC_UNLIKELY(totalPresentToPresentWalls <= 0)) {
+        ALOGW("Invalid total present-to-present duration when computing fps: %" PRId64,
+              totalPresentToPresentWalls);
+        return 0.0f;
+    }
+
+    const constexpr nsecs_t kOneSecond =
+            std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
+    // (10^9 nanoseconds / second) * (N present deltas) / (total nanoseconds in N present deltas) =
+    // M frames / second
+    return kOneSecond * static_cast<nsecs_t>((presentTimes.size() - 1)) /
+            static_cast<float>(totalPresentToPresentWalls);
+}
+
 void FrameTimeline::flushPendingPresentFences() {
     for (size_t i = 0; i < mPendingPresentFences.size(); i++) {
         const auto& pendingPresentFence = mPendingPresentFences[i];
@@ -997,7 +1103,7 @@
     mDisplayFrames.push_back(mCurrentDisplayFrame);
     mCurrentDisplayFrame.reset();
     mCurrentDisplayFrame = std::make_shared<DisplayFrame>(mTimeStats, mJankClassificationThresholds,
-                                                          &mTraceCookieCounter);
+                                                          mHwcDuration, &mTraceCookieCounter);
 }
 
 nsecs_t FrameTimeline::DisplayFrame::getBaseTime() const {
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index 8f3157d..7c6a0cc 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -156,9 +156,10 @@
     // Only FrameTimeline can construct a SurfaceFrame as it provides Predictions(through
     // TokenManager), Thresholds and TimeStats pointer.
     SurfaceFrame(const FrameTimelineInfo& frameTimelineInfo, pid_t ownerPid, uid_t ownerUid,
-                 std::string layerName, std::string debugName, PredictionState predictionState,
-                 TimelineItem&& predictions, std::shared_ptr<TimeStats> timeStats,
-                 JankClassificationThresholds thresholds, TraceCookieCounter* traceCookieCounter);
+                 int32_t layerId, std::string layerName, std::string debugName,
+                 PredictionState predictionState, TimelineItem&& predictions,
+                 std::shared_ptr<TimeStats> timeStats, JankClassificationThresholds thresholds,
+                 TraceCookieCounter* traceCookieCounter);
     ~SurfaceFrame() = default;
 
     // Returns std::nullopt if the frame hasn't been classified yet.
@@ -199,6 +200,7 @@
     // Getter functions used only by FrameTimelineTests and SurfaceFrame internally
     TimelineItem getActuals() const;
     pid_t getOwnerPid() const { return mOwnerPid; };
+    int32_t getLayerId() const { return mLayerId; };
     PredictionState getPredictionState() const;
     PresentState getPresentState() const;
     FrameReadyMetadata getFrameReadyMetadata() const;
@@ -221,6 +223,7 @@
     const uid_t mOwnerUid;
     const std::string mLayerName;
     const std::string mDebugName;
+    const int32_t mLayerId;
     PresentState mPresentState GUARDED_BY(mMutex);
     const PredictionState mPredictionState;
     const TimelineItem mPredictions;
@@ -267,7 +270,7 @@
     // Debug name is the human-readable debugging string for dumpsys.
     virtual std::shared_ptr<SurfaceFrame> createSurfaceFrameForToken(
             const FrameTimelineInfo& frameTimelineInfo, pid_t ownerPid, uid_t ownerUid,
-            std::string layerName, std::string debugName) = 0;
+            int32_t layerId, std::string layerName, std::string debugName) = 0;
 
     // Adds a new SurfaceFrame to the current DisplayFrame. Frames from multiple layers can be
     // composited into one display frame.
@@ -292,6 +295,11 @@
     // Sets the max number of display frames that can be stored. Called by SF backdoor.
     virtual void setMaxDisplayFrames(uint32_t size);
 
+    // Computes the historical fps for the provided set of layer IDs
+    // The fps is compted from the linear timeline of present timestamps for DisplayFrames
+    // containing at least one layer ID.
+    virtual float computeFps(const std::unordered_set<int32_t>& layerIds);
+
     // Restores the max number of display frames to default. Called by SF backdoor.
     virtual void reset() = 0;
 };
@@ -334,7 +342,7 @@
     class DisplayFrame {
     public:
         DisplayFrame(std::shared_ptr<TimeStats> timeStats, JankClassificationThresholds thresholds,
-                     TraceCookieCounter* traceCookieCounter);
+                     nsecs_t hwcDuration, TraceCookieCounter* traceCookieCounter);
         virtual ~DisplayFrame() = default;
         // Dumpsys interface - dumps only if the DisplayFrame itself is janky or is at least one
         // SurfaceFrame is janky.
@@ -363,6 +371,7 @@
         // Functions to be used only in testing.
         TimelineItem getActuals() const { return mSurfaceFlingerActuals; };
         TimelineItem getPredictions() const { return mSurfaceFlingerPredictions; };
+        FrameStartMetadata getFrameStartMetadata() const { return mFrameStartMetadata; };
         FramePresentMetadata getFramePresentMetadata() const { return mFramePresentMetadata; };
         FrameReadyMetadata getFrameReadyMetadata() const { return mFrameReadyMetadata; };
         int32_t getJankType() const { return mJankType; }
@@ -386,6 +395,7 @@
         TimelineItem mSurfaceFlingerActuals;
         std::shared_ptr<TimeStats> mTimeStats;
         const JankClassificationThresholds mJankClassificationThresholds;
+        const nsecs_t mHwcDuration;
 
         // Collection of predictions and actual values sent over by Layers
         std::vector<std::shared_ptr<SurfaceFrame>> mSurfaceFrames;
@@ -411,19 +421,21 @@
     };
 
     FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
-                  JankClassificationThresholds thresholds = {});
+                  JankClassificationThresholds thresholds = {},
+                  nsecs_t hwcDuration = kDefaultHwcDuration);
     ~FrameTimeline() = default;
 
     frametimeline::TokenManager* getTokenManager() override { return &mTokenManager; }
     std::shared_ptr<SurfaceFrame> createSurfaceFrameForToken(
             const FrameTimelineInfo& frameTimelineInfo, pid_t ownerPid, uid_t ownerUid,
-            std::string layerName, std::string debugName) override;
+            int32_t layerId, std::string layerName, std::string debugName) override;
     void addSurfaceFrame(std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame) override;
     void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate) override;
     void setSfPresent(nsecs_t sfPresentTime,
                       const std::shared_ptr<FenceTime>& presentFence) override;
     void parseArgs(const Vector<String16>& args, std::string& result) override;
     void setMaxDisplayFrames(uint32_t size) override;
+    float computeFps(const std::unordered_set<int32_t>& layerIds) override;
     void reset() override;
 
     // Sets up the perfetto tracing backend and data source.
@@ -455,6 +467,11 @@
     std::shared_ptr<TimeStats> mTimeStats;
     const pid_t mSurfaceFlingerPid;
     const JankClassificationThresholds mJankClassificationThresholds;
+    // In SF Predictions, both end & present are the same. The predictions consider the time used by
+    // composer as well, but we have no way to estimate how much time the composer needs. We are
+    // assuming an arbitrary time for the composer work.
+    const nsecs_t mHwcDuration;
+    static constexpr nsecs_t kDefaultHwcDuration = std::chrono::nanoseconds(3ms).count();
     static constexpr uint32_t kDefaultMaxDisplayFrames = 64;
     // The initial container size for the vector<SurfaceFrames> inside display frame. Although
     // this number doesn't represent any bounds on the number of surface frames that can go in a
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 6a03996..061ad0e 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -489,7 +489,7 @@
     compositionState->alpha = alpha;
     compositionState->backgroundBlurRadius = drawingState.backgroundBlurRadius;
     compositionState->blurRegions = drawingState.blurRegions;
-    compositionState->stretchEffect = drawingState.stretchEffect;
+    compositionState->stretchEffect = getStretchEffect();
 }
 
 void Layer::prepareGeometryCompositionState() {
@@ -559,7 +559,7 @@
 
     // Force client composition for special cases known only to the front-end.
     if (isHdrY410() || usesRoundedCorners || drawShadows() || drawingState.blurRegions.size() > 0 ||
-        drawingState.stretchEffect.hasEffect()) {
+        compositionState->stretchEffect.hasEffect()) {
         compositionState->forceClientComposition = true;
     }
 }
@@ -1339,7 +1339,6 @@
 
 bool Layer::setMetadata(const LayerMetadata& data) {
     if (!mCurrentState.metadata.merge(data, true /* eraseEmpty */)) return false;
-    mCurrentState.sequence++;
     mCurrentState.modified = true;
     setTransactionFlags(eTransactionNeeded);
     return true;
@@ -1437,6 +1436,22 @@
     return true;
 }
 
+StretchEffect Layer::getStretchEffect() const {
+    if (mDrawingState.stretchEffect.hasEffect()) {
+        return mDrawingState.stretchEffect;
+    }
+
+    sp<Layer> parent = getParent();
+    if (parent != nullptr) {
+        auto effect = parent->getStretchEffect();
+        if (effect.hasEffect()) {
+            // TODO(b/179047472): Map it? Or do we make the effect be in global space?
+            return effect;
+        }
+    }
+    return StretchEffect{};
+}
+
 void Layer::updateTreeHasFrameRateVote() {
     const auto traverseTree = [&](const LayerVector::Visitor& visitor) {
         auto parent = getParent();
@@ -1452,7 +1467,7 @@
     // First traverse the tree and count how many layers has votes. In addition
     // activate the layers in Scheduler's LayerHistory for it to check for changes
     int layersWithVote = 0;
-    traverseTree([&layersWithVote, this](Layer* layer) {
+    traverseTree([&layersWithVote](Layer* layer) {
         const auto layerVotedWithDefaultCompatibility =
                 layer->mCurrentState.frameRate.rate.isValid() &&
                 layer->mCurrentState.frameRate.type == FrameRateCompatibility::Default;
@@ -1468,20 +1483,21 @@
             layerVotedWithExactCompatibility) {
             layersWithVote++;
         }
-
-        mFlinger->mScheduler->recordLayerHistory(layer, systemTime(),
-                                                 LayerHistory::LayerUpdateType::SetFrameRate);
     });
 
     // Now update the other layers
     bool transactionNeeded = false;
-    traverseTree([layersWithVote, &transactionNeeded](Layer* layer) {
-        if (layer->mCurrentState.treeHasFrameRateVote != layersWithVote > 0) {
+    traverseTree([layersWithVote, &transactionNeeded, this](Layer* layer) {
+        const bool treeHasFrameRateVote = layersWithVote > 0;
+        if (layer->mCurrentState.treeHasFrameRateVote != treeHasFrameRateVote) {
             layer->mCurrentState.sequence++;
-            layer->mCurrentState.treeHasFrameRateVote = layersWithVote > 0;
+            layer->mCurrentState.treeHasFrameRateVote = treeHasFrameRateVote;
             layer->mCurrentState.modified = true;
             layer->setTransactionFlags(eTransactionNeeded);
             transactionNeeded = true;
+
+            mFlinger->mScheduler->recordLayerHistory(layer, systemTime(),
+                                                     LayerHistory::LayerUpdateType::SetFrameRate);
         }
     });
 
@@ -1578,7 +1594,8 @@
 std::shared_ptr<frametimeline::SurfaceFrame> Layer::createSurfaceFrameForTransaction(
         const FrameTimelineInfo& info, nsecs_t postTime) {
     auto surfaceFrame =
-            mFlinger->mFrameTimeline->createSurfaceFrameForToken(info, mOwnerPid, mOwnerUid, mName,
+            mFlinger->mFrameTimeline->createSurfaceFrameForToken(info, mOwnerPid, mOwnerUid,
+                                                                 getSequence(), mName,
                                                                  mTransactionName);
     // For Transactions, the post time is considered to be both queue and acquire fence time.
     surfaceFrame->setActualQueueTime(postTime);
@@ -1594,8 +1611,8 @@
 std::shared_ptr<frametimeline::SurfaceFrame> Layer::createSurfaceFrameForBuffer(
         const FrameTimelineInfo& info, nsecs_t queueTime, std::string debugName) {
     auto surfaceFrame =
-            mFlinger->mFrameTimeline->createSurfaceFrameForToken(info, mOwnerPid, mOwnerUid, mName,
-                                                                 debugName);
+            mFlinger->mFrameTimeline->createSurfaceFrameForToken(info, mOwnerPid, mOwnerUid,
+                                                                 getSequence(), mName, debugName);
     // For buffers, acquire fence time will set during latch.
     surfaceFrame->setActualQueueTime(queueTime);
     const auto fps = mFlinger->mScheduler->getFrameRateOverride(getOwnerUid());
@@ -1742,6 +1759,7 @@
     info.mRefreshPending = isBufferLatched();
     info.mIsOpaque = isOpaque(ds);
     info.mContentDirty = contentDirty;
+    info.mStretchEffect = getStretchEffect();
     return info;
 }
 
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 1461756..ce97155 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -501,8 +501,7 @@
     // one empty rect.
     virtual void useSurfaceDamage() {}
     virtual void useEmptyDamage() {}
-
-    virtual void incrementPendingBufferCount() {}
+    Region getVisibleRegion(const DisplayDevice*) const;
 
     /*
      * isOpaque - true if this surface is opaque
@@ -723,7 +722,7 @@
     // Returns the bounds of the layer without any buffer scaling.
     FloatRect getBoundsPreScaling(const ui::Transform& bufferScaleTransform) const;
 
-    int32_t getSequence() const { return sequence; }
+    int32_t getSequence() const override { return sequence; }
 
     // For tracing.
     // TODO: Replace with raw buffer id from buffer metadata when that becomes available.
@@ -923,6 +922,8 @@
 
     pid_t getOwnerPid() { return mOwnerPid; }
 
+    virtual bool frameIsEarly(nsecs_t /*expectedPresentTime*/) const { return false; }
+
     // This layer is not a clone, but it's the parent to the cloned hierarchy. The
     // variable mClonedChild represents the top layer that will be cloned so this
     // layer will be the parent of mClonedChild.
@@ -943,6 +944,10 @@
     bool backpressureEnabled() { return mDrawingState.flags & layer_state_t::eEnableBackpressure; }
 
     bool setStretchEffect(const StretchEffect& effect);
+    StretchEffect getStretchEffect() const;
+
+    virtual std::atomic<int32_t>* getPendingBufferCounter() { return nullptr; }
+    virtual std::string getPendingBufferCounterName() { return ""; }
 
 protected:
     class SyncPoint {
@@ -1008,6 +1013,7 @@
 
     // For unit tests
     friend class TestableSurfaceFlinger;
+    friend class FpsReporterTest;
     friend class RefreshRateSelectionTest;
     friend class SetFrameRateTest;
     friend class TransactionFrameTracerTest;
@@ -1164,7 +1170,6 @@
     virtual bool canDrawShadows() const { return true; }
 
     Hwc2::IComposerClient::Composition getCompositionType(const DisplayDevice&) const;
-    Region getVisibleRegion(const DisplayDevice*) const;
 
     /**
      * Returns an unsorted vector of all layers that are part of this tree.
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 09615f9..e06bc88 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -481,6 +481,12 @@
     // 1) The region sampling thread is the last owner of the buffer, and the freeing of the buffer
     // happens in this thread, as opposed to the main thread.
     // 2) The listener(s) receive their notifications prior to freeing the buffer.
+    if (mCachedBuffer != nullptr && mCachedBuffer != buffer) {
+        if (mFlinger.getRenderEngine().getRenderEngineType() ==
+            renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED) {
+            mFlinger.getRenderEngine().unbindExternalTextureBuffer(mCachedBuffer->getId());
+        }
+    }
     mCachedBuffer = buffer;
     ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::noWorkNeeded));
 }
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index a03f793..91e8043 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -64,7 +64,7 @@
 using RefreshRate = RefreshRateConfigs::RefreshRate;
 
 std::string RefreshRate::toString() const {
-    return base::StringPrintf("{id=%zu, hwcId=%d, fps=%.2f, width=%d, height=%d group=%d}",
+    return base::StringPrintf("{id=%d, hwcId=%d, fps=%.2f, width=%d, height=%d group=%d}",
                               getModeId().value(), mode->getHwcId(), getFps().getValue(),
                               mode->getWidth(), mode->getHeight(), getModeGroup());
 }
@@ -89,7 +89,7 @@
 }
 
 std::string RefreshRateConfigs::Policy::toString() const {
-    return base::StringPrintf("default mode ID: %zu, allowGroupSwitching = %d"
+    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());
@@ -610,15 +610,16 @@
 void RefreshRateConfigs::updateDisplayModes(const DisplayModes& modes,
                                             DisplayModeId currentModeId) {
     std::lock_guard lock(mLock);
-    LOG_ALWAYS_FATAL_IF(modes.empty());
-    LOG_ALWAYS_FATAL_IF(currentModeId.value() >= modes.size());
+    // The current mode should be supported
+    LOG_ALWAYS_FATAL_IF(std::none_of(modes.begin(), modes.end(), [&](DisplayModePtr mode) {
+        return mode->getId() == currentModeId;
+    }));
 
     mRefreshRates.clear();
     for (const auto& mode : modes) {
         const auto modeId = mode->getId();
-        const auto fps = Fps::fromPeriodNsecs(mode->getVsyncPeriod());
         mRefreshRates.emplace(modeId,
-                              std::make_unique<RefreshRate>(modeId, mode, fps,
+                              std::make_unique<RefreshRate>(modeId, mode, mode->getFps(),
                                                             RefreshRate::ConstructorTag(0)));
         if (modeId == currentModeId) {
             mCurrentRefreshRate = mRefreshRates.at(modeId).get();
@@ -724,7 +725,7 @@
     outRefreshRates->reserve(mRefreshRates.size());
     for (const auto& [type, refreshRate] : mRefreshRates) {
         if (shouldAddRefreshRate(*refreshRate)) {
-            ALOGV("getSortedRefreshRateListLocked: mode %zu added to list policy",
+            ALOGV("getSortedRefreshRateListLocked: mode %d added to list policy",
                   refreshRate->modeId.value());
             outRefreshRates->push_back(refreshRate.get());
         }
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 70e4eb4..727386c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -68,12 +68,13 @@
 #include <ui/ColorSpace.h>
 #include <ui/DebugUtils.h>
 #include <ui/DisplayId.h>
-#include <ui/DisplayInfo.h>
 #include <ui/DisplayMode.h>
 #include <ui/DisplayStatInfo.h>
 #include <ui/DisplayState.h>
+#include <ui/DynamicDisplayInfo.h>
 #include <ui/GraphicBufferAllocator.h>
 #include <ui/PixelFormat.h>
+#include <ui/StaticDisplayInfo.h>
 #include <utils/StopWatch.h>
 #include <utils/String16.h>
 #include <utils/String8.h>
@@ -103,10 +104,12 @@
 #include "DisplayHardware/DisplayIdentification.h"
 #include "DisplayHardware/FramebufferSurface.h"
 #include "DisplayHardware/HWComposer.h"
+#include "DisplayHardware/Hal.h"
 #include "DisplayHardware/VirtualDisplaySurface.h"
 #include "DisplayRenderArea.h"
 #include "EffectLayer.h"
 #include "Effects/Daltonizer.h"
+#include "FpsReporter.h"
 #include "FrameTimeline/FrameTimeline.h"
 #include "FrameTracer/FrameTracer.h"
 #include "Layer.h"
@@ -129,6 +132,7 @@
 #include "TimeStats/TimeStats.h"
 #include "android-base/parseint.h"
 #include "android-base/stringprintf.h"
+#include "android-base/strings.h"
 
 #define MAIN_THREAD ACQUIRE(mStateLock) RELEASE(mStateLock)
 
@@ -253,6 +257,11 @@
     std::function<void()> mCallback;
 };
 
+enum Permission {
+    ACCESS_SURFACE_FLINGER = 0x1,
+    ROTATE_SURFACE_FLINGER = 0x2,
+};
+
 }  // namespace anonymous
 
 struct SetInputWindowsListener : os::BnSetInputWindowsListener {
@@ -489,6 +498,9 @@
     // the window manager died on us. prepare its eulogy.
     mBootFinished = false;
 
+    // Sever the link to inputflinger since its gone as well.
+    static_cast<void>(schedule([=] { mInputFlinger = nullptr; }));
+
     // restore initial conditions (default device unblank, etc)
     initializeDisplays();
 
@@ -854,7 +866,8 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::getDisplayInfo(const sp<IBinder>& displayToken, DisplayInfo* info) {
+status_t SurfaceFlinger::getStaticDisplayInfo(const sp<IBinder>& displayToken,
+                                              ui::StaticDisplayInfo* info) {
     if (!displayToken || !info) {
         return BAD_VALUE;
     }
@@ -875,7 +888,7 @@
     if (mEmulatedDisplayDensity) {
         info->density = mEmulatedDisplayDensity;
     } else {
-        info->density = info->connectionType == DisplayConnectionType::Internal
+        info->density = info->connectionType == ui::DisplayConnectionType::Internal
                 ? mInternalDisplayDensity
                 : FALLBACK_DENSITY;
     }
@@ -887,9 +900,9 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::getDisplayModes(const sp<IBinder>& displayToken,
-                                         Vector<ui::DisplayMode>* modes) {
-    if (!displayToken || !modes) {
+status_t SurfaceFlinger::getDynamicDisplayInfo(const sp<IBinder>& displayToken,
+                                               ui::DynamicDisplayInfo* info) {
+    if (!displayToken || !info) {
         return BAD_VALUE;
     }
 
@@ -900,16 +913,25 @@
         return NAME_NOT_FOUND;
     }
 
-    modes->clear();
+    info->activeDisplayModeId = static_cast<int32_t>(display->getActiveMode()->getId().value());
+    if (display->isPrimary()) {
+        if (const auto mode = getDesiredActiveMode()) {
+            info->activeDisplayModeId = static_cast<int32_t>(mode->modeId.value());
+        }
+    }
 
-    for (const auto& supportedMode : display->getSupportedModes()) {
-        ui::DisplayMode mode;
+    const auto& supportedModes = display->getSupportedModes();
+    info->supportedDisplayModes.clear();
+    info->supportedDisplayModes.reserve(supportedModes.size());
+    for (const auto& mode : supportedModes) {
+        ui::DisplayMode outMode;
+        outMode.id = static_cast<int32_t>(mode->getId().value());
 
-        auto width = supportedMode->getWidth();
-        auto height = supportedMode->getHeight();
+        auto width = mode->getWidth();
+        auto height = mode->getHeight();
 
-        auto xDpi = supportedMode->getDpiX();
-        auto yDpi = supportedMode->getDpiY();
+        auto xDpi = mode->getDpiX();
+        auto yDpi = mode->getDpiY();
 
         if (display->isPrimary() &&
             (internalDisplayOrientation == ui::ROTATION_90 ||
@@ -918,24 +940,24 @@
             std::swap(xDpi, yDpi);
         }
 
-        mode.resolution = ui::Size(width, height);
+        outMode.resolution = ui::Size(width, height);
 
         if (mEmulatedDisplayDensity) {
-            mode.xDpi = mEmulatedDisplayDensity;
-            mode.yDpi = mEmulatedDisplayDensity;
+            outMode.xDpi = mEmulatedDisplayDensity;
+            outMode.yDpi = mEmulatedDisplayDensity;
         } else {
-            mode.xDpi = xDpi;
-            mode.yDpi = yDpi;
+            outMode.xDpi = xDpi;
+            outMode.yDpi = yDpi;
         }
 
-        const nsecs_t period = supportedMode->getVsyncPeriod();
-        mode.refreshRate = Fps::fromPeriodNsecs(period).getValue();
+        const nsecs_t period = mode->getVsyncPeriod();
+        outMode.refreshRate = Fps::fromPeriodNsecs(period).getValue();
 
         const auto vsyncConfigSet =
-                mVsyncConfiguration->getConfigsForRefreshRate(Fps(mode.refreshRate));
-        mode.appVsyncOffset = vsyncConfigSet.late.appOffset;
-        mode.sfVsyncOffset = vsyncConfigSet.late.sfOffset;
-        mode.group = supportedMode->getGroup();
+                mVsyncConfiguration->getConfigsForRefreshRate(Fps(outMode.refreshRate));
+        outMode.appVsyncOffset = vsyncConfigSet.late.appOffset;
+        outMode.sfVsyncOffset = vsyncConfigSet.late.sfOffset;
+        outMode.group = mode->getGroup();
 
         // This is how far in advance a buffer must be queued for
         // presentation at a given time.  If you want a buffer to appear
@@ -949,11 +971,24 @@
         //
         // We add an additional 1ms to allow for processing time and
         // differences between the ideal and actual refresh rate.
-        mode.presentationDeadline = period - mode.sfVsyncOffset + 1000000;
+        outMode.presentationDeadline = period - outMode.sfVsyncOffset + 1000000;
 
-        modes->push_back(mode);
+        info->supportedDisplayModes.push_back(outMode);
     }
 
+    info->activeColorMode = display->getCompositionDisplay()->getState().colorMode;
+    const auto displayId = display->getPhysicalId();
+    info->supportedColorModes = getDisplayColorModes(displayId);
+
+    info->hdrCapabilities = display->getHdrCapabilities();
+    info->autoLowLatencyModeSupported =
+            getHwComposer().hasDisplayCapability(displayId,
+                                                 hal::DisplayCapability::AUTO_LOW_LATENCY_MODE);
+    std::vector<hal::ContentType> types;
+    getHwComposer().getSupportedContentTypes(displayId, &types);
+    info->gameContentTypeSupported = std::any_of(types.begin(), types.end(), [](auto type) {
+        return type == hal::ContentType::GAME;
+    });
     return NO_ERROR;
 }
 
@@ -966,31 +1001,6 @@
     return NO_ERROR;
 }
 
-int SurfaceFlinger::getActiveDisplayModeId(const sp<IBinder>& displayToken) {
-    int activeMode;
-    bool isPrimary;
-
-    {
-        Mutex::Autolock lock(mStateLock);
-
-        if (const auto display = getDisplayDeviceLocked(displayToken)) {
-            activeMode = display->getActiveMode()->getId().value();
-            isPrimary = display->isPrimary();
-        } else {
-            ALOGE("%s: Invalid display token %p", __FUNCTION__, displayToken.get());
-            return NAME_NOT_FOUND;
-        }
-    }
-
-    if (isPrimary) {
-        if (const auto mode = getDesiredActiveMode()) {
-            return mode->modeId.value();
-        }
-    }
-
-    return activeMode;
-}
-
 void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info) {
     ATRACE_CALL();
     auto refreshRate = mRefreshRateConfigs->getRefreshRateFromModeId(info.modeId);
@@ -1082,7 +1092,7 @@
 
     const auto upcomingMode = display->getMode(mUpcomingActiveMode.modeId);
     if (!upcomingMode) {
-        ALOGW("Upcoming active mode is no longer supported. Mode ID = %zu",
+        ALOGW("Upcoming active mode is no longer supported. Mode ID = %d",
               mUpcomingActiveMode.modeId.value());
         // TODO(b/159590486) Handle the error better. Some parts of SurfaceFlinger may
         // have been already updated with the upcoming active mode.
@@ -1142,13 +1152,13 @@
     const auto display = getDefaultDisplayDeviceLocked();
     const auto desiredMode = display->getMode(desiredActiveMode->modeId);
     if (!desiredMode) {
-        ALOGW("Desired display mode is no longer supported. Mode ID = %zu",
+        ALOGW("Desired display mode is no longer supported. Mode ID = %d",
               desiredActiveMode->modeId.value());
         clearDesiredActiveModeState();
         return;
     }
     const auto refreshRate = desiredMode->getFps();
-    ALOGV("%s changing active mode to %zu(%s)", __FUNCTION__, desiredMode->getId().value(),
+    ALOGV("%s changing active mode to %d(%s)", __FUNCTION__, desiredMode->getId().value(),
           to_string(refreshRate).c_str());
 
     if (!display || display->getActiveMode()->getId() == desiredActiveMode->modeId) {
@@ -1190,39 +1200,20 @@
     mSetActiveModePending = true;
 }
 
-status_t SurfaceFlinger::getDisplayColorModes(const sp<IBinder>& displayToken,
-                                              Vector<ColorMode>* outColorModes) {
-    if (!displayToken || !outColorModes) {
-        return BAD_VALUE;
-    }
-
-    std::vector<ColorMode> modes;
-    bool isInternalDisplay = false;
-    {
-        ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
-
-        const auto displayId = getPhysicalDisplayIdLocked(displayToken);
-        if (!displayId) {
-            return NAME_NOT_FOUND;
-        }
-
-        modes = getHwComposer().getColorModes(*displayId);
-        isInternalDisplay = displayId == getInternalDisplayIdLocked();
-    }
-    outColorModes->clear();
+std::vector<ColorMode> SurfaceFlinger::getDisplayColorModes(PhysicalDisplayId displayId) {
+    auto modes = getHwComposer().getColorModes(displayId);
+    bool isInternalDisplay = displayId == getInternalDisplayIdLocked();
 
     // If it's built-in display and the configuration claims it's not wide color capable,
     // filter out all wide color modes. The typical reason why this happens is that the
     // hardware is not good enough to support GPU composition of wide color, and thus the
     // OEMs choose to disable this capability.
     if (isInternalDisplay && !hasWideColorDisplay) {
-        std::remove_copy_if(modes.cbegin(), modes.cend(), std::back_inserter(*outColorModes),
-                            isWideColorMode);
-    } else {
-        std::copy(modes.cbegin(), modes.cend(), std::back_inserter(*outColorModes));
+        const auto newEnd = std::remove_if(modes.begin(), modes.end(), isWideColorMode);
+        modes.erase(newEnd, modes.end());
     }
 
-    return NO_ERROR;
+    return modes;
 }
 
 status_t SurfaceFlinger::getDisplayNativePrimaries(const sp<IBinder>& displayToken,
@@ -1240,19 +1231,14 @@
     return NO_ERROR;
 }
 
-ColorMode SurfaceFlinger::getActiveColorMode(const sp<IBinder>& displayToken) {
-    Mutex::Autolock lock(mStateLock);
-
-    if (const auto display = getDisplayDeviceLocked(displayToken)) {
-        return display->getCompositionDisplay()->getState().colorMode;
-    }
-    return static_cast<ColorMode>(BAD_VALUE);
-}
-
 status_t SurfaceFlinger::setActiveColorMode(const sp<IBinder>& displayToken, ColorMode mode) {
     schedule([=]() MAIN_THREAD {
-        Vector<ColorMode> modes;
-        getDisplayColorModes(displayToken, &modes);
+        const auto displayId = getPhysicalDisplayIdLocked(displayToken);
+        if (!displayId) {
+            ALOGE("Invalid display token %p", displayToken.get());
+            return;
+        }
+        const auto modes = getDisplayColorModes(*displayId);
         bool exists = std::find(std::begin(modes), std::end(modes), mode) != std::end(modes);
         if (mode < ColorMode::NATIVE || !exists) {
             ALOGE("Attempt to set invalid active color mode %s (%d) for display token %p",
@@ -1277,24 +1263,6 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::getAutoLowLatencyModeSupport(const sp<IBinder>& displayToken,
-                                                      bool* outSupport) const {
-    if (!displayToken) {
-        return BAD_VALUE;
-    }
-
-    Mutex::Autolock lock(mStateLock);
-
-    const auto displayId = getPhysicalDisplayIdLocked(displayToken);
-    if (!displayId) {
-        return NAME_NOT_FOUND;
-    }
-    *outSupport =
-            getHwComposer().hasDisplayCapability(*displayId,
-                                                 hal::DisplayCapability::AUTO_LOW_LATENCY_MODE);
-    return NO_ERROR;
-}
-
 void SurfaceFlinger::setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) {
     static_cast<void>(schedule([=]() MAIN_THREAD {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
@@ -1305,27 +1273,6 @@
     }));
 }
 
-status_t SurfaceFlinger::getGameContentTypeSupport(const sp<IBinder>& displayToken,
-                                                   bool* outSupport) const {
-    if (!displayToken) {
-        return BAD_VALUE;
-    }
-
-    Mutex::Autolock lock(mStateLock);
-
-    const auto displayId = getPhysicalDisplayIdLocked(displayToken);
-    if (!displayId) {
-        return NAME_NOT_FOUND;
-    }
-
-    std::vector<hal::ContentType> types;
-    getHwComposer().getSupportedContentTypes(*displayId, &types);
-
-    *outSupport = std::any_of(types.begin(), types.end(),
-                              [](auto type) { return type == hal::ContentType::GAME; });
-    return NO_ERROR;
-}
-
 void SurfaceFlinger::setGameContentType(const sp<IBinder>& displayToken, bool on) {
     static_cast<void>(schedule([=]() MAIN_THREAD {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
@@ -1349,28 +1296,6 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::getHdrCapabilities(const sp<IBinder>& displayToken,
-                                            HdrCapabilities* outCapabilities) const {
-    Mutex::Autolock lock(mStateLock);
-
-    const auto display = getDisplayDeviceLocked(displayToken);
-    if (!display) {
-        ALOGE("%s: Invalid display token %p", __FUNCTION__, displayToken.get());
-        return NAME_NOT_FOUND;
-    }
-
-    // At this point the DisplayDevice should already be set up,
-    // meaning the luminance information is already queried from
-    // hardware composer and stored properly.
-    const HdrCapabilities& capabilities = display->getHdrCapabilities();
-    *outCapabilities = HdrCapabilities(capabilities.getSupportedHdrTypes(),
-                                       capabilities.getDesiredMaxLuminance(),
-                                       capabilities.getDesiredMaxAverageLuminance(),
-                                       capabilities.getDesiredMinLuminance());
-
-    return NO_ERROR;
-}
-
 status_t SurfaceFlinger::getDisplayedContentSamplingAttributes(const sp<IBinder>& displayToken,
                                                                ui::PixelFormat* outFormat,
                                                                ui::Dataspace* outDataspace,
@@ -1508,6 +1433,23 @@
     return NO_ERROR;
 }
 
+status_t SurfaceFlinger::addFpsListener(int32_t taskId, const sp<gui::IFpsListener>& listener) {
+    if (!listener) {
+        return BAD_VALUE;
+    }
+
+    mFpsReporter->addListener(listener, taskId);
+    return NO_ERROR;
+}
+
+status_t SurfaceFlinger::removeFpsListener(const sp<gui::IFpsListener>& listener) {
+    if (!listener) {
+        return BAD_VALUE;
+    }
+    mFpsReporter->removeListener(listener);
+    return NO_ERROR;
+}
+
 status_t SurfaceFlinger::getDisplayBrightnessSupport(const sp<IBinder>& displayToken,
                                                      bool* outSupport) const {
     if (!displayToken || !outSupport) {
@@ -1640,7 +1582,7 @@
 
     // Don't do any updating if the current fps is the same as the new one.
     if (!isDisplayModeAllowed(refreshRate.getModeId())) {
-        ALOGV("Skipping mode %zu as it is not part of allowed modes",
+        ALOGV("Skipping mode %d as it is not part of allowed modes",
               refreshRate.getModeId().value());
         return;
     }
@@ -2194,6 +2136,13 @@
         }
     });
 
+    {
+        Mutex::Autolock lock(mStateLock);
+        if (mFpsReporter) {
+            mFpsReporter->dispatchLayerFps();
+        }
+    }
+
     mTransactionCallbackInvoker.addPresentFence(mPreviousPresentFences[0]);
     mTransactionCallbackInvoker.sendCallbacks();
 
@@ -2357,22 +2306,74 @@
     // here the transaction has been committed
 }
 
-DisplayModes SurfaceFlinger::loadSupportedDisplayModes(PhysicalDisplayId displayId) const {
-    const auto hwcModes = getHwComposer().getModes(displayId);
-    DisplayModes modes;
-    size_t nextModeId = 0;
-    for (const auto& hwcMode : hwcModes) {
-        modes.push_back(DisplayMode::Builder(hwcMode.hwcId)
-                                .setId(DisplayModeId{nextModeId++})
-                                .setWidth(hwcMode.width)
-                                .setHeight(hwcMode.height)
-                                .setVsyncPeriod(hwcMode.vsyncPeriod)
-                                .setDpiX(hwcMode.dpiX)
-                                .setDpiY(hwcMode.dpiY)
-                                .setGroup(hwcMode.configGroup)
-                                .build());
+void SurfaceFlinger::loadDisplayModes(PhysicalDisplayId displayId, DisplayModes& outModes,
+                                      DisplayModePtr& outActiveMode) const {
+    std::vector<HWComposer::HWCDisplayMode> hwcModes;
+    std::optional<hal::HWDisplayId> activeModeHwcId;
+    bool activeModeIsSupported;
+    int attempt = 0;
+    constexpr int kMaxAttempts = 3;
+    do {
+        hwcModes = getHwComposer().getModes(displayId);
+        activeModeHwcId = getHwComposer().getActiveMode(displayId);
+        LOG_ALWAYS_FATAL_IF(!activeModeHwcId, "HWC returned no active mode");
+
+        activeModeIsSupported =
+                std::any_of(hwcModes.begin(), hwcModes.end(),
+                            [activeModeHwcId](const HWComposer::HWCDisplayMode& mode) {
+                                return mode.hwcId == *activeModeHwcId;
+                            });
+    } while (!activeModeIsSupported && ++attempt < kMaxAttempts);
+    LOG_ALWAYS_FATAL_IF(!activeModeIsSupported,
+                        "After %d attempts HWC still returns an active mode which is not"
+                        " supported. Active mode ID = %" PRIu64 " . Supported modes = %s",
+                        kMaxAttempts, *activeModeHwcId, base::Join(hwcModes, ", ").c_str());
+
+    DisplayModes oldModes;
+
+    if (const auto token = getPhysicalDisplayTokenLocked(displayId)) {
+        oldModes = getDisplayDeviceLocked(token)->getSupportedModes();
     }
-    return modes;
+
+    int largestUsedModeId = -1; // Use int instead of DisplayModeId for signedness
+    for (const auto& mode : oldModes) {
+        const auto id = static_cast<int>(mode->getId().value());
+        if (id > largestUsedModeId) {
+            largestUsedModeId = id;
+        }
+    }
+
+    DisplayModes newModes;
+    int32_t nextModeId = largestUsedModeId + 1;
+    for (const auto& hwcMode : hwcModes) {
+        newModes.push_back(DisplayMode::Builder(hwcMode.hwcId)
+                                   .setId(DisplayModeId{nextModeId++})
+                                   .setWidth(hwcMode.width)
+                                   .setHeight(hwcMode.height)
+                                   .setVsyncPeriod(hwcMode.vsyncPeriod)
+                                   .setDpiX(hwcMode.dpiX)
+                                   .setDpiY(hwcMode.dpiY)
+                                   .setGroup(hwcMode.configGroup)
+                                   .build());
+    }
+
+    const bool modesAreSame =
+            std::equal(newModes.begin(), newModes.end(), oldModes.begin(), oldModes.end(),
+                       [](DisplayModePtr left, DisplayModePtr right) {
+                           return left->equalsExceptDisplayModeId(right);
+                       });
+
+    if (modesAreSame) {
+        // The supported modes have not changed, keep the old IDs.
+        outModes = oldModes;
+    } else {
+        outModes = newModes;
+    }
+
+    outActiveMode = *std::find_if(outModes.begin(), outModes.end(),
+                                  [activeModeHwcId](const DisplayModePtr& mode) {
+                                      return mode->getHwcId() == *activeModeHwcId;
+                                  });
 }
 
 void SurfaceFlinger::processDisplayHotplugEventsLocked() {
@@ -2388,15 +2389,9 @@
         const auto it = mPhysicalDisplayTokens.find(displayId);
 
         if (event.connection == hal::Connection::CONNECTED) {
-            auto supportedModes = loadSupportedDisplayModes(displayId);
-            const auto activeModeHwcId = getHwComposer().getActiveMode(displayId);
-            LOG_ALWAYS_FATAL_IF(!activeModeHwcId, "HWC returned no active mode");
-
-            const auto activeMode = *std::find_if(supportedModes.begin(), supportedModes.end(),
-                                                  [activeModeHwcId](const DisplayModePtr& mode) {
-                                                      return mode->getHwcId() == *activeModeHwcId;
-                                                  });
-            // TODO(b/175678215) Handle the case when activeMode is not in supportedModes
+            DisplayModes supportedModes;
+            DisplayModePtr activeMode;
+            loadDisplayModes(displayId, supportedModes, activeMode);
 
             if (it == mPhysicalDisplayTokens.end()) {
                 ALOGV("Creating display %s", to_string(displayId).c_str());
@@ -2465,7 +2460,6 @@
         const sp<IGraphicBufferProducer>& producer) {
     DisplayDeviceCreationArgs creationArgs(this, getHwComposer(), displayToken, compositionDisplay);
     creationArgs.sequenceId = state.sequenceId;
-    creationArgs.hwComposer = getHwComposer();
     creationArgs.isSecure = state.isSecure;
     creationArgs.displaySurface = displaySurface;
     creationArgs.hasWideColorGamut = false;
@@ -3010,6 +3004,7 @@
     mRegionSamplingThread =
             new RegionSamplingThread(*this, *mScheduler,
                                      RegionSamplingThread::EnvironmentTimingTunables());
+    mFpsReporter = new FpsReporter(*mFrameTimeline, *this);
     // Dispatch a mode change request for the primary display on scheduler
     // initialization, so that the EventThreads always contain a reference to a
     // prior configuration.
@@ -3321,7 +3316,6 @@
                     if (!transactionIsReadyToBeApplied(transaction.isAutoTimestamp,
                                                        transaction.desiredPresentTime,
                                                        transaction.states,
-                                                       false /* updateTransactionCounters*/,
                                                        pendingBuffers)) {
                         setTransactionFlags(eTransactionFlushNeeded);
                         break;
@@ -3346,13 +3340,9 @@
                 const auto& transaction = mTransactionQueue.front();
                 bool pendingTransactions = mPendingTransactionQueues.find(transaction.applyToken) !=
                         mPendingTransactionQueues.end();
-                // Call transactionIsReadyToBeApplied first in case we need to
-                // incrementPendingBufferCount and keep track of pending buffers
-                // if the transaction contains a buffer.
                 if (!transactionIsReadyToBeApplied(transaction.isAutoTimestamp,
                                                    transaction.desiredPresentTime,
                                                    transaction.states,
-                                                   true /* updateTransactionCounters */,
                                                    pendingBuffers) ||
                     pendingTransactions) {
                     mPendingTransactionQueues[transaction.applyToken].push(transaction);
@@ -3360,6 +3350,7 @@
                     transactions.push_back(transaction);
                 }
                 mTransactionQueue.pop();
+                ATRACE_INT("TransactionQueue", mTransactionQueue.size());
             }
         }
 
@@ -3369,7 +3360,7 @@
                                   transaction.displays, transaction.flags,
                                   transaction.inputWindowCommands, transaction.desiredPresentTime,
                                   transaction.isAutoTimestamp, transaction.buffer,
-                                  transaction.postTime, transaction.privileged,
+                                  transaction.postTime, transaction.permissions,
                                   transaction.hasListenerCallbacks, transaction.listenerCallbacks,
                                   transaction.originPid, transaction.originUid, transaction.id);
         }
@@ -3383,7 +3374,6 @@
 
 bool SurfaceFlinger::transactionIsReadyToBeApplied(
         bool isAutoTimestamp, int64_t desiredPresentTime, const Vector<ComposerState>& states,
-        bool updateTransactionCounters,
         std::unordered_set<sp<IBinder>, ISurfaceComposer::SpHash<IBinder>>& pendingBuffers) {
     const nsecs_t expectedPresentTime = mExpectedPresentTime.load();
     bool ready = true;
@@ -3413,15 +3403,15 @@
         if (!layer) {
             continue;
         }
+        if (layer->frameIsEarly(expectedPresentTime)) {
+            ATRACE_NAME("frameIsEarly()");
+            return false;
+        }
 
         if (!mScheduler->isVsyncValid(expectedPresentTime, layer->getOwnerUid())) {
             ATRACE_NAME("!isVsyncValidForUid");
             ready = false;
         }
-        if (updateTransactionCounters) {
-            // See BufferStateLayer::mPendingBufferTransactions
-            layer->incrementPendingBufferCount();
-        }
 
         // If backpressure is enabled and we already have a buffer to commit, keep the transaction
         // in the queue.
@@ -3434,94 +3424,51 @@
     return ready;
 }
 
-status_t SurfaceFlinger::setTransactionState(
-        const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states,
-        const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
-        const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
-        bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
-        const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {
-    ATRACE_CALL();
+void SurfaceFlinger::queueTransaction(TransactionState state) {
+    Mutex::Autolock _l(mQueueLock);
 
-    {
-        Mutex::Autolock _l(mQueueLock);
-
-        const int64_t postTime = systemTime();
-        bool privileged = callingThreadHasUnscopedSurfaceFlingerAccess();
-
-        IPCThreadState* ipc = IPCThreadState::self();
-        const int originPid = ipc->getCallingPid();
-        const int originUid = ipc->getCallingUid();
-
-        // If its TransactionQueue already has a pending TransactionState or if it is pending
-        auto itr = mPendingTransactionQueues.find(applyToken);
-        // if this is an animation frame, wait until prior animation frame has
-        // been applied by SF
-        if (flags & eAnimation) {
-            while (itr != mPendingTransactionQueues.end()) {
-                status_t err = mTransactionQueueCV.waitRelative(mQueueLock, s2ns(5));
-                if (CC_UNLIKELY(err != NO_ERROR)) {
-                    ALOGW_IF(err == TIMED_OUT,
-                             "setTransactionState timed out "
-                             "waiting for animation frame to apply");
-                    break;
-                }
-                itr = mPendingTransactionQueues.find(applyToken);
+    // If its TransactionQueue already has a pending TransactionState or if it is pending
+    auto itr = mPendingTransactionQueues.find(state.applyToken);
+    // if this is an animation frame, wait until prior animation frame has
+    // been applied by SF
+    if (state.flags & eAnimation) {
+        while (itr != mPendingTransactionQueues.end()) {
+            status_t err = mTransactionQueueCV.waitRelative(mQueueLock, s2ns(5));
+            if (CC_UNLIKELY(err != NO_ERROR)) {
+                ALOGW_IF(err == TIMED_OUT,
+                         "setTransactionState timed out "
+                         "waiting for animation frame to apply");
+                break;
             }
+            itr = mPendingTransactionQueues.find(state.applyToken);
         }
-
-        const bool pendingTransactions = itr != mPendingTransactionQueues.end();
-        // Expected present time is computed and cached on invalidate, so it may be stale.
-        if (!pendingTransactions) {
-            const auto now = systemTime();
-            const bool nextVsyncPending = now < mExpectedPresentTime.load();
-            const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(now);
-            mExpectedPresentTime = calculateExpectedPresentTime(stats);
-            // The transaction might arrive just before the next vsync but after
-            // invalidate was called. In that case we need to get the next vsync
-            // afterwards.
-            if (nextVsyncPending) {
-                mExpectedPresentTime += stats.vsyncPeriod;
-            }
-        }
-
-        mTransactionQueue.emplace(frameTimelineInfo, states, displays, flags, applyToken,
-                                  inputWindowCommands, desiredPresentTime, isAutoTimestamp,
-                                  uncacheBuffer, postTime, privileged, hasListenerCallbacks,
-                                  listenerCallbacks, originPid, originUid, transactionId);
-
-        if (pendingTransactions ||
-            (!isAutoTimestamp && desiredPresentTime > mExpectedPresentTime.load())) {
-            setTransactionFlags(eTransactionFlushNeeded);
-            return NO_ERROR;
-        }
-
-        // TODO(b/159125966): Remove eEarlyWakeup completely as no client should use this flag
-        if (flags & eEarlyWakeup) {
-            ALOGW("eEarlyWakeup is deprecated. Use eExplicitEarlyWakeup[Start|End]");
-        }
-
-        if (!privileged && (flags & (eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd))) {
-            ALOGE("Only WindowManager is allowed to use eExplicitEarlyWakeup[Start|End] flags");
-            flags &= ~(eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd);
-        }
-
-        const auto schedule = [](uint32_t flags) {
-            if (flags & eEarlyWakeup) return TransactionSchedule::Early;
-            if (flags & eExplicitEarlyWakeupEnd) return TransactionSchedule::EarlyEnd;
-            if (flags & eExplicitEarlyWakeupStart) return TransactionSchedule::EarlyStart;
-            return TransactionSchedule::Late;
-        }(flags);
-        setTransactionFlags(eTransactionFlushNeeded, schedule);
     }
 
-    // if this is a synchronous transaction, wait for it to take effect
-    // before returning.
-    const bool synchronous = flags & eSynchronous;
-    const bool syncInput = inputWindowCommands.syncInputWindows;
-    if (!synchronous && !syncInput) {
-        return NO_ERROR;
+    mTransactionQueue.emplace(state);
+    ATRACE_INT("TransactionQueue", mTransactionQueue.size());
+
+    // TODO(b/159125966): Remove eEarlyWakeup completely as no client should use this flag
+    if (state.flags & eEarlyWakeup) {
+        ALOGW("eEarlyWakeup is deprecated. Use eExplicitEarlyWakeup[Start|End]");
     }
 
+    if (!(state.permissions & Permission::ACCESS_SURFACE_FLINGER) &&
+        (state.flags & (eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd))) {
+        ALOGE("Only WindowManager is allowed to use eExplicitEarlyWakeup[Start|End] flags");
+        state.flags &= ~(eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd);
+    }
+
+    const auto schedule = [](uint32_t flags) {
+        if (flags & eEarlyWakeup) return TransactionSchedule::Early;
+        if (flags & eExplicitEarlyWakeupEnd) return TransactionSchedule::EarlyEnd;
+        if (flags & eExplicitEarlyWakeupStart) return TransactionSchedule::EarlyStart;
+        return TransactionSchedule::Late;
+    }(state.flags);
+
+    setTransactionFlags(eTransactionFlushNeeded, schedule);
+}
+
+void SurfaceFlinger::waitForSynchronousTransaction(bool synchronous, bool syncInput) {
     Mutex::Autolock _l(mStateLock);
     if (synchronous) {
         mTransactionPending = true;
@@ -3545,6 +3492,48 @@
             break;
         }
     }
+}
+
+status_t SurfaceFlinger::setTransactionState(
+        const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states,
+        const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+        const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
+        bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
+        const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {
+    ATRACE_CALL();
+
+    // Check for incoming buffer updates and increment the pending buffer count.
+    for (const auto& state : states) {
+        if ((state.state.what & layer_state_t::eAcquireFenceChanged) && (state.state.surface)) {
+            mBufferCountTracker.increment(state.state.surface->localBinder());
+        }
+    }
+
+    uint32_t permissions =
+            callingThreadHasUnscopedSurfaceFlingerAccess() ? Permission::ACCESS_SURFACE_FLINGER : 0;
+    // Avoid checking for rotation permissions if the caller already has ACCESS_SURFACE_FLINGER
+    // permissions.
+    if ((permissions & Permission::ACCESS_SURFACE_FLINGER) ||
+        callingThreadHasRotateSurfaceFlingerAccess()) {
+        permissions |= Permission::ROTATE_SURFACE_FLINGER;
+    }
+
+    const int64_t postTime = systemTime();
+
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int originPid = ipc->getCallingPid();
+    const int originUid = ipc->getCallingUid();
+
+    queueTransaction({frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands,
+                      desiredPresentTime, isAutoTimestamp, uncacheBuffer, postTime, permissions,
+                      hasListenerCallbacks, listenerCallbacks, originPid, originUid,
+                      transactionId});
+
+    const bool synchronous = flags & eSynchronous;
+    const bool syncInput = inputWindowCommands.syncInputWindows;
+    if (synchronous || syncInput) {
+        waitForSynchronousTransaction(synchronous, syncInput);
+    }
 
     return NO_ERROR;
 }
@@ -3555,12 +3544,11 @@
                                            const InputWindowCommands& inputWindowCommands,
                                            const int64_t desiredPresentTime, bool isAutoTimestamp,
                                            const client_cache_t& uncacheBuffer,
-                                           const int64_t postTime, bool privileged,
+                                           const int64_t postTime, uint32_t permissions,
                                            bool hasListenerCallbacks,
                                            const std::vector<ListenerCallbacks>& listenerCallbacks,
                                            int originPid, int originUid, uint64_t transactionId) {
     uint32_t transactionFlags = 0;
-
     for (const DisplayState& display : displays) {
         transactionFlags |= setDisplayStateLocked(display);
     }
@@ -3578,7 +3566,7 @@
     for (const ComposerState& state : states) {
         clientStateFlags |=
                 setClientStateLocked(frameTimelineInfo, state, desiredPresentTime, isAutoTimestamp,
-                                     postTime, privileged, listenerCallbacksWithSurfaces);
+                                     postTime, permissions, listenerCallbacksWithSurfaces);
         if ((flags & eAnimation) && state.state.surface) {
             if (const auto layer = fromHandleLocked(state.state.surface).promote(); layer) {
                 mScheduler->recordLayerHistory(layer.get(),
@@ -3598,7 +3586,7 @@
     }
     transactionFlags |= clientStateFlags;
 
-    if (privileged) {
+    if (permissions & Permission::ACCESS_SURFACE_FLINGER) {
         transactionFlags |= addInputWindowCommands(inputWindowCommands);
     } else if (!inputWindowCommands.empty()) {
         ALOGE("Only privileged callers are allowed to send input commands.");
@@ -3702,10 +3690,10 @@
 
 uint32_t SurfaceFlinger::setClientStateLocked(
         const FrameTimelineInfo& frameTimelineInfo, const ComposerState& composerState,
-        int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, bool privileged,
+        int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, uint32_t permissions,
         std::unordered_set<ListenerCallbacks, ListenerCallbacksHash>& listenerCallbacks) {
     const layer_state_t& s = composerState.state;
-
+    const bool privileged = permissions & Permission::ACCESS_SURFACE_FLINGER;
     for (auto& listener : s.listeners) {
         // note that startRegistration will not re-register if the listener has
         // already be registered for a prior surface control
@@ -3830,8 +3818,8 @@
         // ACCESS_SURFACE_FLINGER nor ROTATE_SURFACE_FLINGER
         // (a.k.a. everyone except WindowManager / tests / Launcher) from setting non rectangle
         // preserving transformations.
-        bool allowNonRectPreservingTransforms =
-                privileged || callingThreadHasRotateSurfaceFlingerAccess();
+        const bool allowNonRectPreservingTransforms =
+                permissions & Permission::ROTATE_SURFACE_FLINGER;
         if (layer->setMatrix(s.matrix, allowNonRectPreservingTransforms)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eTransparentRegionChanged) {
@@ -4098,10 +4086,16 @@
                                             std::move(metadata), format, handle, gbp, &layer);
 
             break;
-        case ISurfaceComposerClient::eFXSurfaceBufferState:
+        case ISurfaceComposerClient::eFXSurfaceBufferState: {
             result = createBufferStateLayer(client, std::move(uniqueName), w, h, flags,
                                             std::move(metadata), handle, &layer);
-            break;
+            std::atomic<int32_t>* pendingBufferCounter = layer->getPendingBufferCounter();
+            if (pendingBufferCounter) {
+                std::string counterName = layer->getPendingBufferCounterName();
+                mBufferCountTracker.add((*handle)->localBinder(), counterName,
+                                        pendingBufferCounter);
+            }
+        } break;
         case ISurfaceComposerClient::eFXSurfaceEffect:
             // check if buffer size is set for color layer.
             if (w > 0 || h > 0) {
@@ -4268,6 +4262,7 @@
     auto it = mLayersByLocalBinderToken.begin();
     while (it != mLayersByLocalBinderToken.end()) {
         if (it->second == layer) {
+            mBufferCountTracker.remove(it->first->localBinder());
             it = mLayersByLocalBinderToken.erase(it);
         } else {
             it++;
@@ -4433,6 +4428,7 @@
                 {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)},
                 {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)},
                 {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)},
+                {"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)},
                 {"--static-screen"s, dumper(&SurfaceFlinger::dumpStaticScreenStats)},
                 {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
                 {"--vsync"s, dumper(&SurfaceFlinger::dumpVSync)},
@@ -4569,6 +4565,13 @@
     mScheduler->dumpVsync(result);
 }
 
+void SurfaceFlinger::dumpPlannerInfo(const DumpArgs& args, std::string& result) const {
+    for (const auto& [token, display] : mDisplays) {
+        const auto compositionDisplay = display->getCompositionDisplay();
+        compositionDisplay->dumpPlannerInfo(args, result);
+    }
+}
+
 void SurfaceFlinger::dumpStaticScreenStats(std::string& result) const {
     result.append("Static screen stats:\n");
     for (size_t b = 0; b < SurfaceFlingerBE::NUM_BUCKETS - 1; ++b) {
@@ -4868,6 +4871,8 @@
 
     getRenderEngine().dump(result);
 
+    result.append("ClientCache state:\n");
+    ClientCache::getInstance().dump(result);
     DebugEGLImageTracker::getInstance()->dump(result);
 
     if (const auto display = getDefaultDisplayDeviceLocked()) {
@@ -5030,7 +5035,8 @@
         case GET_PHYSICAL_DISPLAY_TOKEN:
         case GET_DISPLAY_COLOR_MODES:
         case GET_DISPLAY_NATIVE_PRIMARIES:
-        case GET_DISPLAY_INFO:
+        case GET_STATIC_DISPLAY_INFO:
+        case GET_DYNAMIC_DISPLAY_INFO:
         case GET_DISPLAY_MODES:
         case GET_DISPLAY_STATE:
         case GET_DISPLAY_STATS:
@@ -5057,6 +5063,8 @@
             // This is not sensitive information, so should not require permission control.
             return OK;
         }
+        case ADD_FPS_LISTENER:
+        case REMOVE_FPS_LISTENER:
         case ADD_REGION_SAMPLING_LISTENER:
         case REMOVE_REGION_SAMPLING_LISTENER: {
             // codes that require permission check
@@ -5913,6 +5921,15 @@
                                             regionSampling, grayscale, captureResults);
         });
 
+        // TODO(b/180767535): Remove this once we optimize buffer lifecycle for RenderEngine
+        // Only do this when we're not doing region sampling, to allow the region sampling thread to
+        // manage buffer lifecycle itself.
+        if (!regionSampling &&
+            getRenderEngine().getRenderEngineType() ==
+                    renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED) {
+            getRenderEngine().unbindExternalTextureBuffer(buffer->getId());
+        }
+
         captureResults.result = result;
         captureListener->onScreenCaptureCompleted(captureResults);
     }));
@@ -6032,8 +6049,12 @@
     base::unique_fd bufferFence;
     base::unique_fd drawFence;
     getRenderEngine().useProtectedContext(useProtected);
+
+    // TODO(b/180767535): Remove this once we optimize buffer lifecycle for RenderEngine
+    const bool useFramebufferCache = getRenderEngine().getRenderEngineType() ==
+            renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED;
     getRenderEngine().drawLayers(clientCompositionDisplay, clientCompositionLayerPointers, buffer,
-                                 /*useFramebufferCache=*/false, std::move(bufferFence), &drawFence);
+                                 useFramebufferCache, std::move(bufferFence), &drawFence);
 
     if (drawFence >= 0) {
         sp<Fence> releaseFence = new Fence(dup(drawFence));
@@ -6162,27 +6183,25 @@
             ? mRefreshRateConfigs->getRefreshRateFromModeId(*modeId)
             // NOTE: Choose the default mode ID, if Scheduler doesn't have one in mind.
             : mRefreshRateConfigs->getRefreshRateFromModeId(currentPolicy.defaultMode);
-    ALOGV("trying to switch to Scheduler preferred mode %zu (%s)",
+    ALOGV("trying to switch to Scheduler preferred mode %d (%s)",
           preferredRefreshRate.getModeId().value(), preferredRefreshRate.getName().c_str());
 
     if (isDisplayModeAllowed(preferredRefreshRate.getModeId())) {
-        ALOGV("switching to Scheduler preferred display mode %zu",
+        ALOGV("switching to Scheduler preferred display mode %d",
               preferredRefreshRate.getModeId().value());
         setDesiredActiveMode({preferredRefreshRate.getModeId(), Scheduler::ModeEvent::Changed});
     } else {
-        LOG_ALWAYS_FATAL("Desired display mode not allowed: %zu",
+        LOG_ALWAYS_FATAL("Desired display mode not allowed: %d",
                          preferredRefreshRate.getModeId().value());
     }
 
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
-                                                    size_t defaultMode, bool allowGroupSwitching,
-                                                    float primaryRefreshRateMin,
-                                                    float primaryRefreshRateMax,
-                                                    float appRequestRefreshRateMin,
-                                                    float appRequestRefreshRateMax) {
+status_t SurfaceFlinger::setDesiredDisplayModeSpecs(
+        const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching,
+        float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin,
+        float appRequestRefreshRateMax) {
     ATRACE_CALL();
 
     if (!displayToken) {
@@ -6213,10 +6232,13 @@
     return future.get();
 }
 
-status_t SurfaceFlinger::getDesiredDisplayModeSpecs(
-        const sp<IBinder>& displayToken, size_t* outDefaultMode, bool* outAllowGroupSwitching,
-        float* outPrimaryRefreshRateMin, float* outPrimaryRefreshRateMax,
-        float* outAppRequestRefreshRateMin, float* outAppRequestRefreshRateMax) {
+status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                                    ui::DisplayModeId* outDefaultMode,
+                                                    bool* outAllowGroupSwitching,
+                                                    float* outPrimaryRefreshRateMin,
+                                                    float* outPrimaryRefreshRateMax,
+                                                    float* outAppRequestRefreshRateMin,
+                                                    float* outAppRequestRefreshRateMax) {
     ATRACE_CALL();
 
     if (!displayToken || !outDefaultMode || !outPrimaryRefreshRateMin ||
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index a62d0b9..8e69ead 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -89,6 +89,7 @@
 
 class Client;
 class EventThread;
+class FpsReporter;
 class HWComposer;
 struct SetInputWindowsListener;
 class IGraphicBufferProducer;
@@ -344,7 +345,8 @@
 
     virtual uint32_t setClientStateLocked(
             const FrameTimelineInfo& info, const ComposerState& composerState,
-            int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, bool privileged,
+            int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime,
+            uint32_t permissions,
             std::unordered_set<ListenerCallbacks, ListenerCallbacksHash>& listenerCallbacks)
             REQUIRES(mStateLock);
     virtual void commitTransactionLocked();
@@ -361,6 +363,7 @@
     friend class BufferQueueLayer;
     friend class BufferStateLayer;
     friend class Client;
+    friend class FpsReporter;
     friend class Layer;
     friend class MonitoredProducer;
     friend class RefreshRateOverlay;
@@ -414,6 +417,43 @@
         void traverseInReverseZOrder(const LayerVector::Visitor& visitor) const;
     };
 
+    // Keeps track of pending buffers per layer handle in the transaction queue or current/drawing
+    // state before the buffers are latched. The layer owns the atomic counters and decrements the
+    // count in the main thread when dropping or latching a buffer.
+    //
+    // The binder threads increment the same counter when a new transaction containing a buffer is
+    // added to the transaction queue. The map is updated with the layer handle lifecycle updates.
+    // This is done to avoid lock contention with the main thread.
+    class BufferCountTracker {
+    public:
+        void increment(BBinder* layerHandle) {
+            std::lock_guard<std::mutex> lock(mLock);
+            auto it = mCounterByLayerHandle.find(layerHandle);
+            if (it != mCounterByLayerHandle.end()) {
+                auto [name, pendingBuffers] = it->second;
+                int32_t count = ++(*pendingBuffers);
+                ATRACE_INT(name.c_str(), count);
+            } else {
+                ALOGW("Handle not found! %p", layerHandle);
+            }
+        }
+
+        void add(BBinder* layerHandle, const std::string& name, std::atomic<int32_t>* counter) {
+            std::lock_guard<std::mutex> lock(mLock);
+            mCounterByLayerHandle[layerHandle] = std::make_pair(name, counter);
+        }
+
+        void remove(BBinder* layerHandle) {
+            std::lock_guard<std::mutex> lock(mLock);
+            mCounterByLayerHandle.erase(layerHandle);
+        }
+
+    private:
+        std::mutex mLock;
+        std::unordered_map<BBinder*, std::pair<std::string, std::atomic<int32_t>*>>
+                mCounterByLayerHandle GUARDED_BY(mLock);
+    };
+
     struct ActiveModeInfo {
         DisplayModeId modeId;
         Scheduler::ModeEvent event = Scheduler::ModeEvent::None;
@@ -441,7 +481,7 @@
                          const sp<IBinder>& applyToken,
                          const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
                          bool isAutoTimestamp, const client_cache_t& uncacheBuffer,
-                         int64_t postTime, bool privileged, bool hasListenerCallbacks,
+                         int64_t postTime, uint32_t permissions, bool hasListenerCallbacks,
                          std::vector<ListenerCallbacks> listenerCallbacks, int originPid,
                          int originUid, uint64_t transactionId)
               : frameTimelineInfo(frameTimelineInfo),
@@ -454,7 +494,7 @@
                 isAutoTimestamp(isAutoTimestamp),
                 buffer(uncacheBuffer),
                 postTime(postTime),
-                privileged(privileged),
+                permissions(permissions),
                 hasListenerCallbacks(hasListenerCallbacks),
                 listenerCallbacks(listenerCallbacks),
                 originPid(originPid),
@@ -471,7 +511,7 @@
         const bool isAutoTimestamp;
         client_cache_t buffer;
         const int64_t postTime;
-        bool privileged;
+        uint32_t permissions;
         bool hasListenerCallbacks;
         std::vector<ListenerCallbacks> listenerCallbacks;
         int originPid;
@@ -552,26 +592,20 @@
                            const sp<IScreenCaptureListener>& captureListener) override;
 
     status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* stats) override;
-    status_t getDisplayState(const sp<IBinder>& displayToken, ui::DisplayState*) override;
-    status_t getDisplayInfo(const sp<IBinder>& displayToken, DisplayInfo*) override;
-    status_t getDisplayModes(const sp<IBinder>& displayToken, Vector<ui::DisplayMode>*) override;
-    int getActiveDisplayModeId(const sp<IBinder>& displayToken) override;
-    status_t getDisplayColorModes(const sp<IBinder>& displayToken, Vector<ui::ColorMode>*) override;
+    status_t getDisplayState(const sp<IBinder>& displayToken, ui::DisplayState*)
+            EXCLUDES(mStateLock) override;
+    status_t getStaticDisplayInfo(const sp<IBinder>& displayToken, ui::StaticDisplayInfo*)
+            EXCLUDES(mStateLock) override;
+    status_t getDynamicDisplayInfo(const sp<IBinder>& displayToken, ui::DynamicDisplayInfo*)
+            EXCLUDES(mStateLock) override;
     status_t getDisplayNativePrimaries(const sp<IBinder>& displayToken,
                                        ui::DisplayPrimaries&) override;
-    ui::ColorMode getActiveColorMode(const sp<IBinder>& displayToken) override;
     status_t setActiveColorMode(const sp<IBinder>& displayToken, ui::ColorMode colorMode) override;
-    status_t getAutoLowLatencyModeSupport(const sp<IBinder>& displayToken,
-                                          bool* outSupported) const override;
     void setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) override;
-    status_t getGameContentTypeSupport(const sp<IBinder>& displayToken,
-                                       bool* outSupported) const override;
     void setGameContentType(const sp<IBinder>& displayToken, bool on) override;
     void setPowerMode(const sp<IBinder>& displayToken, int mode) override;
     status_t clearAnimationFrameStats() override;
     status_t getAnimationFrameStats(FrameStats* outStats) const override;
-    status_t getHdrCapabilities(const sp<IBinder>& displayToken,
-                                HdrCapabilities* outCapabilities) const override;
     status_t enableVSyncInjections(bool enable) override;
     status_t injectVSync(nsecs_t when) override;
     status_t getLayerDebugInfo(std::vector<LayerDebugInfo>* outLayers) override;
@@ -594,11 +628,15 @@
     status_t addRegionSamplingListener(const Rect& samplingArea, const sp<IBinder>& stopLayerHandle,
                                        const sp<IRegionSamplingListener>& listener) override;
     status_t removeRegionSamplingListener(const sp<IRegionSamplingListener>& listener) override;
-    status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, size_t displayModeId,
-                                        bool allowGroupSwitching, float primaryRefreshRateMin,
-                                        float primaryRefreshRateMax, float appRequestRefreshRateMin,
+    status_t addFpsListener(int32_t taskId, const sp<gui::IFpsListener>& listener) override;
+    status_t removeFpsListener(const sp<gui::IFpsListener>& listener) override;
+    status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                        ui::DisplayModeId displayModeId, bool allowGroupSwitching,
+                                        float primaryRefreshRateMin, float primaryRefreshRateMax,
+                                        float appRequestRefreshRateMin,
                                         float appRequestRefreshRateMax) override;
-    status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, size_t* outDefaultMode,
+    status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                        ui::DisplayModeId* outDefaultMode,
                                         bool* outAllowGroupSwitching,
                                         float* outPrimaryRefreshRateMin,
                                         float* outPrimaryRefreshRateMax,
@@ -740,7 +778,7 @@
                                const InputWindowCommands& inputWindowCommands,
                                const int64_t desiredPresentTime, bool isAutoTimestamp,
                                const client_cache_t& uncacheBuffer, const int64_t postTime,
-                               bool privileged, bool hasListenerCallbacks,
+                               uint32_t permissions, bool hasListenerCallbacks,
                                const std::vector<ListenerCallbacks>& listenerCallbacks,
                                int originPid, int originUid, uint64_t transactionId)
             REQUIRES(mStateLock);
@@ -763,7 +801,6 @@
     void commitOffscreenLayers();
     bool transactionIsReadyToBeApplied(
             bool isAutoTimestamp, int64_t desiredPresentTime, const Vector<ComposerState>& states,
-            bool updateTransactionCounters,
             std::unordered_set<sp<IBinder>, ISurfaceComposer::SpHash<IBinder>>& pendingBuffers)
             REQUIRES(mStateLock);
     uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock);
@@ -905,7 +942,8 @@
     /*
      * Display management
      */
-    DisplayModes loadSupportedDisplayModes(PhysicalDisplayId) const;
+    void loadDisplayModes(PhysicalDisplayId displayId, DisplayModes& outModes,
+                          DisplayModePtr& outActiveMode) const REQUIRES(mStateLock);
     sp<DisplayDevice> setupNewDisplayDeviceInternal(
             const wp<IBinder>& displayToken,
             std::shared_ptr<compositionengine::Display> compositionDisplay,
@@ -1016,6 +1054,7 @@
     LayersProto dumpProtoFromMainThread(uint32_t traceFlags = SurfaceTracing::TRACE_ALL)
             EXCLUDES(mStateLock);
     void dumpOffscreenLayers(std::string& result) EXCLUDES(mStateLock);
+    void dumpPlannerInfo(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
 
     bool isLayerTripleBufferingDisabled() const {
         return this->mLayerTripleBufferingDisabled;
@@ -1039,6 +1078,10 @@
     // either AID_GRAPHICS or AID_SYSTEM.
     status_t CheckTransactCodeCredentials(uint32_t code);
 
+    // Add transaction to the Transaction Queue
+    void queueTransaction(TransactionState state) EXCLUDES(mQueueLock);
+    void waitForSynchronousTransaction(bool synchronous, bool syncInput) EXCLUDES(mStateLock);
+
     /*
      * Generic Layer Metadata
      */
@@ -1054,6 +1097,9 @@
         return std::nullopt;
     }
 
+    std::vector<ui::ColorMode> getDisplayColorModes(PhysicalDisplayId displayId)
+            REQUIRES(mStateLock);
+
     static int calculateExtraBufferCount(Fps maxSupportedRefreshRate,
                                          std::chrono::nanoseconds presentLatency);
 
@@ -1265,6 +1311,7 @@
 
     bool mLumaSampling = true;
     sp<RegionSamplingThread> mRegionSamplingThread;
+    sp<FpsReporter> mFpsReporter;
     ui::DisplayPrimaries mInternalDisplayPrimaries;
 
     const float mInternalDisplayDensity;
@@ -1304,6 +1351,8 @@
     int mFrameRateFlexibilityTokenCount = 0;
 
     sp<IBinder> mDebugFrameRateFlexibilityToken;
+
+    BufferCountTracker mBufferCountTracker;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp
index c043866..b3dca78 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.cpp
+++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp
@@ -380,5 +380,9 @@
     return SurfaceFlingerProperties::enable_frame_rate_override().value_or(defaultValue);
 }
 
+bool enable_layer_caching(bool defaultValue) {
+    return SurfaceFlingerProperties::enable_layer_caching().value_or(defaultValue);
+}
+
 } // namespace sysprop
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h
index 816cb09..b19d216 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.h
+++ b/services/surfaceflinger/SurfaceFlingerProperties.h
@@ -100,6 +100,8 @@
 
 bool enable_frame_rate_override(bool defaultValue);
 
+bool enable_layer_caching(bool defaultValue);
+
 } // namespace sysprop
 } // namespace android
 #endif // SURFACEFLINGERPROPERTIES_H_
diff --git a/services/surfaceflinger/SurfaceInterceptor.cpp b/services/surfaceflinger/SurfaceInterceptor.cpp
index 61005c9..b0413f1 100644
--- a/services/surfaceflinger/SurfaceInterceptor.cpp
+++ b/services/surfaceflinger/SurfaceInterceptor.cpp
@@ -499,6 +499,9 @@
     if (state.what & layer_state_t::eShadowRadiusChanged) {
         addShadowRadiusLocked(transaction, layerId, state.shadowRadius);
     }
+    if (state.what & layer_state_t::eStretchChanged) {
+        ALOGW("SurfaceInterceptor not implemented for eStretchChanged");
+    }
 }
 
 void SurfaceInterceptor::addDisplayChangesLocked(Transaction* transaction,
diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
index 4d25a7a..ee5542d 100644
--- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
+++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
@@ -454,3 +454,12 @@
     access: Readonly
     prop_name: "ro.surface_flinger.enable_frame_rate_override"
 }
+
+# Enables Layer Caching
+prop {
+    api_name: "enable_layer_caching"
+    type: Boolean
+    scope: Public
+    access: Readonly
+    prop_name: "ro.surface_flinger.enable_layer_caching"
+}
diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
index 0e0be09..47e14f6 100644
--- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
+++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
@@ -45,6 +45,10 @@
     prop_name: "ro.surface_flinger.enable_frame_rate_override"
   }
   prop {
+    api_name: "enable_layer_caching"
+    prop_name: "ro.surface_flinger.enable_layer_caching"
+  }
+  prop {
     api_name: "enable_protected_contents"
     prop_name: "ro.surface_flinger.protected_contents"
   }
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 53e37d8..6246321 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -26,6 +26,7 @@
 #include <private/android_filesystem_config.h>
 #include <private/gui/ComposerService.h>
 #include <ui/DisplayMode.h>
+#include <ui/DynamicDisplayInfo.h>
 #include <utils/String8.h>
 #include <functional>
 #include "utils/ScreenshotUtils.h"
@@ -188,19 +189,15 @@
     ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
 
     Vector<ui::DisplayMode> modes;
-    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayModes(display, &modes));
-
-    ASSERT_TRUE(SurfaceComposerClient::getActiveDisplayModeId(display) >= 0);
-
-    ASSERT_NE(static_cast<ui::ColorMode>(BAD_VALUE),
-              SurfaceComposerClient::getActiveColorMode(display));
+    ui::DynamicDisplayInfo info;
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info));
 }
 
-TEST_F(CredentialsTest, GetDisplayColorModesTest) {
+TEST_F(CredentialsTest, GetDynamicDisplayInfoTest) {
     const auto display = SurfaceComposerClient::getInternalDisplayToken();
     std::function<status_t()> condition = [=]() {
-        Vector<ui::ColorMode> outColorModes;
-        return SurfaceComposerClient::getDisplayColorModes(display, &outColorModes);
+        ui::DynamicDisplayInfo info;
+        return SurfaceComposerClient::getDynamicDisplayInfo(display, &info);
     };
     ASSERT_NO_FATAL_FAILURE(checkWithPrivileges<status_t>(condition, NO_ERROR, NO_ERROR));
 }
@@ -216,7 +213,7 @@
 
 TEST_F(CredentialsTest, SetDesiredDisplayConfigsTest) {
     const auto display = SurfaceComposerClient::getInternalDisplayToken();
-    size_t defaultMode;
+    ui::DisplayModeId defaultMode;
     bool allowGroupSwitching;
     float primaryFpsMin;
     float primaryFpsMax;
@@ -355,8 +352,9 @@
     status_t error = SurfaceComposerClient::isWideColorDisplay(display, &result);
     ASSERT_EQ(NO_ERROR, error);
     bool hasWideColorMode = false;
-    Vector<ColorMode> colorModes;
-    SurfaceComposerClient::getDisplayColorModes(display, &colorModes);
+    ui::DynamicDisplayInfo info;
+    SurfaceComposerClient::getDynamicDisplayInfo(display, &info);
+    const auto& colorModes = info.supportedColorModes;
     for (ColorMode colorMode : colorModes) {
         switch (colorMode) {
             case ColorMode::DISPLAY_P3:
@@ -384,7 +382,9 @@
 TEST_F(CredentialsTest, GetActiveColorModeBasicCorrectness) {
     const auto display = SurfaceComposerClient::getInternalDisplayToken();
     ASSERT_FALSE(display == nullptr);
-    ColorMode colorMode = SurfaceComposerClient::getActiveColorMode(display);
+    ui::DynamicDisplayInfo info;
+    SurfaceComposerClient::getDynamicDisplayInfo(display, &info);
+    ColorMode colorMode = info.activeColorMode;
     ASSERT_NE(static_cast<ColorMode>(BAD_VALUE), colorMode);
 }
 
diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
index 9f025a6..2dc96b8 100644
--- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp
+++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
@@ -23,6 +23,7 @@
 #include <gui/SurfaceComposerClient.h>
 #include <private/gui/ComposerService.h>
 #include <ui/DisplayMode.h>
+#include <ui/DynamicDisplayInfo.h>
 #include <utils/Errors.h>
 #include <utils/Vector.h>
 
@@ -38,7 +39,7 @@
  */
 class RefreshRateRangeTest : public ::testing::Test {
 private:
-    size_t initialDefaultMode;
+    ui::DisplayModeId initialDefaultMode;
     bool initialAllowGroupSwitching;
     float initialPrimaryMin;
     float initialPrimaryMax;
@@ -76,20 +77,21 @@
 };
 
 TEST_F(RefreshRateRangeTest, setAllConfigs) {
-    Vector<ui::DisplayMode> modes;
-    status_t res = SurfaceComposerClient::getDisplayModes(mDisplayToken, &modes);
+    ui::DynamicDisplayInfo info;
+    status_t res = SurfaceComposerClient::getDynamicDisplayInfo(mDisplayToken, &info);
+    const auto& modes = info.supportedDisplayModes;
     ASSERT_EQ(res, NO_ERROR);
     ASSERT_GT(modes.size(), 0);
 
     for (size_t i = 0; i < modes.size(); i++) {
-        res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, i, false,
+        res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, modes[i].id, false,
                                                                 modes[i].refreshRate,
                                                                 modes[i].refreshRate,
                                                                 modes[i].refreshRate,
                                                                 modes[i].refreshRate);
         ASSERT_EQ(res, NO_ERROR);
 
-        size_t defaultConfig;
+        ui::DisplayModeId defaultConfig;
         bool allowGroupSwitching;
         float primaryRefreshRateMin;
         float primaryRefreshRateMax;
@@ -116,7 +118,7 @@
             SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, 0, allowGroupSwitching,
                                                               0.f, 90.f, 0.f, 90.f);
     ASSERT_EQ(res, NO_ERROR);
-    size_t defaultConfig;
+    ui::DisplayModeId defaultConfig;
     bool newAllowGroupSwitching;
     float primaryRefreshRateMin;
     float primaryRefreshRateMax;
diff --git a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
index 56e1ae9..11bd9eb 100644
--- a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
+++ b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
@@ -44,6 +44,7 @@
 #include <log/log.h>
 #include <private/gui/ComposerService.h>
 #include <ui/DisplayMode.h>
+#include <ui/DynamicDisplayInfo.h>
 #include <utils/Looper.h>
 
 #include <gmock/gmock.h>
@@ -432,8 +433,9 @@
             }
         }
 
-        Vector<ui::DisplayMode> modes;
-        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayModes(display, &modes));
+        ui::DynamicDisplayInfo info;
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info));
+        const auto& modes = info.supportedDisplayModes;
         EXPECT_EQ(modes.size(), 2);
 
         // change active mode
@@ -539,8 +541,9 @@
             }
         }
 
-        Vector<ui::DisplayMode> modes;
-        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayModes(display, &modes));
+        ui::DynamicDisplayInfo info;
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info));
+        const auto& modes = info.supportedDisplayModes;
         EXPECT_EQ(modes.size(), 2);
 
         // change active mode
@@ -655,8 +658,9 @@
             }
         }
 
-        Vector<ui::DisplayMode> modes;
-        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayModes(display, &modes));
+        ui::DynamicDisplayInfo info;
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info));
+        const auto& modes = info.supportedDisplayModes;
         EXPECT_EQ(modes.size(), 4);
 
         // change active mode to 800x1600@90Hz
@@ -884,8 +888,9 @@
             EXPECT_EQ(ui::Size(800, 1600), mode.resolution);
             EXPECT_EQ(1e9f / 11'111'111, mode.refreshRate);
 
-            Vector<ui::DisplayMode> modes;
-            EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayModes(display, &modes));
+            ui::DynamicDisplayInfo info;
+            EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info));
+            const auto& modes = info.supportedDisplayModes;
             EXPECT_EQ(modes.size(), 1);
         }
 
@@ -923,8 +928,9 @@
             EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate);
         }
 
-        Vector<ui::DisplayMode> modes;
-        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayModes(display, &modes));
+        ui::DynamicDisplayInfo info;
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info));
+        const auto& modes = info.supportedDisplayModes;
         EXPECT_EQ(modes.size(), 3);
 
         EXPECT_EQ(ui::Size(800, 1600), modes[0].resolution);
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 2ac6b09..3c1b9d8 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -53,6 +53,7 @@
         "DisplayDevice_GetBestColorModeTest.cpp",
         "DisplayDevice_SetProjectionTest.cpp",
         "EventThreadTest.cpp",
+        "FpsReporterTest.cpp",
         "FpsTest.cpp",
         "FrameTimelineTest.cpp",
         "HWComposerTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index b696a6d..256be27 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -286,7 +286,7 @@
 
         auto ceDisplayArgs =
                 compositionengine::DisplayCreationArgsBuilder()
-                        .setPhysical({DEFAULT_DISPLAY_ID, DisplayConnectionType::Internal})
+                        .setPhysical({DEFAULT_DISPLAY_ID, ui::DisplayConnectionType::Internal})
                         .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
                         .setIsSecure(Derived::IS_SECURE)
                         .setLayerStackId(DEFAULT_LAYER_STACK)
@@ -300,7 +300,7 @@
                                                        ceDisplayArgs);
 
         test->mDisplay = FakeDisplayDeviceInjector(test->mFlinger, compositionDisplay,
-                                                   DisplayConnectionType::Internal, HWC_DISPLAY,
+                                                   ui::DisplayConnectionType::Internal, HWC_DISPLAY,
                                                    true /* isPrimary */)
                                  .setDisplaySurface(test->mDisplaySurface)
                                  .setNativeWindow(test->mNativeWindow)
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index 9069200..a3e8108 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -139,14 +139,14 @@
             createDisplay(mFlinger.getCompositionEngine(),
                           compositionengine::DisplayCreationArgsBuilder()
                                   .setPhysical(
-                                          {DEFAULT_DISPLAY_ID, DisplayConnectionType::Internal})
+                                          {DEFAULT_DISPLAY_ID, ui::DisplayConnectionType::Internal})
                                   .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
                                   .setPowerAdvisor(&mPowerAdvisor)
                                   .build());
 
-    auto injector =
-            FakeDisplayDeviceInjector(mFlinger, compositionDisplay, DisplayConnectionType::Internal,
-                                      DEFAULT_DISPLAY_HWC_DISPLAY_ID, true /* isPrimary */);
+    auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
+                                              ui::DisplayConnectionType::Internal,
+                                              DEFAULT_DISPLAY_HWC_DISPLAY_ID, true /* isPrimary */);
 
     injector.setNativeWindow(mNativeWindow);
     if (injectExtra) {
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 1664301..d68fff6 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -202,12 +202,13 @@
 
 template <typename>
 struct DisplayConnectionTypeGetter {
-    static constexpr std::optional<DisplayConnectionType> value;
+    static constexpr std::optional<ui::DisplayConnectionType> value;
 };
 
 template <typename PhysicalDisplay>
 struct DisplayConnectionTypeGetter<PhysicalDisplayIdType<PhysicalDisplay>> {
-    static constexpr std::optional<DisplayConnectionType> value = PhysicalDisplay::CONNECTION_TYPE;
+    static constexpr std::optional<ui::DisplayConnectionType> value =
+            PhysicalDisplay::CONNECTION_TYPE;
 };
 
 template <typename>
@@ -263,7 +264,7 @@
     static auto makeFakeExistingDisplayInjector(DisplayTransactionTest* test) {
         auto ceDisplayArgs = compositionengine::DisplayCreationArgsBuilder();
         if (auto displayId = PhysicalDisplayId::tryCast(DISPLAY_ID::get())) {
-            ceDisplayArgs.setPhysical({*displayId, DisplayConnectionType::Internal});
+            ceDisplayArgs.setPhysical({*displayId, ui::DisplayConnectionType::Internal});
         } else {
             // We turn off the use of HwcVirtualDisplays, to prevent Composition Engine
             // from calling into HWComposer. This way all virtual displays will get
@@ -457,7 +458,7 @@
 
     static void setupHwcHotplugCallExpectations(DisplayTransactionTest* test) {
         constexpr auto CONNECTION_TYPE =
-                PhysicalDisplay::CONNECTION_TYPE == DisplayConnectionType::Internal
+                PhysicalDisplay::CONNECTION_TYPE == ui::DisplayConnectionType::Internal
                 ? IComposerClient::DisplayConnectionType::INTERNAL
                 : IComposerClient::DisplayConnectionType::EXTERNAL;
 
@@ -504,7 +505,7 @@
 
 template <bool hasIdentificationData>
 struct PrimaryDisplay {
-    static constexpr auto CONNECTION_TYPE = DisplayConnectionType::Internal;
+    static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::Internal;
     static constexpr Primary PRIMARY = Primary::TRUE;
     static constexpr uint8_t PORT = 255;
     static constexpr HWDisplayId HWC_DISPLAY_ID = 1001;
@@ -514,7 +515,7 @@
 
 template <bool hasIdentificationData>
 struct ExternalDisplay {
-    static constexpr auto CONNECTION_TYPE = DisplayConnectionType::External;
+    static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::External;
     static constexpr Primary PRIMARY = Primary::FALSE;
     static constexpr uint8_t PORT = 254;
     static constexpr HWDisplayId HWC_DISPLAY_ID = 1002;
diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
new file mode 100644
index 0000000..a2291b2
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
@@ -0,0 +1,191 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "FpsReporterTest"
+
+#include <android/gui/BnFpsListener.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <gui/LayerMetadata.h>
+
+#include "BufferQueueLayer.h"
+#include "BufferStateLayer.h"
+#include "EffectLayer.h"
+#include "FpsReporter.h"
+#include "Layer.h"
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockComposer.h"
+#include "mock/MockEventThread.h"
+#include "mock/MockFrameTimeline.h"
+#include "mock/MockVsyncController.h"
+
+namespace android {
+
+using testing::_;
+using testing::DoAll;
+using testing::Mock;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::UnorderedElementsAre;
+
+using android::Hwc2::IComposer;
+using android::Hwc2::IComposerClient;
+
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+
+struct TestableFpsListener : public gui::BnFpsListener {
+    TestableFpsListener() {}
+
+    float lastReportedFps = 0;
+
+    binder::Status onFpsReported(float fps) override {
+        lastReportedFps = fps;
+        return binder::Status::ok();
+    }
+};
+
+/**
+ * This class covers all the test that are related to refresh rate selection.
+ */
+class FpsReporterTest : public testing::Test {
+public:
+    FpsReporterTest();
+    ~FpsReporterTest() override;
+
+protected:
+    static constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
+    static constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
+    static constexpr uint32_t WIDTH = 100;
+    static constexpr uint32_t HEIGHT = 100;
+    static constexpr uint32_t LAYER_FLAGS = 0;
+    static constexpr int32_t PRIORITY_UNSET = -1;
+
+    void setupScheduler();
+    void setupComposer(uint32_t virtualDisplayCount);
+    sp<BufferStateLayer> createBufferStateLayer(LayerMetadata metadata);
+
+    TestableSurfaceFlinger mFlinger;
+    Hwc2::mock::Composer* mComposer = nullptr;
+    mock::FrameTimeline mFrameTimeline =
+            mock::FrameTimeline(std::make_shared<impl::TimeStats>(), 0);
+
+    sp<Client> mClient;
+    sp<Layer> mParent;
+    sp<Layer> mTarget;
+    sp<Layer> mChild;
+    sp<Layer> mGrandChild;
+    sp<Layer> mUnrelated;
+
+    sp<TestableFpsListener> mFpsListener;
+    sp<FpsReporter> mFpsReporter = new FpsReporter(mFrameTimeline, *(mFlinger.flinger()));
+};
+
+FpsReporterTest::FpsReporterTest() {
+    const ::testing::TestInfo* const test_info =
+            ::testing::UnitTest::GetInstance()->current_test_info();
+    ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+
+    setupScheduler();
+    setupComposer(0);
+    mFpsListener = new TestableFpsListener();
+}
+
+FpsReporterTest::~FpsReporterTest() {
+    const ::testing::TestInfo* const test_info =
+            ::testing::UnitTest::GetInstance()->current_test_info();
+    ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+}
+
+sp<BufferStateLayer> FpsReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) {
+    sp<Client> client;
+    LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", WIDTH, HEIGHT,
+                           LAYER_FLAGS, metadata);
+    return new BufferStateLayer(args);
+}
+
+void FpsReporterTest::setupScheduler() {
+    auto eventThread = std::make_unique<mock::EventThread>();
+    auto sfEventThread = std::make_unique<mock::EventThread>();
+
+    EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
+    EXPECT_CALL(*eventThread, createEventConnection(_, _))
+            .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback())));
+
+    EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
+    EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
+            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback())));
+
+    auto vsyncController = std::make_unique<mock::VsyncController>();
+    auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
+
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    EXPECT_CALL(*vsyncTracker, currentPeriod())
+            .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
+                            std::move(eventThread), std::move(sfEventThread));
+}
+
+void FpsReporterTest::setupComposer(uint32_t virtualDisplayCount) {
+    mComposer = new Hwc2::mock::Composer();
+    EXPECT_CALL(*mComposer, getMaxVirtualDisplayCount()).WillOnce(Return(virtualDisplayCount));
+    mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
+
+    Mock::VerifyAndClear(mComposer);
+}
+
+namespace {
+
+TEST_F(FpsReporterTest, callsListeners) {
+    mParent = createBufferStateLayer();
+    const constexpr int32_t kTaskId = 12;
+    LayerMetadata targetMetadata;
+    targetMetadata.setInt32(METADATA_TASK_ID, kTaskId);
+    mTarget = createBufferStateLayer(targetMetadata);
+    mChild = createBufferStateLayer();
+    mGrandChild = createBufferStateLayer();
+    mUnrelated = createBufferStateLayer();
+    mParent->addChild(mTarget);
+    mTarget->addChild(mChild);
+    mChild->addChild(mGrandChild);
+    mParent->commitChildList();
+    mFlinger.mutableCurrentState().layersSortedByZ.add(mParent);
+    mFlinger.mutableCurrentState().layersSortedByZ.add(mTarget);
+    mFlinger.mutableCurrentState().layersSortedByZ.add(mChild);
+    mFlinger.mutableCurrentState().layersSortedByZ.add(mGrandChild);
+
+    float expectedFps = 44.0;
+
+    EXPECT_CALL(mFrameTimeline,
+                computeFps(UnorderedElementsAre(mTarget->getSequence(), mChild->getSequence(),
+                                                mGrandChild->getSequence())))
+            .WillOnce(Return(expectedFps));
+
+    mFpsReporter->addListener(mFpsListener, kTaskId);
+    mFpsReporter->dispatchLayerFps();
+    EXPECT_EQ(expectedFps, mFpsListener->lastReportedFps);
+    mFpsReporter->removeListener(mFpsListener);
+    Mock::VerifyAndClearExpectations(&mFrameTimeline);
+
+    EXPECT_CALL(mFrameTimeline, computeFps(_)).Times(0);
+    mFpsReporter->dispatchLayerFps();
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index b8c1607..f2cb951 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -40,6 +40,7 @@
 using ProtoFrameEnd = perfetto::protos::FrameTimelineEvent_FrameEnd;
 using ProtoPresentType = perfetto::protos::FrameTimelineEvent_PresentType;
 using ProtoJankType = perfetto::protos::FrameTimelineEvent_JankType;
+using ProtoPredictionType = perfetto::protos::FrameTimelineEvent_PredictionType;
 
 namespace android::frametimeline {
 
@@ -67,7 +68,7 @@
     void SetUp() override {
         mTimeStats = std::make_shared<mock::TimeStats>();
         mFrameTimeline = std::make_unique<impl::FrameTimeline>(mTimeStats, kSurfaceFlingerPid,
-                                                               kTestThresholds);
+                                                               kTestThresholds, kHwcDuration);
         mFrameTimeline->registerDataSource();
         mTokenManager = &mFrameTimeline->mTokenManager;
         mTraceCookieCounter = &mFrameTimeline->mTraceCookieCounter;
@@ -162,6 +163,7 @@
     static constexpr JankClassificationThresholds kTestThresholds{kPresentThreshold,
                                                                   kDeadlineThreshold,
                                                                   kStartThreshold};
+    static constexpr nsecs_t kHwcDuration = std::chrono::nanoseconds(3ns).count();
 };
 
 static const std::string sLayerNameOne = "layer1";
@@ -170,6 +172,8 @@
 static constexpr pid_t sPidOne = 10;
 static constexpr pid_t sPidTwo = 20;
 static constexpr int32_t sInputEventId = 5;
+static constexpr int32_t sLayerIdOne = 1;
+static constexpr int32_t sLayerIdTwo = 2;
 
 TEST_F(FrameTimelineTest, tokenManagerRemovesStalePredictions) {
     int64_t token1 = mTokenManager->generateTokenForPredictions({0, 0, 0});
@@ -187,17 +191,20 @@
 }
 
 TEST_F(FrameTimelineTest, createSurfaceFrameForToken_getOwnerPidReturnsCorrectPid) {
-    auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne,
-                                                                    sLayerNameOne, sLayerNameOne);
-    auto surfaceFrame2 = mFrameTimeline->createSurfaceFrameForToken({}, sPidTwo, sUidOne,
-                                                                    sLayerNameOne, sLayerNameOne);
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne);
+    auto surfaceFrame2 =
+            mFrameTimeline->createSurfaceFrameForToken({}, sPidTwo, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne);
     EXPECT_EQ(surfaceFrame1->getOwnerPid(), sPidOne);
     EXPECT_EQ(surfaceFrame2->getOwnerPid(), sPidTwo);
 }
 
 TEST_F(FrameTimelineTest, createSurfaceFrameForToken_noToken) {
-    auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne,
-                                                                   sLayerNameOne, sLayerNameOne);
+    auto surfaceFrame =
+            mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne);
     EXPECT_EQ(surfaceFrame->getPredictionState(), PredictionState::None);
 }
 
@@ -206,7 +213,7 @@
     flushTokens(systemTime() + maxTokenRetentionTime);
     auto surfaceFrame =
             mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne,
-                                                       sLayerNameOne, sLayerNameOne);
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
 
     EXPECT_EQ(surfaceFrame->getPredictionState(), PredictionState::Expired);
 }
@@ -215,7 +222,7 @@
     int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
     auto surfaceFrame =
             mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne,
-                                                       sLayerNameOne, sLayerNameOne);
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
 
     EXPECT_EQ(surfaceFrame->getPredictionState(), PredictionState::Valid);
     EXPECT_EQ(compareTimelineItems(surfaceFrame->getPredictions(), TimelineItem(10, 20, 30)), true);
@@ -226,7 +233,7 @@
     constexpr int32_t inputEventId = 1;
     auto surfaceFrame =
             mFrameTimeline->createSurfaceFrameForToken({token1, inputEventId}, sPidOne, sUidOne,
-                                                       sLayerNameOne, sLayerNameOne);
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
 
     EXPECT_EQ(inputEventId, surfaceFrame->getInputEventId());
 }
@@ -236,7 +243,7 @@
     int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne,
-                                                       sLayerNameOne, sLayerNameOne);
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
 
     // Set up the display frame
     mFrameTimeline->setSfWakeUp(token1, 20, Fps::fromPeriodNsecs(11));
@@ -263,10 +270,12 @@
     int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 30});
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     auto surfaceFrame2 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameTwo, sLayerNameTwo);
+                                                       sUidOne, sLayerIdTwo, sLayerNameTwo,
+                                                       sLayerNameTwo);
     mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -307,8 +316,8 @@
                 {22 + frameTimeFactor, 26 + frameTimeFactor, 30 + frameTimeFactor});
         auto surfaceFrame =
                 mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId},
-                                                           sPidOne, sUidOne, sLayerNameOne,
-                                                           sLayerNameOne);
+                                                           sPidOne, sUidOne, sLayerIdOne,
+                                                           sLayerNameOne, sLayerNameOne);
         mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, Fps::fromPeriodNsecs(11));
         surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
         mFrameTimeline->addSurfaceFrame(surfaceFrame);
@@ -329,7 +338,8 @@
             {22 + frameTimeFactor, 26 + frameTimeFactor, 30 + frameTimeFactor});
     auto surfaceFrame =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, Fps::fromPeriodNsecs(11));
     surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame);
@@ -343,18 +353,18 @@
 }
 
 TEST_F(FrameTimelineTest, surfaceFrameEndTimeAcquireFenceAfterQueue) {
-    auto surfaceFrame =
-            mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, 0, "acquireFenceAfterQueue",
-                                                       "acquireFenceAfterQueue");
+    auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, 0, sLayerIdOne,
+                                                                   "acquireFenceAfterQueue",
+                                                                   "acquireFenceAfterQueue");
     surfaceFrame->setActualQueueTime(123);
     surfaceFrame->setAcquireFenceTime(456);
     EXPECT_EQ(surfaceFrame->getActuals().endTime, 456);
 }
 
 TEST_F(FrameTimelineTest, surfaceFrameEndTimeAcquireFenceBeforeQueue) {
-    auto surfaceFrame =
-            mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, 0, "acquireFenceAfterQueue",
-                                                       "acquireFenceAfterQueue");
+    auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, 0, sLayerIdOne,
+                                                                   "acquireFenceAfterQueue",
+                                                                   "acquireFenceAfterQueue");
     surfaceFrame->setActualQueueTime(456);
     surfaceFrame->setAcquireFenceTime(123);
     EXPECT_EQ(surfaceFrame->getActuals().endTime, 456);
@@ -367,8 +377,8 @@
     // Size shouldn't exceed maxDisplayFrames - 64
     for (size_t i = 0; i < *maxDisplayFrames + 10; i++) {
         auto surfaceFrame =
-                mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerNameOne,
-                                                           sLayerNameOne);
+                mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerIdOne,
+                                                           sLayerNameOne, sLayerNameOne);
         int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
         mFrameTimeline->setSfWakeUp(sfToken, 22, Fps::fromPeriodNsecs(11));
         surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
@@ -383,8 +393,8 @@
 
     for (size_t i = 0; i < *maxDisplayFrames + 10; i++) {
         auto surfaceFrame =
-                mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerNameOne,
-                                                           sLayerNameOne);
+                mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerIdOne,
+                                                           sLayerNameOne, sLayerNameOne);
         int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
         mFrameTimeline->setSfWakeUp(sfToken, 22, Fps::fromPeriodNsecs(11));
         surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
@@ -399,8 +409,8 @@
 
     for (size_t i = 0; i < *maxDisplayFrames + 10; i++) {
         auto surfaceFrame =
-                mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerNameOne,
-                                                           sLayerNameOne);
+                mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerIdOne,
+                                                           sLayerNameOne, sLayerNameOne);
         int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
         mFrameTimeline->setSfWakeUp(sfToken, 22, Fps::fromPeriodNsecs(11));
         surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
@@ -412,36 +422,29 @@
 
 // Tests related to TimeStats
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) {
-    Fps refreshRate = Fps(11);
+    Fps refreshRate = Fps::fromPeriodNsecs(11);
+    // Deadline delta is 2ms because, sf's adjusted deadline is 60 - composerTime(3) = 57ms.
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(
                         TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
                                                    sLayerNameOne,
-                                                   JankType::SurfaceFlingerCpuDeadlineMissed,
-                                                   std::chrono::duration_cast<
-                                                           std::chrono::nanoseconds>(3ms)
-                                                           .count(),
-                                                   std::chrono::duration_cast<
-                                                           std::chrono::nanoseconds>(10ms)
-                                                           .count(),
+                                                   JankType::SurfaceFlingerCpuDeadlineMissed, 2, 10,
                                                    0}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(10ms).count(), std::chrono::nanoseconds(20ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(52ms).count(), std::chrono::nanoseconds(56ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(52ms).count(), refreshRate);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(20ms).count());
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    surfaceFrame1->setAcquireFenceTime(20);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(70ms).count());
+    presentFence1->signalForTest(70);
 
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(59ms).count(), presentFence1);
+    mFrameTimeline->setSfPresent(59, presentFence1);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsDisplayMiss) {
@@ -449,180 +452,153 @@
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
                                                                 sLayerNameOne, JankType::DisplayHAL,
-                                                                0, 0, 0}));
+                                                                -1, 0, 0}));
 
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(10ms).count(), std::chrono::nanoseconds(20ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(52ms).count(), std::chrono::nanoseconds(56ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(52ms).count(), refreshRate);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(20ms).count());
+    surfaceFrame1->setAcquireFenceTime(20);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(90ms).count());
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(56ms).count(), presentFence1);
+    presentFence1->signalForTest(90);
+    mFrameTimeline->setSfPresent(56, presentFence1);
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::DisplayHAL);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) {
     Fps refreshRate = Fps(11.0);
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(
-                        TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
-                                                   sLayerNameOne, JankType::AppDeadlineMissed, 0, 0,
-                                                   std::chrono::duration_cast<
-                                                           std::chrono::nanoseconds>(25ms)
-                                                           .count()}));
+                incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
+                                                                sLayerNameOne,
+                                                                JankType::AppDeadlineMissed, -1, 0,
+                                                                25}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(10ms).count(), std::chrono::nanoseconds(20ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(82ms).count(), std::chrono::nanoseconds(86ms).count(),
-             std::chrono::nanoseconds(90ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(45ms).count());
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(52ms).count(), refreshRate);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
+    surfaceFrame1->setAcquireFenceTime(45);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(90ms).count());
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(86ms).count(), presentFence1);
+    presentFence1->signalForTest(90);
+    mFrameTimeline->setSfPresent(86, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfScheduling) {
-    Fps refreshRate = Fps::fromPeriodNsecs(std::chrono::nanoseconds(32ms).count());
+    Fps refreshRate = Fps::fromPeriodNsecs(32);
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(
-                        TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
-                                                   sLayerNameOne,
-                                                   JankType::SurfaceFlingerScheduling, 0, 0,
-                                                   std::chrono::duration_cast<
-                                                           std::chrono::nanoseconds>(-10ms)
-                                                           .count()}));
+                incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
+                                                                sLayerNameOne,
+                                                                JankType::SurfaceFlingerScheduling,
+                                                                -1, 0, -10}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(40ms).count(), std::chrono::nanoseconds(60ms).count(),
-             std::chrono::nanoseconds(92ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(52ms).count(), std::chrono::nanoseconds(56ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({40, 60, 92});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(50ms).count());
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(52ms).count(), refreshRate);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
+    surfaceFrame1->setAcquireFenceTime(50);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(60ms).count());
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(56ms).count(), presentFence1);
+    presentFence1->signalForTest(60);
+    mFrameTimeline->setSfPresent(56, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::SurfaceFlingerScheduling);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfPredictionError) {
-    Fps refreshRate = Fps(16.66f);
+    Fps refreshRate = Fps::fromPeriodNsecs(16);
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(
-                        TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
-                                                   sLayerNameOne, JankType::PredictionError, 0,
-                                                   std::chrono::duration_cast<
-                                                           std::chrono::nanoseconds>(5ms)
-                                                           .count(),
-                                                   0}));
+                incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
+                                                                sLayerNameOne,
+                                                                JankType::PredictionError, -1, 5,
+                                                                0}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(30ms).count(), std::chrono::nanoseconds(40ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(52ms).count(), std::chrono::nanoseconds(56ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({30, 40, 60});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(40ms).count());
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(52ms).count(), refreshRate);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
+    surfaceFrame1->setAcquireFenceTime(40);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(65ms).count());
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(56ms).count(), presentFence1);
+    presentFence1->signalForTest(65);
+    mFrameTimeline->setSfPresent(56, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::PredictionError);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppBufferStuffing) {
-    Fps refreshRate = Fps::fromPeriodNsecs(std::chrono::nanoseconds(32ms).count());
+    Fps refreshRate = Fps::fromPeriodNsecs(32);
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(
-                        TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
-                                                   sLayerNameOne,
-                                                   JankType::BufferStuffing |
-                                                           JankType::SurfaceFlingerScheduling,
-                                                   0, 0, 0}));
+                incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
+                                                                sLayerNameOne,
+                                                                JankType::BufferStuffing, -1, 0,
+                                                                0}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(30ms).count(), std::chrono::nanoseconds(40ms).count(),
-             std::chrono::nanoseconds(58ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(82ms).count(), std::chrono::nanoseconds(86ms).count(),
-             std::chrono::nanoseconds(90ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({30, 40, 58});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(40ms).count());
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(82ms).count(), refreshRate);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
+    surfaceFrame1->setAcquireFenceTime(40);
+    mFrameTimeline->setSfWakeUp(sfToken1, 82, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented,
-                                   /*previousLatchTime*/
-                                   std::chrono::nanoseconds(56ms).count());
+                                   /*previousLatchTime*/ 56);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(90ms).count());
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(86ms).count(), presentFence1);
+    presentFence1->signalForTest(90);
+    mFrameTimeline->setSfPresent(86, presentFence1);
 
-    EXPECT_EQ(surfaceFrame1->getJankType(),
-              JankType::BufferStuffing | JankType::SurfaceFlingerScheduling);
+    EXPECT_EQ(surfaceFrame1->getJankType(), JankType::BufferStuffing);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMissWithRenderRate) {
-    Fps refreshRate = Fps(11.0);
-    Fps renderRate = Fps(30.0);
+    Fps refreshRate = Fps::fromPeriodNsecs(11);
+    Fps renderRate = Fps::fromPeriodNsecs(30);
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(
                         TimeStats::JankyFramesInfo{refreshRate, renderRate, sUidOne, sLayerNameOne,
-                                                   JankType::AppDeadlineMissed, 0, 0,
-                                                   std::chrono::duration_cast<
-                                                           std::chrono::nanoseconds>(25ms)
-                                                           .count()}));
+                                                   JankType::AppDeadlineMissed, -1, 0, 25}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(10ms).count(), std::chrono::nanoseconds(20ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(82ms).count(), std::chrono::nanoseconds(86ms).count(),
-             std::chrono::nanoseconds(90ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(45ms).count());
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(52ms).count(), refreshRate);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
+    surfaceFrame1->setAcquireFenceTime(45);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     surfaceFrame1->setRenderRate(renderRate);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(90ms).count());
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(86ms).count(), presentFence1);
+    presentFence1->signalForTest(90);
+    mFrameTimeline->setSfPresent(86, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
 }
@@ -641,7 +617,7 @@
     int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne,
-                                                       sLayerNameOne, sLayerNameOne);
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
 
     // Set up the display frame
     mFrameTimeline->setSfWakeUp(token1, 20, Fps::fromPeriodNsecs(11));
@@ -667,7 +643,7 @@
     int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne,
-                                                       sLayerNameOne, sLayerNameOne);
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
 
     // Set up the display frame
     mFrameTimeline->setSfWakeUp(token2, 20, Fps::fromPeriodNsecs(11));
@@ -710,8 +686,9 @@
 
     tracingSession->StartBlocking();
     int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
-    auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne,
-                                                                    sLayerNameOne, sLayerNameOne);
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne);
 
     // Set up the display frame
     mFrameTimeline->setSfWakeUp(token1, 20, Fps::fromPeriodNsecs(11));
@@ -741,7 +718,7 @@
 
 ProtoActualDisplayFrameStart createProtoActualDisplayFrameStart(
         int64_t cookie, int64_t token, pid_t pid, ProtoPresentType presentType, bool onTimeFinish,
-        bool gpuComposition, ProtoJankType jankType) {
+        bool gpuComposition, ProtoJankType jankType, ProtoPredictionType predictionType) {
     ProtoActualDisplayFrameStart proto;
     proto.set_cookie(cookie);
     proto.set_token(token);
@@ -750,6 +727,7 @@
     proto.set_on_time_finish(onTimeFinish);
     proto.set_gpu_composition(gpuComposition);
     proto.set_jank_type(jankType);
+    proto.set_prediction_type(predictionType);
     return proto;
 }
 
@@ -769,7 +747,7 @@
 ProtoActualSurfaceFrameStart createProtoActualSurfaceFrameStart(
         int64_t cookie, int64_t token, int64_t displayFrameToken, pid_t pid, std::string layerName,
         ProtoPresentType presentType, bool onTimeFinish, bool gpuComposition,
-        ProtoJankType jankType) {
+        ProtoJankType jankType, ProtoPredictionType predictionType) {
     ProtoActualSurfaceFrameStart proto;
     proto.set_cookie(cookie);
     proto.set_token(token);
@@ -780,6 +758,7 @@
     proto.set_on_time_finish(onTimeFinish);
     proto.set_gpu_composition(gpuComposition);
     proto.set_jank_type(jankType);
+    proto.set_prediction_type(predictionType);
     return proto;
 }
 
@@ -820,6 +799,8 @@
     EXPECT_EQ(received.gpu_composition(), source.gpu_composition());
     ASSERT_TRUE(received.has_jank_type());
     EXPECT_EQ(received.jank_type(), source.jank_type());
+    ASSERT_TRUE(received.has_prediction_type());
+    EXPECT_EQ(received.prediction_type(), source.prediction_type());
 }
 
 void validateTraceEvent(const ProtoExpectedSurfaceFrameStart& received,
@@ -865,6 +846,8 @@
     EXPECT_EQ(received.gpu_composition(), source.gpu_composition());
     ASSERT_TRUE(received.has_jank_type());
     EXPECT_EQ(received.jank_type(), source.jank_type());
+    ASSERT_TRUE(received.has_prediction_type());
+    EXPECT_EQ(received.prediction_type(), source.prediction_type());
 }
 
 void validateTraceEvent(const ProtoFrameEnd& received, const ProtoFrameEnd& source) {
@@ -877,7 +860,7 @@
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
 
     tracingSession->StartBlocking();
-    int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 25, 30});
+    int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 30, 30});
 
     // Set up the display frame
     mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
@@ -893,7 +876,8 @@
             createProtoActualDisplayFrameStart(traceCookie + 2, displayFrameToken1,
                                                kSurfaceFlingerPid,
                                                FrameTimelineEvent::PRESENT_ON_TIME, true, false,
-                                               FrameTimelineEvent::JANK_NONE);
+                                               FrameTimelineEvent::JANK_NONE,
+                                               FrameTimelineEvent::PREDICTION_VALID);
     auto protoActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 2);
 
     addEmptyDisplayFrame();
@@ -917,7 +901,7 @@
     // Packet - 1 : FrameEnd (ExpectedDisplayFrame)
     const auto& packet1 = packets[1];
     ASSERT_TRUE(packet1.has_timestamp());
-    EXPECT_EQ(packet1.timestamp(), 25u);
+    EXPECT_EQ(packet1.timestamp(), 30u);
     ASSERT_TRUE(packet1.has_frame_timeline_event());
 
     const auto& event1 = packet1.frame_timeline_event();
@@ -939,7 +923,7 @@
     // Packet - 3 : FrameEnd (ActualDisplayFrame)
     const auto& packet3 = packets[3];
     ASSERT_TRUE(packet3.has_timestamp());
-    EXPECT_EQ(packet3.timestamp(), 26u);
+    EXPECT_EQ(packet3.timestamp(), 31u);
     ASSERT_TRUE(packet3.has_frame_timeline_event());
 
     const auto& event3 = packet3.frame_timeline_event();
@@ -968,7 +952,8 @@
             createProtoActualDisplayFrameStart(traceCookie + 1, displayFrameToken1,
                                                kSurfaceFlingerPid,
                                                FrameTimelineEvent::PRESENT_UNSPECIFIED, false,
-                                               false, FrameTimelineEvent::JANK_UNKNOWN);
+                                               false, FrameTimelineEvent::JANK_UNKNOWN,
+                                               FrameTimelineEvent::PREDICTION_EXPIRED);
     auto protoActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
     addEmptyDisplayFrame();
@@ -993,7 +978,7 @@
     // Packet - 1 : FrameEnd (ActualDisplayFrame)
     const auto& packet1 = packets[1];
     ASSERT_TRUE(packet1.has_timestamp());
-    EXPECT_EQ(packet1.timestamp(), 26u);
+    EXPECT_EQ(packet1.timestamp(), 31u);
     ASSERT_TRUE(packet1.has_frame_timeline_event());
 
     const auto& event1 = packet1.frame_timeline_event();
@@ -1015,10 +1000,12 @@
 
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     auto surfaceFrame2 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     surfaceFrame1->setActualQueueTime(10);
     surfaceFrame1->setDropTime(15);
 
@@ -1036,7 +1023,8 @@
             createProtoActualSurfaceFrameStart(traceCookie + 2, surfaceFrameToken,
                                                displayFrameToken1, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_DROPPED, false, false,
-                                               FrameTimelineEvent::JANK_NONE);
+                                               FrameTimelineEvent::JANK_NONE,
+                                               FrameTimelineEvent::PREDICTION_VALID);
     auto protoDroppedSurfaceFrameActualEnd = createProtoFrameEnd(traceCookie + 2);
 
     auto protoPresentedSurfaceFrameExpectedStart =
@@ -1047,7 +1035,8 @@
             createProtoActualSurfaceFrameStart(traceCookie + 4, surfaceFrameToken,
                                                displayFrameToken1, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_ON_TIME, true, false,
-                                               FrameTimelineEvent::JANK_NONE);
+                                               FrameTimelineEvent::JANK_NONE,
+                                               FrameTimelineEvent::PREDICTION_VALID);
     auto protoPresentedSurfaceFrameActualEnd = createProtoFrameEnd(traceCookie + 4);
 
     // Set up the display frame
@@ -1161,12 +1150,9 @@
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
 
     tracingSession->StartBlocking();
-    constexpr nsecs_t appStartTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(10ms).count();
-    constexpr nsecs_t appEndTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(20ms).count();
-    constexpr nsecs_t appPresentTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(30ms).count();
+    constexpr nsecs_t appStartTime = std::chrono::nanoseconds(10ms).count();
+    constexpr nsecs_t appEndTime = std::chrono::nanoseconds(20ms).count();
+    constexpr nsecs_t appPresentTime = std::chrono::nanoseconds(30ms).count();
     int64_t surfaceFrameToken =
             mTokenManager->generateTokenForPredictions({appStartTime, appEndTime, appPresentTime});
 
@@ -1174,17 +1160,14 @@
     flushTokens(systemTime() + maxTokenRetentionTime);
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, /*inputEventId*/ 0},
-                                                       sPidOne, sUidOne, sLayerNameOne,
+                                                       sPidOne, sUidOne, sLayerIdOne, sLayerNameOne,
                                                        sLayerNameOne);
     surfaceFrame1->setActualQueueTime(appEndTime);
     surfaceFrame1->setAcquireFenceTime(appEndTime);
 
-    constexpr nsecs_t sfStartTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(20ms).count();
-    constexpr nsecs_t sfEndTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(30ms).count();
-    constexpr nsecs_t sfPresentTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(30ms).count();
+    constexpr nsecs_t sfStartTime = std::chrono::nanoseconds(20ms).count();
+    constexpr nsecs_t sfEndTime = std::chrono::nanoseconds(30ms).count();
+    constexpr nsecs_t sfPresentTime = std::chrono::nanoseconds(30ms).count();
     int64_t displayFrameToken =
             mTokenManager->generateTokenForPredictions({sfStartTime, sfEndTime, sfPresentTime});
 
@@ -1195,7 +1178,8 @@
             createProtoActualSurfaceFrameStart(traceCookie + 1, surfaceFrameToken,
                                                displayFrameToken, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_UNSPECIFIED, false,
-                                               false, FrameTimelineEvent::JANK_UNKNOWN);
+                                               false, FrameTimelineEvent::JANK_UNKNOWN,
+                                               FrameTimelineEvent::PREDICTION_EXPIRED);
     auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
     // Set up the display frame
@@ -1243,10 +1227,11 @@
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t surfaceFrameToken = mTokenManager->generateTokenForPredictions({10, 20, 30});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 30});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 30});
     auto surfaceFrame =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
     surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame);
@@ -1273,8 +1258,8 @@
 TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishEarlyPresent) {
     Fps vsyncRate = Fps::fromPeriodNsecs(11);
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
-    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 70});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
     mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate);
     mFrameTimeline->setSfPresent(26, presentFence1);
     auto displayFrame = getDisplayFrame(0);
@@ -1312,8 +1297,8 @@
 TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishLatePresent) {
     Fps vsyncRate = Fps::fromPeriodNsecs(11);
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
-    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 70});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
     mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate);
     mFrameTimeline->setSfPresent(26, presentFence1);
     auto displayFrame = getDisplayFrame(0);
@@ -1392,21 +1377,44 @@
     EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerCpuDeadlineMissed);
 }
 
+TEST_F(FrameTimelineTest, jankClassification_displayFrameLateStartLateFinishLatePresent) {
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
+    mFrameTimeline->setSfWakeUp(sfToken1, 26, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfPresent(36, presentFence1);
+    auto displayFrame = getDisplayFrame(0);
+    presentFence1->signalForTest(52);
+
+    // Fences haven't been flushed yet, so it should be 0
+    EXPECT_EQ(displayFrame->getActuals().presentTime, 0);
+
+    addEmptyDisplayFrame();
+    displayFrame = getDisplayFrame(0);
+
+    // Fences have flushed, so the present timestamps should be updated
+    EXPECT_EQ(displayFrame->getActuals().presentTime, 52);
+    EXPECT_EQ(displayFrame->getFrameStartMetadata(), FrameStartMetadata::LateStart);
+    EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+    EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+    EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerScheduling);
+}
+
 TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishEarlyPresent) {
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
-    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 70});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 40});
     int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 70});
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     surfaceFrame1->setAcquireFenceTime(16);
     mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    mFrameTimeline->setSfPresent(26, presentFence1);
+    mFrameTimeline->setSfPresent(27, presentFence1);
     auto displayFrame1 = getDisplayFrame(0);
     auto& presentedSurfaceFrame1 = getSurfaceFrame(0, 0);
     presentFence1->signalForTest(30);
@@ -1420,12 +1428,13 @@
     auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     auto surfaceFrame2 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     surfaceFrame2->setAcquireFenceTime(36);
     mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11));
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
-    mFrameTimeline->setSfPresent(56, presentFence2);
+    mFrameTimeline->setSfPresent(57, presentFence2);
     auto displayFrame2 = getDisplayFrame(1);
     auto& presentedSurfaceFrame2 = getSurfaceFrame(1, 0);
 
@@ -1473,13 +1482,14 @@
 TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishLatePresent) {
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
-    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 70});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 40});
     int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 70});
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     surfaceFrame1->setAcquireFenceTime(16);
     mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
@@ -1498,12 +1508,13 @@
     auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     auto surfaceFrame2 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     surfaceFrame2->setAcquireFenceTime(36);
     mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11));
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
-    mFrameTimeline->setSfPresent(56, presentFence2);
+    mFrameTimeline->setSfPresent(57, presentFence2);
     auto displayFrame2 = getDisplayFrame(1);
     auto& presentedSurfaceFrame2 = getSurfaceFrame(1, 0);
 
@@ -1552,11 +1563,12 @@
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_));
 
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({42, 46, 50});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({42, 50, 50});
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 26, 60});
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     surfaceFrame1->setAcquireFenceTime(40);
     mFrameTimeline->setSfWakeUp(sfToken1, 42, Fps::fromPeriodNsecs(11));
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
@@ -1593,13 +1605,14 @@
 
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(2);
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({32, 36, 40});
-    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({42, 46, 50});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({32, 40, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({42, 50, 50});
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 30});
     int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 50});
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     surfaceFrame1->setAcquireFenceTime(26);
     mFrameTimeline->setSfWakeUp(sfToken1, 32, Fps::fromPeriodNsecs(11));
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
@@ -1618,7 +1631,8 @@
     auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     auto surfaceFrame2 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     surfaceFrame2->setAcquireFenceTime(40);
     mFrameTimeline->setSfWakeUp(sfToken2, 43, Fps::fromPeriodNsecs(11));
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
@@ -1667,11 +1681,12 @@
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
     int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
 
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 56, 60});
-    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({112, 116, 120});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({112, 120, 120});
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     surfaceFrame1->setAcquireFenceTime(50);
     mFrameTimeline->setSfWakeUp(sfToken1, 52, Fps::fromPeriodNsecs(30));
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
@@ -1690,7 +1705,8 @@
     auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     auto surfaceFrame2 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne,
-                                                       sUidOne, sLayerNameOne, sLayerNameOne);
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
     surfaceFrame2->setAcquireFenceTime(84);
     mFrameTimeline->setSfWakeUp(sfToken2, 112, Fps::fromPeriodNsecs(30));
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented, 54);
@@ -1735,4 +1751,229 @@
     EXPECT_EQ(presentedSurfaceFrame2.getJankType(),
               JankType::AppDeadlineMissed | JankType::BufferStuffing);
 }
+
+TEST_F(FrameTimelineTest, jankClassification_appDeadlineAdjustedForBufferStuffing) {
+    // Layer specific increment
+    EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(2);
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+    int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
+    surfaceFrame1->setAcquireFenceTime(50);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, Fps::fromPeriodNsecs(30));
+    surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+    mFrameTimeline->setSfPresent(56, presentFence1);
+    auto displayFrame1 = getDisplayFrame(0);
+    auto& presentedSurfaceFrame1 = getSurfaceFrame(0, 0);
+    presentFence1->signalForTest(60);
+
+    // Fences for the first frame haven't been flushed yet, so it should be 0
+    EXPECT_EQ(displayFrame1->getActuals().presentTime, 0);
+    auto actuals1 = presentedSurfaceFrame1.getActuals();
+    EXPECT_EQ(actuals1.presentTime, 0);
+
+    // Trigger a flush by finalizing the next DisplayFrame
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    auto surfaceFrame2 =
+            mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne,
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
+    surfaceFrame2->setAcquireFenceTime(80);
+    mFrameTimeline->setSfWakeUp(sfToken2, 82, Fps::fromPeriodNsecs(30));
+    // Setting previous latch time to 54, adjusted deadline will be 54 + vsyncTime(30) = 84
+    surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented, 54);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+    mFrameTimeline->setSfPresent(86, presentFence2);
+    auto displayFrame2 = getDisplayFrame(1);
+    auto& presentedSurfaceFrame2 = getSurfaceFrame(1, 0);
+    presentFence2->signalForTest(90);
+
+    // Fences for the first frame have flushed, so the present timestamps should be updated
+    EXPECT_EQ(displayFrame1->getActuals().presentTime, 60);
+    actuals1 = presentedSurfaceFrame1.getActuals();
+    EXPECT_EQ(actuals1.endTime, 50);
+    EXPECT_EQ(actuals1.presentTime, 60);
+
+    EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
+    EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+    EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+
+    EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+    EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+    EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::AppDeadlineMissed);
+
+    // Fences for the second frame haven't been flushed yet, so it should be 0
+    EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
+    auto actuals2 = presentedSurfaceFrame2.getActuals();
+    EXPECT_EQ(actuals2.presentTime, 0);
+
+    addEmptyDisplayFrame();
+
+    // Fences for the second frame have flushed, so the present timestamps should be updated
+    EXPECT_EQ(displayFrame2->getActuals().presentTime, 90);
+    actuals2 = presentedSurfaceFrame2.getActuals();
+    EXPECT_EQ(actuals2.presentTime, 90);
+
+    EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
+    EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+    EXPECT_EQ(displayFrame2->getJankType(), JankType::None);
+
+    EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+    EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+    EXPECT_EQ(presentedSurfaceFrame2.getJankType(), JankType::BufferStuffing);
+}
+
+TEST_F(FrameTimelineTest, computeFps_noLayerIds_returnsZero) {
+    EXPECT_EQ(mFrameTimeline->computeFps({}), 0.0f);
+}
+
+TEST_F(FrameTimelineTest, computeFps_singleDisplayFrame_returnsZero) {
+    const auto oneHundredMs = std::chrono::nanoseconds(100ms).count();
+
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+    presentFence1->signalForTest(oneHundredMs);
+    mFrameTimeline->setSfPresent(oneHundredMs, presentFence1);
+
+    EXPECT_EQ(mFrameTimeline->computeFps({sLayerIdOne}), 0.0f);
+}
+
+TEST_F(FrameTimelineTest, computeFps_twoDisplayFrames_oneLayer) {
+    const auto oneHundredMs = std::chrono::nanoseconds(100ms).count();
+    const auto twoHundredMs = std::chrono::nanoseconds(200ms).count();
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+    presentFence1->signalForTest(oneHundredMs);
+    mFrameTimeline->setSfPresent(oneHundredMs, presentFence1);
+
+    auto surfaceFrame2 =
+            mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+    presentFence2->signalForTest(twoHundredMs);
+    mFrameTimeline->setSfPresent(twoHundredMs, presentFence2);
+
+    EXPECT_EQ(mFrameTimeline->computeFps({sLayerIdOne}), 10.0);
+}
+
+TEST_F(FrameTimelineTest, computeFps_twoDisplayFrames_twoLayers) {
+    const auto oneHundredMs = std::chrono::nanoseconds(100ms).count();
+    const auto twoHundredMs = std::chrono::nanoseconds(200ms).count();
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+    presentFence1->signalForTest(oneHundredMs);
+    mFrameTimeline->setSfPresent(oneHundredMs, presentFence1);
+
+    auto surfaceFrame2 =
+            mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
+                                                       sLayerIdTwo, sLayerNameTwo, sLayerNameTwo);
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+    presentFence2->signalForTest(twoHundredMs);
+    mFrameTimeline->setSfPresent(twoHundredMs, presentFence2);
+
+    EXPECT_EQ(mFrameTimeline->computeFps({sLayerIdOne, sLayerIdTwo}), 10.0f);
+}
+
+TEST_F(FrameTimelineTest, computeFps_filtersOutLayers) {
+    const auto oneHundredMs = std::chrono::nanoseconds(100ms).count();
+    const auto twoHundredMs = std::chrono::nanoseconds(200ms).count();
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+    presentFence1->signalForTest(oneHundredMs);
+    mFrameTimeline->setSfPresent(oneHundredMs, presentFence1);
+
+    auto surfaceFrame2 =
+            mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
+                                                       sLayerIdTwo, sLayerNameTwo, sLayerNameTwo);
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+    presentFence2->signalForTest(twoHundredMs);
+    mFrameTimeline->setSfPresent(twoHundredMs, presentFence2);
+
+    EXPECT_EQ(mFrameTimeline->computeFps({sLayerIdOne}), 0.0f);
+}
+
+TEST_F(FrameTimelineTest, computeFps_averagesOverMultipleFrames) {
+    const auto oneHundredMs = std::chrono::nanoseconds(100ms).count();
+    const auto twoHundredMs = std::chrono::nanoseconds(200ms).count();
+    const auto threeHundredMs = std::chrono::nanoseconds(300ms).count();
+    const auto fiveHundredMs = std::chrono::nanoseconds(500ms).count();
+    const auto sixHundredMs = std::chrono::nanoseconds(600ms).count();
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+    presentFence1->signalForTest(oneHundredMs);
+    mFrameTimeline->setSfPresent(oneHundredMs, presentFence1);
+
+    auto surfaceFrame2 =
+            mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+    presentFence2->signalForTest(twoHundredMs);
+    mFrameTimeline->setSfPresent(twoHundredMs, presentFence2);
+
+    auto surfaceFrame3 =
+            mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
+                                                       sLayerIdTwo, sLayerNameTwo, sLayerNameTwo);
+    auto presentFence3 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    surfaceFrame3->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame3);
+    presentFence3->signalForTest(threeHundredMs);
+    mFrameTimeline->setSfPresent(threeHundredMs, presentFence3);
+
+    auto surfaceFrame4 =
+            mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
+    auto presentFence4 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    surfaceFrame4->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame4);
+    presentFence4->signalForTest(fiveHundredMs);
+    mFrameTimeline->setSfPresent(fiveHundredMs, presentFence4);
+
+    auto surfaceFrame5 =
+            mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
+                                                       sLayerIdOne, sLayerNameOne, sLayerNameOne);
+    auto presentFence5 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    // Dropped frames will be excluded from fps computation
+    surfaceFrame5->setPresentState(SurfaceFrame::PresentState::Dropped);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame5);
+    presentFence5->signalForTest(sixHundredMs);
+    mFrameTimeline->setSfPresent(sixHundredMs, presentFence5);
+
+    EXPECT_EQ(mFrameTimeline->computeFps({sLayerIdOne}), 5.0f);
+}
+
 } // namespace android::frametimeline
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index a6d07d0..5c8c2d8 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -81,7 +81,7 @@
     std::string name() override { return "BufferStateLayer"; }
     sp<Layer> createLayer(TestableSurfaceFlinger& flinger) override {
         sp<Client> client;
-        LayerCreationArgs args(flinger.flinger(), client, "buffer-queue-layer", WIDTH, HEIGHT,
+        LayerCreationArgs args(flinger.flinger(), client, "buffer-state-layer", WIDTH, HEIGHT,
                                LAYER_FLAGS, LayerMetadata());
         return new BufferStateLayer(args);
     }
@@ -188,7 +188,8 @@
             .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
     EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
     mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
-                            std::move(eventThread), std::move(sfEventThread));
+                            std::move(eventThread), std::move(sfEventThread), /*callback*/ nullptr,
+                            /*hasMultipleModes*/ true);
 }
 
 void SetFrameRateTest::setupComposer(uint32_t virtualDisplayCount) {
@@ -488,5 +489,28 @@
     EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, ""));
 }
 
+TEST_P(SetFrameRateTest, SetOnParentActivatesTree) {
+    const auto& layerFactory = GetParam();
+
+    auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    if (!parent->isVisible()) {
+        // This is a hack as all the test layers except EffectLayer are not visible,
+        // but since the logic is unified in Layer, it should be fine.
+        return;
+    }
+
+    auto child = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    addChild(parent, child);
+
+    parent->setFrameRate(FRAME_RATE_VOTE1);
+    commitTransaction();
+
+    const auto layerHistorySummary =
+            mFlinger.mutableScheduler().mutableLayerHistory()->summarize(0);
+    ASSERT_EQ(2u, layerHistorySummary.size());
+    EXPECT_TRUE(FRAME_RATE_VOTE1.rate.equalsWithMargin(layerHistorySummary[0].desiredRefreshRate));
+    EXPECT_TRUE(FRAME_RATE_VOTE1.rate.equalsWithMargin(layerHistorySummary[1].desiredRefreshRate));
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index dee13d6..63baf7d 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -246,6 +246,8 @@
 
     void resetScheduler(Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); }
 
+    TestableScheduler& mutableScheduler() const { return *mScheduler; }
+
     using CreateBufferQueueFunction = surfaceflinger::test::Factory::CreateBufferQueueFunction;
     void setCreateBufferQueueFunction(CreateBufferQueueFunction f) {
         mFactory.mCreateBufferQueue = f;
@@ -634,7 +636,7 @@
     public:
         FakeDisplayDeviceInjector(TestableSurfaceFlinger& flinger,
                                   std::shared_ptr<compositionengine::Display> compositionDisplay,
-                                  std::optional<DisplayConnectionType> connectionType,
+                                  std::optional<ui::DisplayConnectionType> connectionType,
                                   std::optional<hal::HWDisplayId> hwcDisplayId, bool isPrimary)
               : mFlinger(flinger),
                 mCreationArgs(flinger.mFlinger.get(), flinger.mFlinger->getHwComposer(),
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index eb2c1ba..7c431a0 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -127,7 +127,6 @@
         ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
         // called in SurfaceFlinger::signalTransaction
         EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
-        EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillOnce(Return(systemTime()));
         TransactionInfo transaction;
         setupSingle(transaction, flags, syncInputWindows,
                     /*desiredPresentTime*/ systemTime(), /*isAutoTimestamp*/ true,
@@ -163,8 +162,6 @@
         // first check will see desired present time has not passed,
         // but afterwards it will look like the desired present time has passed
         nsecs_t time = systemTime();
-        EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_))
-                .WillOnce(Return(time + nsecs_t(5 * 1e8)));
         TransactionInfo transaction;
         setupSingle(transaction, flags, syncInputWindows,
                     /*desiredPresentTime*/ time + s2ns(1), false, FrameTimelineInfo{});
@@ -177,7 +174,11 @@
                                      transaction.id);
 
         nsecs_t returnedTime = systemTime();
-        EXPECT_LE(returnedTime, applicationSentTime + s2ns(5));
+        if ((flags & ISurfaceComposer::eSynchronous) || syncInputWindows) {
+            EXPECT_GE(systemTime(), applicationSentTime + s2ns(5));
+        } else {
+            EXPECT_LE(returnedTime, applicationSentTime + s2ns(5));
+        }
         // This transaction should have been placed on the transaction queue
         auto transactionQueue = mFlinger.getTransactionQueue();
         EXPECT_EQ(1u, transactionQueue.size());
@@ -187,9 +188,11 @@
         ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
         // called in SurfaceFlinger::signalTransaction
         nsecs_t time = systemTime();
-        EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
-        EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_))
-                .WillOnce(Return(time + nsecs_t(5 * 1e8)));
+        if (!syncInputWindows) {
+            EXPECT_CALL(*mMessageQueue, invalidate()).Times(2);
+        } else {
+            EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+        }
         // transaction that should go on the pending thread
         TransactionInfo transactionA;
         setupSingle(transactionA, /*flags*/ 0, /*syncInputWindows*/ false,
@@ -229,7 +232,8 @@
         // if this is an animation, this thread should be blocked for 5s
         // in setTransactionState waiting for transactionA to flush.  Otherwise,
         // the transaction should be placed on the pending queue
-        if (flags & ISurfaceComposer::eAnimation) {
+        if (flags & (ISurfaceComposer::eAnimation | ISurfaceComposer::eSynchronous) ||
+            syncInputWindows) {
             EXPECT_GE(systemTime(), applicationSentTime + s2ns(5));
         } else {
             EXPECT_LE(systemTime(), applicationSentTime + s2ns(5));
@@ -238,18 +242,9 @@
         // transaction that would goes to pending transaciton queue.
         mFlinger.flushTransactionQueues();
 
-        // check that there is one binder on the pending queue.
+        // check that the transaction was applied.
         auto transactionQueue = mFlinger.getPendingTransactionQueue();
-        EXPECT_EQ(1u, transactionQueue.size());
-
-        auto& [applyToken, transactionStates] = *(transactionQueue.begin());
-        EXPECT_EQ(2u, transactionStates.size());
-
-        auto& transactionStateA = transactionStates.front();
-        transactionStates.pop();
-        checkEqual(transactionA, transactionStateA);
-        auto& transactionStateB = transactionStates.front();
-        checkEqual(transactionB, transactionStateB);
+        EXPECT_EQ(0u, transactionQueue.size());
     }
 
     bool mHasListenerCallbacks = false;
@@ -262,10 +257,6 @@
     // called in SurfaceFlinger::signalTransaction
     EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
 
-    // nsecs_t time = systemTime();
-    EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_))
-            .WillOnce(Return(nsecs_t(5 * 1e8)))
-            .WillOnce(Return(s2ns(2)));
     TransactionInfo transactionA; // transaction to go on pending queue
     setupSingle(transactionA, /*flags*/ 0, /*syncInputWindows*/ false,
                 /*desiredPresentTime*/ s2ns(1), false, FrameTimelineInfo{});
diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
index bf9ec39..363bd80 100644
--- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
@@ -368,6 +368,62 @@
 
         EXPECT_EQ(0u, layer->mPendingJankClassifications.size());
     }
+
+    void BufferSurfaceFrame_ReplaceValidTokenBufferWithInvalidTokenBuffer() {
+        sp<BufferStateLayer> layer = createBufferStateLayer();
+
+        sp<Fence> fence1(new Fence());
+        auto acquireFence1 = fenceFactory.createFenceTimeForTest(fence1);
+        sp<GraphicBuffer> buffer1{new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)};
+        layer->setBuffer(buffer1, fence1, 10, 20, false, mClientCache, 1, std::nullopt,
+                         {/*vsyncId*/ 1, /*inputEventId*/ 0});
+        EXPECT_EQ(0u, layer->mCurrentState.bufferlessSurfaceFramesTX.size());
+        ASSERT_NE(nullptr, layer->mCurrentState.bufferSurfaceFrameTX);
+        const auto droppedSurfaceFrame1 = layer->mCurrentState.bufferSurfaceFrameTX;
+
+        sp<Fence> fence2(new Fence());
+        auto acquireFence2 = fenceFactory.createFenceTimeForTest(fence2);
+        sp<GraphicBuffer> buffer2{new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)};
+        auto dropStartTime1 = systemTime();
+        layer->setBuffer(buffer2, fence2, 10, 20, false, mClientCache, 1, std::nullopt,
+                         {/*vsyncId*/ FrameTimelineInfo::INVALID_VSYNC_ID, /*inputEventId*/ 0});
+        auto dropEndTime1 = systemTime();
+        EXPECT_EQ(0u, layer->mCurrentState.bufferlessSurfaceFramesTX.size());
+        ASSERT_NE(nullptr, layer->mCurrentState.bufferSurfaceFrameTX);
+        const auto droppedSurfaceFrame2 = layer->mCurrentState.bufferSurfaceFrameTX;
+
+        sp<Fence> fence3(new Fence());
+        auto acquireFence3 = fenceFactory.createFenceTimeForTest(fence3);
+        sp<GraphicBuffer> buffer3{new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)};
+        auto dropStartTime2 = systemTime();
+        layer->setBuffer(buffer3, fence3, 10, 20, false, mClientCache, 1, std::nullopt,
+                         {/*vsyncId*/ 2, /*inputEventId*/ 0});
+        auto dropEndTime2 = systemTime();
+        acquireFence3->signalForTest(12);
+
+        EXPECT_EQ(0u, layer->mCurrentState.bufferlessSurfaceFramesTX.size());
+        ASSERT_NE(nullptr, layer->mCurrentState.bufferSurfaceFrameTX);
+        const auto& presentedSurfaceFrame = layer->mCurrentState.bufferSurfaceFrameTX;
+
+        commitTransaction(layer.get());
+        bool computeVisisbleRegions;
+        layer->updateTexImage(computeVisisbleRegions, 15, 0);
+
+        EXPECT_EQ(1, droppedSurfaceFrame1->getToken());
+        EXPECT_EQ(PresentState::Dropped, droppedSurfaceFrame1->getPresentState());
+        EXPECT_EQ(0u, droppedSurfaceFrame1->getActuals().endTime);
+        auto dropTime1 = droppedSurfaceFrame1->getDropTime();
+        EXPECT_TRUE(dropTime1 > dropStartTime1 && dropTime1 < dropEndTime1);
+
+        EXPECT_EQ(FrameTimelineInfo::INVALID_VSYNC_ID, droppedSurfaceFrame2->getToken());
+        EXPECT_EQ(PresentState::Dropped, droppedSurfaceFrame2->getPresentState());
+        EXPECT_EQ(0u, droppedSurfaceFrame2->getActuals().endTime);
+        auto dropTime2 = droppedSurfaceFrame2->getDropTime();
+        EXPECT_TRUE(dropTime2 > dropStartTime2 && dropTime2 < dropEndTime2);
+
+        EXPECT_EQ(2, presentedSurfaceFrame->getToken());
+        EXPECT_EQ(PresentState::Presented, presentedSurfaceFrame->getPresentState());
+    }
 };
 
 TEST_F(TransactionSurfaceFrameTest, PresentedBufferlessSurfaceFrame) {
@@ -407,4 +463,10 @@
 TEST_F(TransactionSurfaceFrameTest, PendingSurfaceFramesRemovedAfterClassification) {
     PendingSurfaceFramesRemovedAfterClassification();
 }
+
+TEST_F(TransactionSurfaceFrameTest,
+       BufferSurfaceFrame_ReplaceValidTokenBufferWithInvalidTokenBuffer) {
+    BufferSurfaceFrame_ReplaceValidTokenBufferWithInvalidTokenBuffer();
+}
+
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
index 44b9b73..b9d1794 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
@@ -33,6 +33,7 @@
     MOCK_METHOD1(addSurfaceFrame, void(std::shared_ptr<frametimeline::SurfaceFrame>));
     MOCK_METHOD3(setSfWakeUp, void(int64_t, nsecs_t, Fps));
     MOCK_METHOD2(setSfPresent, void(nsecs_t, const std::shared_ptr<FenceTime>&));
+    MOCK_METHOD1(computeFps, float(const std::unordered_set<int32_t>&));
 };
 
 } // namespace android::mock
diff --git a/services/vibratorservice/Android.bp b/services/vibratorservice/Android.bp
index 4f89353..2002bdf 100644
--- a/services/vibratorservice/Android.bp
+++ b/services/vibratorservice/Android.bp
@@ -12,6 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
 cc_library_shared {
     name: "libvibratorservice",
 
diff --git a/services/vibratorservice/VibratorHalController.cpp b/services/vibratorservice/VibratorHalController.cpp
index bcd9957..537e49b 100644
--- a/services/vibratorservice/VibratorHalController.cpp
+++ b/services/vibratorservice/VibratorHalController.cpp
@@ -217,10 +217,10 @@
     return apply(performEffectFn, "performEffect");
 }
 
-HalResult<void> HalController::performComposedEffect(
+HalResult<milliseconds> HalController::performComposedEffect(
         const std::vector<CompositeEffect>& primitiveEffects,
         const std::function<void()>& completionCallback) {
-    hal_fn<void> performComposedEffectFn = [&](std::shared_ptr<HalWrapper> hal) {
+    hal_fn<milliseconds> performComposedEffectFn = [&](std::shared_ptr<HalWrapper> hal) {
         return hal->performComposedEffect(primitiveEffects, completionCallback);
     };
     return apply(performComposedEffectFn, "performComposedEffect");
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index 7fee82f..6faab38 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -224,12 +224,45 @@
     return ret;
 }
 
-HalResult<void> AidlHalWrapper::performComposedEffect(
+HalResult<milliseconds> AidlHalWrapper::performComposedEffect(
         const std::vector<CompositeEffect>& primitiveEffects,
         const std::function<void()>& completionCallback) {
     // This method should always support callbacks, so no need to double check.
     auto cb = new HalCallbackWrapper(completionCallback);
-    return HalResult<void>::fromStatus(getHal()->compose(primitiveEffects, cb));
+    milliseconds duration(0);
+    for (const auto& effect : primitiveEffects) {
+        auto durationResult = getPrimitiveDuration(effect.primitive);
+        if (durationResult.isOk()) {
+            duration += durationResult.value();
+        }
+        duration += milliseconds(effect.delayMs);
+    }
+    return HalResult<milliseconds>::fromStatus(getHal()->compose(primitiveEffects, cb), duration);
+}
+
+HalResult<milliseconds> AidlHalWrapper::getPrimitiveDuration(CompositePrimitive primitive) {
+    std::lock_guard<std::mutex> lock(mSupportedPrimitivesMutex);
+    if (mPrimitiveDurations.empty()) {
+        constexpr auto primitiveRange = enum_range<CompositePrimitive>();
+        constexpr auto primitiveCount = std::distance(primitiveRange.begin(), primitiveRange.end());
+        mPrimitiveDurations.resize(primitiveCount);
+    }
+    auto primitiveIdx = static_cast<size_t>(primitive);
+    if (primitiveIdx >= mPrimitiveDurations.size()) {
+        // Safety check, should not happen if enum_range is correct.
+        return HalResult<milliseconds>::unsupported();
+    }
+    auto& cache = mPrimitiveDurations[primitiveIdx];
+    if (cache.has_value()) {
+        return HalResult<milliseconds>::ok(*cache);
+    }
+    int32_t duration;
+    auto result = getHal()->getPrimitiveDuration(primitive, &duration);
+    if (result.isOk()) {
+        // Cache copy of returned value.
+        cache.emplace(duration);
+    }
+    return HalResult<milliseconds>::fromStatus(result, milliseconds(duration));
 }
 
 HalResult<Capabilities> AidlHalWrapper::getCapabilitiesInternal() {
@@ -333,10 +366,10 @@
 }
 
 template <typename I>
-HalResult<void> HidlHalWrapper<I>::performComposedEffect(const std::vector<CompositeEffect>&,
-                                                         const std::function<void()>&) {
+HalResult<std::chrono::milliseconds> HidlHalWrapper<I>::performComposedEffect(
+        const std::vector<CompositeEffect>&, const std::function<void()>&) {
     ALOGV("Skipped composed effect because Vibrator HAL AIDL is not available");
-    return HalResult<void>::unsupported();
+    return HalResult<std::chrono::milliseconds>::unsupported();
 }
 
 template <typename I>
diff --git a/services/vibratorservice/benchmarks/Android.bp b/services/vibratorservice/benchmarks/Android.bp
index 7b4cc19..a468146 100644
--- a/services/vibratorservice/benchmarks/Android.bp
+++ b/services/vibratorservice/benchmarks/Android.bp
@@ -12,6 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
 cc_benchmark {
     name: "libvibratorservice_benchmarks",
     srcs: [
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalController.h b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
index c405545..16d571d 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
@@ -77,7 +77,7 @@
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
             const std::function<void()>& completionCallback) final override;
 
-    HalResult<void> performComposedEffect(
+    HalResult<std::chrono::milliseconds> performComposedEffect(
             const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
             const std::function<void()>& completionCallback) final override;
 
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index 638b483..e22ad34 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -189,7 +189,7 @@
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
             const std::function<void()>& completionCallback) = 0;
 
-    virtual HalResult<void> performComposedEffect(
+    virtual HalResult<std::chrono::milliseconds> performComposedEffect(
             const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
             const std::function<void()>& completionCallback) = 0;
 
@@ -236,7 +236,7 @@
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
             const std::function<void()>& completionCallback) override final;
 
-    HalResult<void> performComposedEffect(
+    HalResult<std::chrono::milliseconds> performComposedEffect(
             const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
             const std::function<void()>& completionCallback) override final;
 
@@ -252,6 +252,12 @@
             GUARDED_BY(mSupportedEffectsMutex);
     std::optional<std::vector<hardware::vibrator::CompositePrimitive>> mSupportedPrimitives
             GUARDED_BY(mSupportedPrimitivesMutex);
+    std::vector<std::optional<std::chrono::milliseconds>> mPrimitiveDurations
+            GUARDED_BY(mSupportedPrimitivesMutex);
+
+    // Loads and caches from IVibrator.
+    HalResult<std::chrono::milliseconds> getPrimitiveDuration(
+            hardware::vibrator::CompositePrimitive primitive);
 
     // Loads directly from IVibrator handle, skipping caches.
     HalResult<Capabilities> getCapabilitiesInternal();
@@ -287,7 +293,7 @@
     HalResult<std::vector<hardware::vibrator::CompositePrimitive>> getSupportedPrimitives()
             override final;
 
-    HalResult<void> performComposedEffect(
+    HalResult<std::chrono::milliseconds> performComposedEffect(
             const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
             const std::function<void()>& completionCallback) override final;
 
diff --git a/services/vibratorservice/test/Android.bp b/services/vibratorservice/test/Android.bp
index ad85990..3294724 100644
--- a/services/vibratorservice/test/Android.bp
+++ b/services/vibratorservice/test/Android.bp
@@ -12,6 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
 cc_test {
     name: "libvibratorservice_test",
     test_suites: ["device-tests"],
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
index 2d9d0d6..c4b39ed 100644
--- a/services/vibratorservice/test/VibratorHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -71,7 +71,7 @@
                 (Effect effect, EffectStrength strength,
                  const std::function<void()>& completionCallback),
                 (override));
-    MOCK_METHOD(vibrator::HalResult<void>, performComposedEffect,
+    MOCK_METHOD(vibrator::HalResult<milliseconds>, performComposedEffect,
                 (const std::vector<CompositeEffect>& primitiveEffects,
                  const std::function<void()>& completionCallback),
                 (override));
@@ -143,7 +143,7 @@
                 .WillRepeatedly(Return(durationResult));
         EXPECT_CALL(*mMockHal.get(), performComposedEffect(Eq(compositeEffects), _))
                 .Times(Exactly(cardinality))
-                .WillRepeatedly(Return(voidResult));
+                .WillRepeatedly(Return(durationResult));
 
         if (cardinality > 1) {
             // One reconnection call after each failure.
@@ -208,7 +208,10 @@
     ASSERT_TRUE(performEffectResult.isOk());
     ASSERT_EQ(100ms, performEffectResult.value());
 
-    ASSERT_TRUE(mController->performComposedEffect(compositeEffects, []() {}).isOk());
+    auto performComposedEffectResult =
+            mController->performComposedEffect(compositeEffects, []() {});
+    ASSERT_TRUE(performComposedEffectResult.isOk());
+    ASSERT_EQ(100ms, performComposedEffectResult.value());
 
     ASSERT_EQ(1, mConnectCounter);
 }
diff --git a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
index 96b76ba..8b5caa5 100644
--- a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
@@ -77,6 +77,8 @@
     MOCK_METHOD(Status, getSupportedAlwaysOnEffects, (std::vector<Effect> * ret), (override));
     MOCK_METHOD(Status, alwaysOnEnable, (int32_t id, Effect e, EffectStrength s), (override));
     MOCK_METHOD(Status, alwaysOnDisable, (int32_t id), (override));
+    MOCK_METHOD(Status, getQFactor, (float * ret), (override));
+    MOCK_METHOD(Status, getResonantFrequency, (float * ret), (override));
     MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
     MOCK_METHOD(std::string, getInterfaceHash, (), (override));
     MOCK_METHOD(IBinder*, onAsBinder, (), (override));
@@ -504,10 +506,21 @@
         EXPECT_CALL(*mMockHal.get(), compose(Eq(emptyEffects), _))
                 .Times(Exactly(1))
                 .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+
+        EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::CLICK), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(1), Return(Status())));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(singleEffect), _))
                 .Times(Exactly(1))
                 .WillRepeatedly(Return(
                         Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+
+        EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::SPIN), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(Status())));
+        EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::THUD), _))
+                .Times(Exactly(1))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(3), Return(Status())));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(multipleEffects), _))
                 .Times(Exactly(1))
                 .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
@@ -518,6 +531,7 @@
 
     auto result = mWrapper->performComposedEffect(emptyEffects, callback);
     ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(0ms, result.value());
     ASSERT_EQ(1, *callbackCounter.get());
 
     result = mWrapper->performComposedEffect(singleEffect, callback);
@@ -530,3 +544,40 @@
     // Callback not triggered on failure
     ASSERT_EQ(1, *callbackCounter.get());
 }
+
+TEST_F(VibratorHalWrapperAidlTest, TestPerformComposedCachesPrimitiveDurationsAndIgnoresFailures) {
+    std::vector<CompositeEffect> multipleEffects;
+    multipleEffects.push_back(
+            vibrator::TestFactory::createCompositeEffect(CompositePrimitive::SPIN, 10ms, 0.5f));
+    multipleEffects.push_back(
+            vibrator::TestFactory::createCompositeEffect(CompositePrimitive::THUD, 100ms, 1.0f));
+
+    EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::SPIN), _))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<1>(1), Return(Status())));
+    EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::THUD), _))
+            .Times(Exactly(2))
+            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+            .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(Status())));
+    EXPECT_CALL(*mMockHal.get(), compose(Eq(multipleEffects), _))
+            .Times(Exactly(3))
+            .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+
+    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+    auto result = mWrapper->performComposedEffect(multipleEffects, callback);
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(111ms, result.value()); // Failed primitive duration counted as 0.
+    ASSERT_EQ(1, *callbackCounter.get());
+
+    result = mWrapper->performComposedEffect(multipleEffects, callback);
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(113ms, result.value()); // Second fetch succeeds and returns primitive duration.
+    ASSERT_EQ(2, *callbackCounter.get());
+
+    result = mWrapper->performComposedEffect(multipleEffects, callback);
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(113ms, result.value()); // Cached durations not fetched again, same duration returned.
+    ASSERT_EQ(3, *callbackCounter.get());
+}
diff --git a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
index dd71a6a..bcfd15d 100644
--- a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
@@ -71,6 +71,8 @@
     MOCK_METHOD(Status, getSupportedAlwaysOnEffects, (std::vector<Effect> * ret), (override));
     MOCK_METHOD(Status, alwaysOnEnable, (int32_t id, Effect e, EffectStrength s), (override));
     MOCK_METHOD(Status, alwaysOnDisable, (int32_t id), (override));
+    MOCK_METHOD(Status, getQFactor, (float * ret), (override));
+    MOCK_METHOD(Status, getResonantFrequency, (float * ret), (override));
     MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
     MOCK_METHOD(std::string, getInterfaceHash, (), (override));
     MOCK_METHOD(IBinder*, onAsBinder, (), (override));
diff --git a/services/vr/bufferhubd/Android.bp b/services/vr/bufferhubd/Android.bp
index 8523bb2..f5491cf 100644
--- a/services/vr/bufferhubd/Android.bp
+++ b/services/vr/bufferhubd/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/services/vr/hardware_composer/aidl/Android.bp b/services/vr/hardware_composer/aidl/Android.bp
index 98afdec..fa71ed7 100644
--- a/services/vr/hardware_composer/aidl/Android.bp
+++ b/services/vr/hardware_composer/aidl/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/vulkan/libvulkan/api.cpp b/vulkan/libvulkan/api.cpp
index 2d4690a..d1cd397 100644
--- a/vulkan/libvulkan/api.cpp
+++ b/vulkan/libvulkan/api.cpp
@@ -33,6 +33,7 @@
 #include <unordered_set>
 #include <utility>
 
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <cutils/properties.h>
 #include <log/log.h>
@@ -134,7 +135,7 @@
         // If no layers specified via Settings, check legacy properties
         if (implicit_layers_.count <= 0) {
             ParseDebugVulkanLayers();
-            property_list(ParseDebugVulkanLayer, this);
+            ParseDebugVulkanLayer();
 
             // sort by priorities
             auto& arr = implicit_layers_;
@@ -181,30 +182,39 @@
             AddImplicitLayer(prio, p, strlen(p));
     }
 
-    static void ParseDebugVulkanLayer(const char* key,
-                                      const char* val,
-                                      void* user_data) {
+    void ParseDebugVulkanLayer() {
+        // Checks for consecutive debug.vulkan.layer.<priority> system
+        // properties after always checking an initial fixed range.
         static const char prefix[] = "debug.vulkan.layer.";
-        const size_t prefix_len = sizeof(prefix) - 1;
+        static constexpr int kFixedRangeBeginInclusive = 0;
+        static constexpr int kFixedRangeEndInclusive = 9;
 
-        if (strncmp(key, prefix, prefix_len) || val[0] == '\0')
-            return;
-        key += prefix_len;
+        bool logged = false;
 
-        // debug.vulkan.layer.<priority>
-        int priority = -1;
-        if (key[0] >= '0' && key[0] <= '9')
-            priority = atoi(key);
+        int priority = kFixedRangeBeginInclusive;
+        while (true) {
+            const std::string prop_key =
+                std::string(prefix) + std::to_string(priority);
+            const std::string prop_val =
+                android::base::GetProperty(prop_key, "");
 
-        if (priority < 0) {
-            ALOGW("Ignored implicit layer %s with invalid priority %s", val,
-                  key);
-            return;
+            if (!prop_val.empty()) {
+                if (!logged) {
+                    ALOGI(
+                        "Detected Vulkan layers configured with "
+                        "debug.vulkan.layer.<priority>. Checking for "
+                        "debug.vulkan.layer.<priority> in the range [%d, %d] "
+                        "followed by a consecutive scan.",
+                        kFixedRangeBeginInclusive, kFixedRangeEndInclusive);
+                    logged = true;
+                }
+                AddImplicitLayer(priority, prop_val.c_str(), prop_val.length());
+            } else if (priority >= kFixedRangeEndInclusive) {
+                return;
+            }
+
+            ++priority;
         }
-
-        OverrideLayerNames& override_layers =
-            *reinterpret_cast<OverrideLayerNames*>(user_data);
-        override_layers.AddImplicitLayer(priority, val, strlen(val));
     }
 
     void AddImplicitLayer(int priority, const char* name, size_t len) {
diff --git a/vulkan/vkjson/vkjson.cc b/vulkan/vkjson/vkjson.cc
index bfc240e..a513239 100644
--- a/vulkan/vkjson/vkjson.cc
+++ b/vulkan/vkjson/vkjson.cc
@@ -1196,10 +1196,10 @@
                                           std::string* errors) {
   *t = T();
   Json::Value object(Json::objectValue);
-  Json::Reader reader;
-  reader.parse(json, object, false);
-  if (!object) {
-    if (errors) errors->assign(reader.getFormatedErrorMessages());
+  Json::CharReaderBuilder builder;
+  builder["collectComments"] = false;
+  std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+  if (!reader->parse(json.data(), json.data() + json.size(), &object, errors)) {
     return false;
   }
   return AsValue(&object, t);