Merge "Fix callback drop"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 3fa5430..20bfe65 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -4,6 +4,7 @@
 [Builtin Hooks Options]
 # Only turn on clang-format check for the following subfolders.
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+               cmds/idlcli/
                include/input/
                libs/binder/fuzzer/
                libs/binder/ndk/
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..8173c89
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,63 @@
+{
+  "presubmit": [
+    {
+      "name": "SurfaceFlinger_test",
+      "options": [
+        {
+          "include-filter": "*CredentialsTest.*"
+        },
+        {
+          "include-filter": "*SurfaceFlingerStress.*"
+        },
+        {
+          "include-filter": "*SurfaceInterceptorTest.*"
+        },
+        {
+          "include-filter": "*LayerTransactionTest.*"
+        },
+        {
+          "include-filter": "*LayerTypeTransactionTest.*"
+        },
+        {
+          "include-filter": "*LayerUpdateTest.*"
+        },
+        {
+          "include-filter": "*GeometryLatchingTest.*"
+        },
+        {
+          "include-filter": "*CropLatchingTest.*"
+        },
+        {
+          "include-filter": "*ChildLayerTest.*"
+        },
+        {
+          "include-filter": "*ScreenCaptureTest.*"
+        },
+        {
+          "include-filter": "*ScreenCaptureChildOnlyTest.*"
+        },
+        {
+          "include-filter": "*DereferenceSurfaceControlTest.*"
+        },
+        {
+          "include-filter": "*BoundlessLayerTest.*"
+        },
+        {
+          "include-filter": "*MultiDisplayLayerBoundsTest.*"
+        },
+        {
+          "include-filter": "*InvalidHandleTest.*"
+        },
+        {
+          "include-filter": "*VirtualDisplayTest.*"
+        },
+        {
+          "include-filter": "*RelativeZTest.*"
+        }
+      ]
+    },
+    {
+      "name": "libsurfaceflinger_unittest"
+    }
+  ]
+}
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
index ccd74db..87ea520 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -81,8 +81,8 @@
 
 binder::Status DumpstateService::startBugreport(int32_t calling_uid,
                                                 const std::string& calling_package,
-                                                const android::base::unique_fd& bugreport_fd,
-                                                const android::base::unique_fd& screenshot_fd,
+                                                android::base::unique_fd bugreport_fd,
+                                                android::base::unique_fd screenshot_fd,
                                                 int bugreport_mode,
                                                 const sp<IDumpstateListener>& listener) {
     MYLOGI("startBugreport() with mode: %d\n", bugreport_mode);
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
index 27954ad..6dc0225 100644
--- a/cmds/dumpstate/DumpstateService.h
+++ b/cmds/dumpstate/DumpstateService.h
@@ -39,8 +39,8 @@
     status_t dump(int fd, const Vector<String16>& args) override;
 
     binder::Status startBugreport(int32_t calling_uid, const std::string& calling_package,
-                                  const android::base::unique_fd& bugreport_fd,
-                                  const android::base::unique_fd& screenshot_fd, int bugreport_mode,
+                                  android::base::unique_fd bugreport_fd,
+                                  android::base::unique_fd screenshot_fd, int bugreport_mode,
                                   const sp<IDumpstateListener>& listener) override;
 
     // No-op
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 61e22a4..9d6a7ff 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -933,6 +933,31 @@
     unlink(path.c_str());
 }
 
+static void DumpVisibleWindowViews() {
+    if (!ds.IsZipping()) {
+        MYLOGD("Not dumping visible views because it's not a zipped bugreport\n");
+        return;
+    }
+    DurationReporter duration_reporter("VISIBLE WINDOW VIEWS");
+    const std::string path = ds.bugreport_internal_dir_ + "/tmp_visible_window_views";
+    auto fd = android::base::unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(),
+                O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
+                S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)));
+    if (fd < 0) {
+        MYLOGE("Could not open %s to dump visible views.\n", path.c_str());
+        return;
+    }
+    RunCommandToFd(fd, "", {"cmd", "window", "dump-visible-window-views"},
+                   CommandOptions::WithTimeout(120).Build());
+    bool empty = 0 == lseek(fd, 0, SEEK_END);
+    if (!empty) {
+        ds.AddZipEntry("visible_windows.zip", path);
+    } else {
+        MYLOGW("Failed to dump visible windows\n");
+    }
+    unlink(path.c_str());
+}
+
 static void DumpIpTablesAsRoot() {
     RunCommand("IPTABLES", {"iptables", "-L", "-nvx"});
     RunCommand("IP6TABLES", {"ip6tables", "-L", "-nvx"});
@@ -1317,6 +1342,8 @@
 
     RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunCommand, "PROCRANK", {"procrank"}, AS_ROOT_20);
 
+    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(DumpVisibleWindowViews);
+
     DumpFile("VIRTUAL MEMORY STATS", "/proc/vmstat");
     DumpFile("VMALLOC INFO", "/proc/vmallocinfo");
     DumpFile("SLAB INFO", "/proc/slabinfo");
@@ -1518,11 +1545,7 @@
  * Returns RunStatus::USER_DENIED_CONSENT if user explicitly denied consent to sharing the bugreport
  * with the caller.
  */
-static Dumpstate::RunStatus DumpstateDefault() {
-    // Invoking the following dumpsys calls before DumpTraces() to try and
-    // keep the system stats as close to its initial state as possible.
-    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunDumpsysCritical);
-
+Dumpstate::RunStatus Dumpstate::DumpstateDefaultAfterCritical() {
     // Capture first logcat early on; useful to take a snapshot before dumpstate logs take over the
     // buffer.
     DoLogcat();
@@ -1607,6 +1630,7 @@
 // This method collects dumpsys for telephony debugging only
 static void DumpstateTelephonyOnly() {
     DurationReporter duration_reporter("DUMPSTATE");
+
     const CommandOptions DUMPSYS_COMPONENTS_OPTIONS = CommandOptions::WithTimeout(60).Build();
 
     DumpstateRadioCommon();
@@ -2033,12 +2057,12 @@
                                   ? StringPrintf("[fd:%d]", ds.options_->bugreport_fd.get())
                                   : ds.bugreport_internal_dir_.c_str();
     MYLOGD(
-        "Bugreport dir: %s\n"
-        "Base name: %s\n"
-        "Suffix: %s\n"
-        "Log path: %s\n"
-        "Temporary path: %s\n"
-        "Screenshot path: %s\n",
+        "Bugreport dir: [%s] "
+        "Base name: [%s] "
+        "Suffix: [%s] "
+        "Log path: [%s] "
+        "Temporary path: [%s] "
+        "Screenshot path: [%s]\n",
         destination.c_str(), ds.base_name_.c_str(), ds.name_.c_str(), ds.log_path_.c_str(),
         ds.tmp_path_.c_str(), ds.screenshot_path_.c_str());
 
@@ -2140,21 +2164,14 @@
 }
 
 static void LogDumpOptions(const Dumpstate::DumpOptions& options) {
-    MYLOGI("do_zip_file: %d\n", options.do_zip_file);
-    MYLOGI("do_add_date: %d\n", options.do_add_date);
-    MYLOGI("do_vibrate: %d\n", options.do_vibrate);
-    MYLOGI("use_socket: %d\n", options.use_socket);
-    MYLOGI("use_control_socket: %d\n", options.use_control_socket);
-    MYLOGI("do_fb: %d\n", options.do_fb);
-    MYLOGI("is_remote_mode: %d\n", options.is_remote_mode);
-    MYLOGI("show_header_only: %d\n", options.show_header_only);
-    MYLOGI("do_start_service: %d\n", options.do_start_service);
-    MYLOGI("telephony_only: %d\n", options.telephony_only);
-    MYLOGI("wifi_only: %d\n", options.wifi_only);
-    MYLOGI("do_progress_updates: %d\n", options.do_progress_updates);
-    MYLOGI("fd: %d\n", options.bugreport_fd.get());
-    MYLOGI("bugreport_mode: %s\n", options.bugreport_mode.c_str());
-    MYLOGI("args: %s\n", options.args.c_str());
+    MYLOGI(
+        "do_zip_file: %d do_vibrate: %d use_socket: %d use_control_socket: %d do_fb: %d "
+        "is_remote_mode: %d show_header_only: %d do_start_service: %d telephony_only: %d "
+        "wifi_only: %d do_progress_updates: %d fd: %d bugreport_mode: %s args: %s\n",
+        options.do_zip_file, options.do_vibrate, options.use_socket, options.use_control_socket,
+        options.do_fb, options.is_remote_mode, options.show_header_only, options.do_start_service,
+        options.telephony_only, options.wifi_only, options.do_progress_updates,
+        options.bugreport_fd.get(), options.bugreport_mode.c_str(), options.args.c_str());
 }
 
 void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode,
@@ -2326,11 +2343,6 @@
 
     MYLOGD("dumpstate calling_uid = %d ; calling package = %s \n",
             calling_uid, calling_package.c_str());
-    if (CalledByApi()) {
-        // If the output needs to be copied over to the caller's fd, get user consent.
-        android::String16 package(calling_package.c_str());
-        CheckUserConsent(calling_uid, package);
-    }
 
     // Redirect output if needed
     bool is_redirecting = options_->OutputToFile();
@@ -2347,8 +2359,6 @@
     id_ = ++last_id;
     android::base::SetProperty(PROPERTY_LAST_ID, std::to_string(last_id));
 
-    MYLOGI("begin\n");
-
     if (acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME) < 0) {
         MYLOGE("Failed to acquire wake lock: %s\n", strerror(errno));
     } else {
@@ -2371,10 +2381,8 @@
         MYLOGI("Running on dry-run mode (to disable it, call 'setprop dumpstate.dry_run false')\n");
     }
 
-    MYLOGI("dumpstate info: id=%d, args='%s', bugreport_mode= %s)\n", id_, options_->args.c_str(),
-           options_->bugreport_mode.c_str());
-
-    MYLOGI("bugreport format version: %s\n", version_.c_str());
+    MYLOGI("dumpstate info: id=%d, args='%s', bugreport_mode= %s bugreport format version: %s\n",
+           id_, options_->args.c_str(), options_->bugreport_mode.c_str(), version_.c_str());
 
     do_early_screenshot_ = options_->do_progress_updates;
 
@@ -2473,13 +2481,23 @@
     PrintHeader();
 
     if (options_->telephony_only) {
+        MaybeCheckUserConsent(calling_uid, calling_package);
         DumpstateTelephonyOnly();
         DumpstateBoard();
     } else if (options_->wifi_only) {
+        MaybeCheckUserConsent(calling_uid, calling_package);
         DumpstateWifiOnly();
     } else {
+        // Invoking the critical dumpsys calls before DumpTraces() to try and
+        // keep the system stats as close to its initial state as possible.
+        RunDumpsysCritical();
+
+        // Run consent check only after critical dumpsys has finished -- so the consent
+        // isn't going to pollute the system state / logs.
+        MaybeCheckUserConsent(calling_uid, calling_package);
+
         // Dump state for the default case. This also drops root.
-        RunStatus s = DumpstateDefault();
+        RunStatus s = DumpstateDefaultAfterCritical();
         if (s != RunStatus::OK) {
             if (s == RunStatus::USER_CONSENT_DENIED) {
                 HandleUserConsentDenied();
@@ -2564,17 +2582,20 @@
                : RunStatus::OK;
 }
 
-void Dumpstate::CheckUserConsent(int32_t calling_uid, const android::String16& calling_package) {
-    if (calling_uid == AID_SHELL) {
+void Dumpstate::MaybeCheckUserConsent(int32_t calling_uid, const std::string& calling_package) {
+    if (calling_uid == AID_SHELL || !CalledByApi()) {
+        // No need to get consent for shell triggered dumpstates, or not through
+        // bugreporting API (i.e. no fd to copy back).
         return;
     }
     consent_callback_ = new ConsentCallback();
     const String16 incidentcompanion("incidentcompanion");
     sp<android::IBinder> ics(defaultServiceManager()->getService(incidentcompanion));
+    android::String16 package(calling_package.c_str());
     if (ics != nullptr) {
         MYLOGD("Checking user consent via incidentcompanion service\n");
         android::interface_cast<android::os::IIncidentCompanion>(ics)->authorizeReport(
-            calling_uid, calling_package, String16(), String16(),
+            calling_uid, package, String16(), String16(),
             0x1 /* FLAG_CONFIRMATION_DIALOG */, consent_callback_.get());
     } else {
         MYLOGD("Unable to check user consent; incidentcompanion service unavailable\n");
@@ -3471,8 +3492,8 @@
     }
 
     if (listener_ != nullptr) {
-        if (percent % 5 == 0) {
-            // We don't want to spam logcat, so only log multiples of 5.
+        if (percent % 10 == 0) {
+            // We don't want to spam logcat, so only log multiples of 10.
             MYLOGD("Setting progress: %d/%d (%d%%)\n", progress, max, percent);
         } else {
             // stderr is ignored on normal invocations, but useful when calling
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 831574d..7d9b113 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -486,7 +486,9 @@
   private:
     RunStatus RunInternal(int32_t calling_uid, const std::string& calling_package);
 
-    void CheckUserConsent(int32_t calling_uid, const android::String16& calling_package);
+    RunStatus DumpstateDefaultAfterCritical();
+
+    void MaybeCheckUserConsent(int32_t calling_uid, const std::string& calling_package);
 
     // Removes the in progress files output files (tmp file, zip/txt file, screenshot),
     // but leaves the log file alone.
diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
index f1884f8..dac90d9 100644
--- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
@@ -444,7 +444,7 @@
 
     sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout))));
     android::binder::Status status =
-        ds_binder->startBugreport(123, "com.dummy.package", bugreport_fd, screenshot_fd,
+        ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd),
                                   Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener);
     // startBugreport is an async call. Verify binder call succeeded first, then wait till listener
     // gets expected callbacks.
@@ -480,7 +480,7 @@
     // Call startBugreport with bad arguments.
     sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout))));
     android::binder::Status status =
-        ds_binder->startBugreport(123, "com.dummy.package", bugreport_fd, screenshot_fd,
+        ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd),
                                   2000,  // invalid bugreport mode
                                   listener);
     EXPECT_EQ(listener->getErrorCode(), IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT);
@@ -501,20 +501,24 @@
 
     // Prepare arguments
     unique_fd bugreport_fd(OpenForWrite("/data/local/tmp/tmp.zip"));
+    unique_fd bugreport_fd2(dup(bugreport_fd.get()));
     unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png"));
+    unique_fd screenshot_fd2(dup(screenshot_fd.get()));
 
     EXPECT_NE(bugreport_fd.get(), -1);
+    EXPECT_NE(bugreport_fd2.get(), -1);
     EXPECT_NE(screenshot_fd.get(), -1);
+    EXPECT_NE(screenshot_fd2.get(), -1);
 
     sp<DumpstateListener> listener1(new DumpstateListener(dup(fileno(stdout))));
     android::binder::Status status =
-        ds_binder->startBugreport(123, "com.dummy.package", bugreport_fd, screenshot_fd,
+        ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd),
                                   Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener1);
     EXPECT_TRUE(status.isOk());
 
     // try to make another call to startBugreport. This should fail.
     sp<DumpstateListener> listener2(new DumpstateListener(dup(fileno(stdout))));
-    status = ds_binder->startBugreport(123, "com.dummy.package", bugreport_fd, screenshot_fd,
+    status = ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd2), std::move(screenshot_fd2),
                                        Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener2);
     EXPECT_FALSE(status.isOk());
     WaitTillExecutionComplete(listener2.get());
diff --git a/cmds/dumpsys/dumpsys.cpp b/cmds/dumpsys/dumpsys.cpp
index abdf168..5597bcd 100644
--- a/cmds/dumpsys/dumpsys.cpp
+++ b/cmds/dumpsys/dumpsys.cpp
@@ -364,7 +364,7 @@
         }
 
         if (err != OK) {
-            aerr << "Error dumping service info status_t: (" << err << ") "
+            aerr << "Error dumping service info status_t: " << statusToString(err) << " "
                  << serviceName << endl;
         }
     });
diff --git a/cmds/idlcli/Android.bp b/cmds/idlcli/Android.bp
index bb92fd3..5476319 100644
--- a/cmds/idlcli/Android.bp
+++ b/cmds/idlcli/Android.bp
@@ -36,7 +36,10 @@
     defaults: ["idlcli-defaults"],
     srcs: [
         "CommandVibrator.cpp",
+        "vibrator/CommandCompose.cpp",
         "vibrator/CommandGetCapabilities.cpp",
+        "vibrator/CommandGetCompositionDelayMax.cpp",
+        "vibrator/CommandGetCompositionSizeMax.cpp",
         "vibrator/CommandOff.cpp",
         "vibrator/CommandOn.cpp",
         "vibrator/CommandPerform.cpp",
diff --git a/cmds/idlcli/vibrator/CommandCompose.cpp b/cmds/idlcli/vibrator/CommandCompose.cpp
new file mode 100644
index 0000000..705e40b
--- /dev/null
+++ b/cmds/idlcli/vibrator/CommandCompose.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils.h"
+#include "vibrator.h"
+
+namespace android {
+namespace idlcli {
+
+class CommandVibrator;
+
+namespace vibrator {
+
+using aidl::CompositeEffect;
+
+class CommandCompose : public Command {
+    std::string getDescription() const override { return "Compose vibration."; }
+
+    std::string getUsageSummary() const override { return "<delay> <primitive> <scale> ..."; }
+
+    UsageDetails getUsageDetails() const override {
+        UsageDetails details{
+                {"<delay>", {"In milliseconds"}},
+                {"<primitive>", {"Primitive ID."}},
+                {"<scale>", {"0.0 (exclusive) - 1.0 (inclusive)."}},
+                {"...", {"May repeat multiple times."}},
+        };
+        return details;
+    }
+
+    Status doArgs(Args &args) override {
+        while (!args.empty()) {
+            CompositeEffect effect;
+            if (auto delay = args.pop<decltype(effect.delayMs)>()) {
+                effect.delayMs = *delay;
+                std::cout << "Delay: " << effect.delayMs << std::endl;
+            } else {
+                std::cerr << "Missing or Invalid Delay!" << std::endl;
+                return USAGE;
+            }
+            // TODO: Use range validation when supported by AIDL
+            if (auto primitive = args.pop<std::underlying_type_t<decltype(effect.primitive)>>()) {
+                effect.primitive = static_cast<decltype(effect.primitive)>(*primitive);
+                std::cout << "Primitive: " << toString(effect.primitive) << std::endl;
+            } else {
+                std::cerr << "Missing or Invalid Primitive!" << std::endl;
+                return USAGE;
+            }
+            if (auto scale = args.pop<decltype(effect.scale)>();
+                scale && *scale > 0.0 && scale <= 1.0) {
+                effect.scale = *scale;
+                std::cout << "Scale: " << effect.scale << std::endl;
+            } else {
+                std::cerr << "Missing or Invalid Scale!" << std::endl;
+                return USAGE;
+            }
+            mComposite.emplace_back(std::move(effect));
+        }
+        if (!args.empty()) {
+            std::cerr << "Unexpected Arguments!" << std::endl;
+            return USAGE;
+        }
+        return OK;
+    }
+
+    Status doMain(Args && /*args*/) override {
+        std::string statusStr;
+        Status ret;
+        if (auto hal = getHal<aidl::IVibrator>()) {
+            auto status = hal->call(&aidl::IVibrator::compose, mComposite, nullptr);
+            statusStr = status.toString8();
+            ret = status.isOk() ? OK : ERROR;
+        } else {
+            return UNAVAILABLE;
+        }
+
+        std::cout << "Status: " << statusStr << std::endl;
+
+        return ret;
+    }
+
+    std::vector<CompositeEffect> mComposite;
+};
+
+static const auto Command = CommandRegistry<CommandVibrator>::Register<CommandCompose>("compose");
+
+} // namespace vibrator
+} // namespace idlcli
+} // namespace android
diff --git a/cmds/idlcli/vibrator/CommandGetCompositionDelayMax.cpp b/cmds/idlcli/vibrator/CommandGetCompositionDelayMax.cpp
new file mode 100644
index 0000000..b414307
--- /dev/null
+++ b/cmds/idlcli/vibrator/CommandGetCompositionDelayMax.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils.h"
+#include "vibrator.h"
+
+namespace android {
+namespace idlcli {
+
+class CommandVibrator;
+
+namespace vibrator {
+
+class CommandGetCompositionDelayMax : public Command {
+    std::string getDescription() const override {
+        return "Retrieves vibrator composition delay max.";
+    }
+
+    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;
+        int32_t maxDelayMs;
+        Status ret;
+
+        if (auto hal = getHal<aidl::IVibrator>()) {
+            auto status = hal->call(&aidl::IVibrator::getCompositionDelayMax, &maxDelayMs);
+            statusStr = status.toString8();
+            ret = status.isOk() ? OK : ERROR;
+        } else {
+            return UNAVAILABLE;
+        }
+
+        std::cout << "Status: " << statusStr << std::endl;
+        std::cout << "Max Delay: " << maxDelayMs << " ms" << std::endl;
+
+        return ret;
+    }
+};
+
+static const auto Command =
+        CommandRegistry<CommandVibrator>::Register<CommandGetCompositionDelayMax>(
+                "getCompositionDelayMax");
+
+} // namespace vibrator
+} // namespace idlcli
+} // namespace android
diff --git a/cmds/idlcli/vibrator/CommandGetCompositionSizeMax.cpp b/cmds/idlcli/vibrator/CommandGetCompositionSizeMax.cpp
new file mode 100644
index 0000000..360fc9d
--- /dev/null
+++ b/cmds/idlcli/vibrator/CommandGetCompositionSizeMax.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils.h"
+#include "vibrator.h"
+
+namespace android {
+namespace idlcli {
+
+class CommandVibrator;
+
+namespace vibrator {
+
+class CommandGetCompositionSizeMax : public Command {
+    std::string getDescription() const override {
+        return "Retrieves vibrator composition size max.";
+    }
+
+    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;
+        int32_t maxSize;
+        Status ret;
+
+        if (auto hal = getHal<aidl::IVibrator>()) {
+            auto status = hal->call(&aidl::IVibrator::getCompositionSizeMax, &maxSize);
+            statusStr = status.toString8();
+            ret = status.isOk() ? OK : ERROR;
+        } else {
+            return UNAVAILABLE;
+        }
+
+        std::cout << "Status: " << statusStr << std::endl;
+        std::cout << "Max Size: " << maxSize << std::endl;
+
+        return ret;
+    }
+};
+
+static const auto Command =
+        CommandRegistry<CommandVibrator>::Register<CommandGetCompositionSizeMax>(
+                "getCompositionSizeMax");
+
+} // namespace vibrator
+} // namespace idlcli
+} // namespace android
diff --git a/cmds/idlcli/vibrator/CommandSetAmplitude.cpp b/cmds/idlcli/vibrator/CommandSetAmplitude.cpp
index 6e2261f..33d7eed 100644
--- a/cmds/idlcli/vibrator/CommandSetAmplitude.cpp
+++ b/cmds/idlcli/vibrator/CommandSetAmplitude.cpp
@@ -54,7 +54,8 @@
         Status ret;
 
         if (auto hal = getHal<aidl::IVibrator>()) {
-            auto status = hal->call(&aidl::IVibrator::setAmplitude, mAmplitude);
+            auto status = hal->call(&aidl::IVibrator::setAmplitude,
+                                    static_cast<float>(mAmplitude) / UINT8_MAX);
             statusStr = status.toString8();
             ret = status.isOk() ? OK : ERROR;
         } else if (auto hal = getHal<V1_0::IVibrator>()) {
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index 4026f29..8c8d94d 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -92,15 +92,12 @@
 static constexpr const char* CACHE_DIR_POSTFIX = "/cache";
 static constexpr const char* CODE_CACHE_DIR_POSTFIX = "/code_cache";
 
-static constexpr const char *kIdMapPath = "/system/bin/idmap";
-static constexpr const char* IDMAP_PREFIX = "/data/resource-cache/";
-static constexpr const char* IDMAP_SUFFIX = "@idmap";
-
 // fsverity assumes the page size is always 4096. If not, the feature can not be
 // enabled.
 static constexpr int kVerityPageSize = 4096;
 static constexpr size_t kSha256Size = 32;
 static constexpr const char* kPropApkVerityMode = "ro.apk_verity.mode";
+static constexpr const char* kFuseProp = "persist.sys.fuse";
 
 namespace {
 
@@ -592,12 +589,21 @@
         std::lock_guard<std::recursive_mutex> lock(mMountsLock);
         for (const auto& n : mStorageMounts) {
             auto extPath = n.second;
-            if (n.first.compare(0, 14, "/mnt/media_rw/") != 0) {
-                extPath += StringPrintf("/%d", userId);
-            } else if (userId != 0) {
-                // TODO: support devices mounted under secondary users
-                continue;
+
+            if (android::base::GetBoolProperty(kFuseProp, false)) {
+                std::regex re("^\\/mnt\\/pass_through\\/[0-9]+\\/emulated");
+                if (std::regex_match(extPath, re)) {
+                    extPath += "/" + std::to_string(userId);
+                }
+            } else {
+                if (n.first.compare(0, 14, "/mnt/media_rw/") != 0) {
+                    extPath += StringPrintf("/%d", userId);
+                } else if (userId != 0) {
+                    // TODO: support devices mounted under secondary users
+                    continue;
+                }
             }
+
             if (flags & FLAG_CLEAR_CACHE_ONLY) {
                 // Clear only cached data from shared storage
                 auto path = StringPrintf("%s/Android/data/%s/cache", extPath.c_str(), pkgname);
@@ -688,16 +694,26 @@
         std::lock_guard<std::recursive_mutex> lock(mMountsLock);
         for (const auto& n : mStorageMounts) {
             auto extPath = n.second;
-            if (n.first.compare(0, 14, "/mnt/media_rw/") != 0) {
-                extPath += StringPrintf("/%d", userId);
-            } else if (userId != 0) {
-                // TODO: support devices mounted under secondary users
-                continue;
+
+            if (android::base::GetBoolProperty(kFuseProp, false)) {
+                std::regex re("^\\/mnt\\/pass_through\\/[0-9]+\\/emulated");
+                if (std::regex_match(extPath, re)) {
+                    extPath += "/" + std::to_string(userId);
+                }
+            } else {
+                if (n.first.compare(0, 14, "/mnt/media_rw/") != 0) {
+                    extPath += StringPrintf("/%d", userId);
+                } else if (userId != 0) {
+                    // TODO: support devices mounted under secondary users
+                    continue;
+                }
             }
+
             auto path = StringPrintf("%s/Android/data/%s", extPath.c_str(), pkgname);
             if (delete_dir_contents_and_dir(path, true) != 0) {
                 res = error("Failed to delete contents of " + path);
             }
+
             path = StringPrintf("%s/Android/media/%s", extPath.c_str(), pkgname);
             if (delete_dir_contents_and_dir(path, true) != 0) {
                 res = error("Failed to delete contents of " + path);
@@ -2253,206 +2269,6 @@
     return res;
 }
 
-static void run_idmap(const char *target_apk, const char *overlay_apk, int idmap_fd)
-{
-    execl(kIdMapPath, kIdMapPath, "--fd", target_apk, overlay_apk,
-            StringPrintf("%d", idmap_fd).c_str(), (char*)nullptr);
-    PLOG(ERROR) << "execl (" << kIdMapPath << ") failed";
-}
-
-static void run_verify_idmap(const char *target_apk, const char *overlay_apk, int idmap_fd)
-{
-    execl(kIdMapPath, kIdMapPath, "--verify", target_apk, overlay_apk,
-            StringPrintf("%d", idmap_fd).c_str(), (char*)nullptr);
-    PLOG(ERROR) << "execl (" << kIdMapPath << ") failed";
-}
-
-static bool delete_stale_idmap(const char* target_apk, const char* overlay_apk,
-        const char* idmap_path, int32_t uid) {
-    int idmap_fd = open(idmap_path, O_RDWR);
-    if (idmap_fd < 0) {
-        PLOG(ERROR) << "idmap open failed: " << idmap_path;
-        unlink(idmap_path);
-        return true;
-    }
-
-    pid_t pid;
-    pid = fork();
-    if (pid == 0) {
-        /* child -- drop privileges before continuing */
-        if (setgid(uid) != 0) {
-            LOG(ERROR) << "setgid(" << uid << ") failed during idmap";
-            exit(1);
-        }
-        if (setuid(uid) != 0) {
-            LOG(ERROR) << "setuid(" << uid << ") failed during idmap";
-            exit(1);
-        }
-        if (flock(idmap_fd, LOCK_EX | LOCK_NB) != 0) {
-            PLOG(ERROR) << "flock(" << idmap_path << ") failed during idmap";
-            exit(1);
-        }
-
-        run_verify_idmap(target_apk, overlay_apk, idmap_fd);
-        exit(1); /* only if exec call to deleting stale idmap failed */
-    } else {
-        int status = wait_child(pid);
-        close(idmap_fd);
-
-        if (status != 0) {
-            // Failed on verifying if idmap is made from target_apk and overlay_apk.
-            LOG(DEBUG) << "delete stale idmap: " << idmap_path;
-            unlink(idmap_path);
-            return true;
-        }
-    }
-    return false;
-}
-
-// Transform string /a/b/c.apk to (prefix)/a@b@c.apk@(suffix)
-// eg /a/b/c.apk to /data/resource-cache/a@b@c.apk@idmap
-static int flatten_path(const char *prefix, const char *suffix,
-        const char *overlay_path, char *idmap_path, size_t N)
-{
-    if (overlay_path == nullptr || idmap_path == nullptr) {
-        return -1;
-    }
-    const size_t len_overlay_path = strlen(overlay_path);
-    // will access overlay_path + 1 further below; requires absolute path
-    if (len_overlay_path < 2 || *overlay_path != '/') {
-        return -1;
-    }
-    const size_t len_idmap_root = strlen(prefix);
-    const size_t len_suffix = strlen(suffix);
-    if (SIZE_MAX - len_idmap_root < len_overlay_path ||
-            SIZE_MAX - (len_idmap_root + len_overlay_path) < len_suffix) {
-        // additions below would cause overflow
-        return -1;
-    }
-    if (N < len_idmap_root + len_overlay_path + len_suffix) {
-        return -1;
-    }
-    memset(idmap_path, 0, N);
-    snprintf(idmap_path, N, "%s%s%s", prefix, overlay_path + 1, suffix);
-    char *ch = idmap_path + len_idmap_root;
-    while (*ch != '\0') {
-        if (*ch == '/') {
-            *ch = '@';
-        }
-        ++ch;
-    }
-    return 0;
-}
-
-binder::Status InstalldNativeService::idmap(const std::string& targetApkPath,
-        const std::string& overlayApkPath, int32_t uid) {
-    ENFORCE_UID(AID_SYSTEM);
-    CHECK_ARGUMENT_PATH(targetApkPath);
-    CHECK_ARGUMENT_PATH(overlayApkPath);
-    std::lock_guard<std::recursive_mutex> lock(mLock);
-
-    const char* target_apk = targetApkPath.c_str();
-    const char* overlay_apk = overlayApkPath.c_str();
-    ALOGV("idmap target_apk=%s overlay_apk=%s uid=%d\n", target_apk, overlay_apk, uid);
-
-    int idmap_fd = -1;
-    char idmap_path[PATH_MAX];
-    struct stat idmap_stat;
-    bool outdated = false;
-
-    if (flatten_path(IDMAP_PREFIX, IDMAP_SUFFIX, overlay_apk,
-                idmap_path, sizeof(idmap_path)) == -1) {
-        ALOGE("idmap cannot generate idmap path for overlay %s\n", overlay_apk);
-        goto fail;
-    }
-
-    if (stat(idmap_path, &idmap_stat) < 0) {
-        outdated = true;
-    } else {
-        outdated = delete_stale_idmap(target_apk, overlay_apk, idmap_path, uid);
-    }
-
-    if (outdated) {
-        idmap_fd = open(idmap_path, O_RDWR | O_CREAT | O_EXCL, 0644);
-    } else {
-        idmap_fd = open(idmap_path, O_RDWR);
-    }
-
-    if (idmap_fd < 0) {
-        ALOGE("idmap cannot open '%s' for output: %s\n", idmap_path, strerror(errno));
-        goto fail;
-    }
-    if (fchown(idmap_fd, AID_SYSTEM, uid) < 0) {
-        ALOGE("idmap cannot chown '%s'\n", idmap_path);
-        goto fail;
-    }
-    if (fchmod(idmap_fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) {
-        ALOGE("idmap cannot chmod '%s'\n", idmap_path);
-        goto fail;
-    }
-
-    if (!outdated) {
-        close(idmap_fd);
-        return ok();
-    }
-
-    pid_t pid;
-    pid = fork();
-    if (pid == 0) {
-        /* child -- drop privileges before continuing */
-        if (setgid(uid) != 0) {
-            ALOGE("setgid(%d) failed during idmap\n", uid);
-            exit(1);
-        }
-        if (setuid(uid) != 0) {
-            ALOGE("setuid(%d) failed during idmap\n", uid);
-            exit(1);
-        }
-        if (flock(idmap_fd, LOCK_EX | LOCK_NB) != 0) {
-            ALOGE("flock(%s) failed during idmap: %s\n", idmap_path, strerror(errno));
-            exit(1);
-        }
-
-        run_idmap(target_apk, overlay_apk, idmap_fd);
-        exit(1); /* only if exec call to idmap failed */
-    } else {
-        int status = wait_child(pid);
-        if (status != 0) {
-            ALOGE("idmap failed, status=0x%04x\n", status);
-            goto fail;
-        }
-    }
-
-    close(idmap_fd);
-    return ok();
-fail:
-    if (idmap_fd >= 0) {
-        close(idmap_fd);
-        unlink(idmap_path);
-    }
-    return error();
-}
-
-binder::Status InstalldNativeService::removeIdmap(const std::string& overlayApkPath) {
-    ENFORCE_UID(AID_SYSTEM);
-    CHECK_ARGUMENT_PATH(overlayApkPath);
-    std::lock_guard<std::recursive_mutex> lock(mLock);
-
-    const char* overlay_apk = overlayApkPath.c_str();
-    char idmap_path[PATH_MAX];
-
-    if (flatten_path(IDMAP_PREFIX, IDMAP_SUFFIX, overlay_apk,
-                idmap_path, sizeof(idmap_path)) == -1) {
-        ALOGE("idmap cannot generate idmap path for overlay %s\n", overlay_apk);
-        return error();
-    }
-    if (unlink(idmap_path) < 0) {
-        ALOGE("couldn't unlink idmap file %s\n", idmap_path);
-        return error();
-    }
-    return ok();
-}
-
 binder::Status InstalldNativeService::restoreconAppData(const std::unique_ptr<std::string>& uuid,
         const std::string& packageName, int32_t userId, int32_t flags, int32_t appId,
         const std::string& seInfo) {
@@ -2605,7 +2421,7 @@
 #endif
 
 binder::Status InstalldNativeService::installApkVerity(const std::string& filePath,
-        const ::android::base::unique_fd& verityInputAshmem, int32_t contentSize) {
+        android::base::unique_fd verityInputAshmem, int32_t contentSize) {
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_PATH(filePath);
     std::lock_guard<std::recursive_mutex> lock(mLock);
@@ -2778,12 +2594,21 @@
         std::getline(in, target, ' ');
         std::getline(in, ignored);
 
+        if (android::base::GetBoolProperty(kFuseProp, false)) {
+            // TODO(b/146139106): Use sdcardfs mounts on devices running sdcardfs so we don't bypass
+            // it's VFS cache
+            if (target.compare(0, 17, "/mnt/pass_through") == 0) {
+                LOG(DEBUG) << "Found storage mount " << source << " at " << target;
+                mStorageMounts[source] = target;
+            }
+        } else {
 #if !BYPASS_SDCARDFS
-        if (target.compare(0, 21, "/mnt/runtime/default/") == 0) {
-            LOG(DEBUG) << "Found storage mount " << source << " at " << target;
-            mStorageMounts[source] = target;
-        }
+            if (target.compare(0, 21, "/mnt/runtime/default/") == 0) {
+                LOG(DEBUG) << "Found storage mount " << source << " at " << target;
+                mStorageMounts[source] = target;
+            }
 #endif
+        }
     }
     return ok();
 }
@@ -2793,6 +2618,17 @@
     std::lock_guard<std::recursive_mutex> lock(mMountsLock);
     const char* uuid_ = uuid ? uuid->c_str() : nullptr;
     auto path = StringPrintf("%s/media", create_data_path(uuid_).c_str());
+    if (android::base::GetBoolProperty(kFuseProp, false)) {
+        // TODO(b/146139106): This is only safe on devices not running sdcardfs where there is no
+        // risk of bypassing the sdcardfs VFS cache
+
+        // Always use the lower filesystem path on FUSE enabled devices not running sdcardfs
+        // The upper filesystem path, /mnt/pass_through/<userid>/<vol>/ which was a bind mount
+        // to the lower filesytem may have been unmounted already when a user is
+        // removed and the path will now be pointing to a tmpfs without content
+        return StringPrintf("%s/%u", path.c_str(), userid);
+    }
+
     auto resolved = mStorageMounts[path];
     if (resolved.empty()) {
         LOG(WARNING) << "Failed to find storage mount for " << path;
diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h
index 2b7bf33..fb02730 100644
--- a/cmds/installd/InstalldNativeService.h
+++ b/cmds/installd/InstalldNativeService.h
@@ -119,9 +119,6 @@
     binder::Status destroyProfileSnapshot(const std::string& packageName,
             const std::string& profileName);
 
-    binder::Status idmap(const std::string& targetApkPath, const std::string& overlayApkPath,
-            int32_t uid);
-    binder::Status removeIdmap(const std::string& overlayApkPath);
     binder::Status rmPackageDir(const std::string& packageDir);
     binder::Status markBootComplete(const std::string& instructionSet);
     binder::Status freeCache(const std::unique_ptr<std::string>& uuid, int64_t targetFreeBytes,
@@ -136,7 +133,7 @@
     binder::Status deleteOdex(const std::string& apkPath, const std::string& instructionSet,
             const std::unique_ptr<std::string>& outputPath);
     binder::Status installApkVerity(const std::string& filePath,
-            const ::android::base::unique_fd& verityInput, int32_t contentSize);
+            android::base::unique_fd verityInput, int32_t contentSize);
     binder::Status assertFsverityRootHashMatches(const std::string& filePath,
             const std::vector<uint8_t>& expectedHash);
     binder::Status reconcileSecondaryDexFile(const std::string& dexPath,
diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl
index d99bcc8..6cc4bde 100644
--- a/cmds/installd/binder/android/os/IInstalld.aidl
+++ b/cmds/installd/binder/android/os/IInstalld.aidl
@@ -72,8 +72,6 @@
             @utf8InCpp String profileName, @utf8InCpp String classpath);
     void destroyProfileSnapshot(@utf8InCpp String packageName, @utf8InCpp String profileName);
 
-    void idmap(@utf8InCpp String targetApkPath, @utf8InCpp String overlayApkPath, int uid);
-    void removeIdmap(@utf8InCpp String overlayApkPath);
     void rmPackageDir(@utf8InCpp String packageDir);
     void markBootComplete(@utf8InCpp String instructionSet);
     void freeCache(@nullable @utf8InCpp String uuid, long targetFreeBytes,
diff --git a/include/android/input.h b/include/android/input.h
index ce439c6..f51cd79 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -158,7 +158,10 @@
     AINPUT_EVENT_TYPE_KEY = 1,
 
     /** Indicates that the input event is a motion event. */
-    AINPUT_EVENT_TYPE_MOTION = 2
+    AINPUT_EVENT_TYPE_MOTION = 2,
+
+    /** Focus event */
+    AINPUT_EVENT_TYPE_FOCUS = 3,
 };
 
 /**
diff --git a/include/binder b/include/binder
deleted file mode 120000
index 35a022a..0000000
--- a/include/binder
+++ /dev/null
@@ -1 +0,0 @@
-../libs/binder/include/binder/
\ No newline at end of file
diff --git a/include/binder/Binder.h b/include/binder/Binder.h
new file mode 120000
index 0000000..0fc6db7
--- /dev/null
+++ b/include/binder/Binder.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/Binder.h
\ No newline at end of file
diff --git a/include/binder/BinderService.h b/include/binder/BinderService.h
new file mode 120000
index 0000000..370b260
--- /dev/null
+++ b/include/binder/BinderService.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/BinderService.h
\ No newline at end of file
diff --git a/include/binder/IBinder.h b/include/binder/IBinder.h
new file mode 120000
index 0000000..93a6219
--- /dev/null
+++ b/include/binder/IBinder.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/IBinder.h
\ No newline at end of file
diff --git a/include/binder/IInterface.h b/include/binder/IInterface.h
new file mode 120000
index 0000000..8579878
--- /dev/null
+++ b/include/binder/IInterface.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/IInterface.h
\ No newline at end of file
diff --git a/include/binder/IMemory.h b/include/binder/IMemory.h
new file mode 120000
index 0000000..5171c08
--- /dev/null
+++ b/include/binder/IMemory.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/IMemory.h
\ No newline at end of file
diff --git a/include/binder/IPCThreadState.h b/include/binder/IPCThreadState.h
new file mode 120000
index 0000000..ecd4f81
--- /dev/null
+++ b/include/binder/IPCThreadState.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/IPCThreadState.h
\ No newline at end of file
diff --git a/include/binder/IServiceManager.h b/include/binder/IServiceManager.h
new file mode 120000
index 0000000..33d18cc
--- /dev/null
+++ b/include/binder/IServiceManager.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/IServiceManager.h
\ No newline at end of file
diff --git a/include/binder/MemoryDealer.h b/include/binder/MemoryDealer.h
new file mode 120000
index 0000000..71881fb
--- /dev/null
+++ b/include/binder/MemoryDealer.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/MemoryDealer.h
\ No newline at end of file
diff --git a/include/binder/MemoryHeapBase.h b/include/binder/MemoryHeapBase.h
new file mode 120000
index 0000000..8fb51cc
--- /dev/null
+++ b/include/binder/MemoryHeapBase.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/MemoryHeapBase.h
\ No newline at end of file
diff --git a/include/binder/Parcel.h b/include/binder/Parcel.h
new file mode 120000
index 0000000..23492be
--- /dev/null
+++ b/include/binder/Parcel.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/Parcel.h
\ No newline at end of file
diff --git a/include/binder/ParcelFileDescriptor.h b/include/binder/ParcelFileDescriptor.h
new file mode 120000
index 0000000..777bd49
--- /dev/null
+++ b/include/binder/ParcelFileDescriptor.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/ParcelFileDescriptor.h
\ No newline at end of file
diff --git a/include/binder/Parcelable.h b/include/binder/Parcelable.h
new file mode 120000
index 0000000..438e223
--- /dev/null
+++ b/include/binder/Parcelable.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/Parcelable.h
\ No newline at end of file
diff --git a/include/binder/PermissionCache.h b/include/binder/PermissionCache.h
new file mode 120000
index 0000000..e910c12
--- /dev/null
+++ b/include/binder/PermissionCache.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/PermissionCache.h
\ No newline at end of file
diff --git a/include/binder/PersistableBundle.h b/include/binder/PersistableBundle.h
new file mode 120000
index 0000000..785f2b4
--- /dev/null
+++ b/include/binder/PersistableBundle.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/PersistableBundle.h
\ No newline at end of file
diff --git a/include/binder/ProcessState.h b/include/binder/ProcessState.h
new file mode 120000
index 0000000..4cbe7a5
--- /dev/null
+++ b/include/binder/ProcessState.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/ProcessState.h
\ No newline at end of file
diff --git a/include/binder/Stability.h b/include/binder/Stability.h
new file mode 120000
index 0000000..9b431d2
--- /dev/null
+++ b/include/binder/Stability.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/Stability.h
\ No newline at end of file
diff --git a/include/binder/Status.h b/include/binder/Status.h
new file mode 120000
index 0000000..ccb994e
--- /dev/null
+++ b/include/binder/Status.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/Status.h
\ No newline at end of file
diff --git a/include/input/Input.h b/include/input/Input.h
index cbd1a41..f871847 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -31,8 +31,8 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 #include <utils/Vector.h>
-
 #include <limits>
+#include <queue>
 
 /*
  * Additional private constants not defined in ndk/ui/input.h.
@@ -167,6 +167,8 @@
 class Parcel;
 #endif
 
+const char* inputEventTypeToString(int32_t type);
+
 /*
  * Flags that flow alongside events in the input dispatch system to help with certain
  * policy decisions such as waking from device sleep.
@@ -687,6 +689,28 @@
 };
 
 /*
+ * Focus events.
+ */
+class FocusEvent : public InputEvent {
+public:
+    virtual ~FocusEvent() {}
+
+    virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_FOCUS; }
+
+    inline bool getHasFocus() const { return mHasFocus; }
+
+    inline bool getInTouchMode() const { return mInTouchMode; }
+
+    void initialize(bool hasFocus, bool inTouchMode);
+
+    void initialize(const FocusEvent& from);
+
+protected:
+    bool mHasFocus;
+    bool mInTouchMode;
+};
+
+/*
  * Input event factory.
  */
 class InputEventFactoryInterface {
@@ -698,6 +722,7 @@
 
     virtual KeyEvent* createKeyEvent() = 0;
     virtual MotionEvent* createMotionEvent() = 0;
+    virtual FocusEvent* createFocusEvent() = 0;
 };
 
 /*
@@ -709,12 +734,14 @@
     PreallocatedInputEventFactory() { }
     virtual ~PreallocatedInputEventFactory() { }
 
-    virtual KeyEvent* createKeyEvent() { return & mKeyEvent; }
-    virtual MotionEvent* createMotionEvent() { return & mMotionEvent; }
+    virtual KeyEvent* createKeyEvent() override { return &mKeyEvent; }
+    virtual MotionEvent* createMotionEvent() override { return &mMotionEvent; }
+    virtual FocusEvent* createFocusEvent() override { return &mFocusEvent; }
 
 private:
     KeyEvent mKeyEvent;
     MotionEvent mMotionEvent;
+    FocusEvent mFocusEvent;
 };
 
 /*
@@ -725,16 +752,18 @@
     explicit PooledInputEventFactory(size_t maxPoolSize = 20);
     virtual ~PooledInputEventFactory();
 
-    virtual KeyEvent* createKeyEvent();
-    virtual MotionEvent* createMotionEvent();
+    virtual KeyEvent* createKeyEvent() override;
+    virtual MotionEvent* createMotionEvent() override;
+    virtual FocusEvent* createFocusEvent() override;
 
     void recycle(InputEvent* event);
 
 private:
     const size_t mMaxPoolSize;
 
-    Vector<KeyEvent*> mKeyEventPool;
-    Vector<MotionEvent*> mMotionEventPool;
+    std::queue<std::unique_ptr<KeyEvent>> mKeyEventPool;
+    std::queue<std::unique_ptr<MotionEvent>> mMotionEventPool;
+    std::queue<std::unique_ptr<FocusEvent>> mFocusEventPool;
 };
 
 } // namespace android
diff --git a/include/input/InputEventLabels.h b/include/input/InputEventLabels.h
index eaa562b..b327d76 100644
--- a/include/input/InputEventLabels.h
+++ b/include/input/InputEventLabels.h
@@ -405,13 +405,12 @@
     { nullptr, 0 }
 };
 
-static const InputEventLabel FLAGS[] = {
-    DEFINE_FLAG(VIRTUAL),
-    DEFINE_FLAG(FUNCTION),
-    DEFINE_FLAG(GESTURE),
+static const InputEventLabel FLAGS[] = {DEFINE_FLAG(VIRTUAL),
+                                        DEFINE_FLAG(FUNCTION),
+                                        DEFINE_FLAG(GESTURE),
+                                        DEFINE_FLAG(WAKE),
 
-    { nullptr, 0 }
-};
+                                        {nullptr, 0}};
 
 static int lookupValueByLabel(const char* literal, const InputEventLabel *list) {
     while (list->literal) {
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 94d90ad..ae47438 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -64,6 +64,7 @@
         KEY,
         MOTION,
         FINISHED,
+        FOCUS,
     };
 
     struct Header {
@@ -92,9 +93,7 @@
             uint32_t empty2;
             nsecs_t downTime __attribute__((aligned(8)));
 
-            inline size_t size() const {
-                return sizeof(Key);
-            }
+            inline size_t size() const { return sizeof(Key); }
         } key;
 
         struct Motion {
@@ -110,7 +109,7 @@
             int32_t metaState;
             int32_t buttonState;
             MotionClassification classification; // base type: uint8_t
-            uint8_t empty2[3];
+            uint8_t empty2[3];                   // 3 bytes to fill gap created by classification
             int32_t edgeFlags;
             nsecs_t downTime __attribute__((aligned(8)));
             float xOffset;
@@ -121,11 +120,16 @@
             float yCursorPosition;
             uint32_t pointerCount;
             uint32_t empty3;
-            // Note that PointerCoords requires 8 byte alignment.
+            /**
+             * The "pointers" field must be the last field of the struct InputMessage.
+             * When we send the struct InputMessage across the socket, we are not
+             * writing the entire "pointers" array, but only the pointerCount portion
+             * of it as an optimization. Adding a field after "pointers" would break this.
+             */
             struct Pointer {
                 PointerProperties properties;
                 PointerCoords coords;
-            } pointers[MAX_POINTERS];
+            } pointers[MAX_POINTERS] __attribute__((aligned(8)));
 
             int32_t getActionId() const {
                 uint32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
@@ -141,12 +145,19 @@
 
         struct Finished {
             uint32_t seq;
-            bool handled;
+            uint32_t handled; // actually a bool, but we must maintain 8-byte alignment
 
-            inline size_t size() const {
-                return sizeof(Finished);
-            }
+            inline size_t size() const { return sizeof(Finished); }
         } finished;
+
+        struct Focus {
+            uint32_t seq;
+            // 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
+
+            inline size_t size() const { return sizeof(Focus); }
+        } focus;
     } __attribute__((aligned(8))) body;
 
     bool isValid(size_t actualSize) const;
@@ -289,6 +300,15 @@
                                 uint32_t pointerCount, const PointerProperties* pointerProperties,
                                 const PointerCoords* pointerCoords);
 
+    /* Publishes a focus event to the input channel.
+     *
+     * Returns OK on success.
+     * Returns WOULD_BLOCK if the channel is full.
+     * Returns DEAD_OBJECT if the channel's peer has been closed.
+     * Other errors probably indicate that the channel is broken.
+     */
+    status_t publishFocusEvent(uint32_t seq, bool hasFocus, bool inTouchMode);
+
     /* Receives the finished signal from the consumer in reply to the original dispatch signal.
      * If a signal was received, returns the message sequence number,
      * and whether the consumer handled the message.
@@ -344,8 +364,8 @@
      * Returns NO_MEMORY if the event could not be created.
      * Other errors probably indicate that the channel is broken.
      */
-    status_t consume(InputEventFactoryInterface* factory, bool consumeBatches,
-            nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
+    status_t consume(InputEventFactoryInterface* factory, bool consumeBatches, nsecs_t frameTime,
+                     uint32_t* outSeq, InputEvent** outEvent);
 
     /* Sends a finished signal to the publisher to inform it that the message
      * with the specified sequence number has finished being process and whether
@@ -516,6 +536,7 @@
     static void rewriteMessage(TouchState& state, InputMessage& msg);
     static void initializeKeyEvent(KeyEvent* event, const InputMessage* msg);
     static void initializeMotionEvent(MotionEvent* event, const InputMessage* msg);
+    static void initializeFocusEvent(FocusEvent* event, const InputMessage* msg);
     static void addSample(MotionEvent* event, const InputMessage* msg);
     static bool canAddSample(const Batch& batch, const InputMessage* msg);
     static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
diff --git a/include/input/InputWindow.h b/include/input/InputWindow.h
index 916af69..f852cd0 100644
--- a/include/input/InputWindow.h
+++ b/include/input/InputWindow.h
@@ -63,7 +63,6 @@
         FLAG_DISMISS_KEYGUARD = 0x00400000,
         FLAG_SPLIT_TOUCH = 0x00800000,
         FLAG_SLIPPERY = 0x20000000,
-        FLAG_NEEDS_MENU_KEY = 0x40000000,
     };
 
     // Window types from WindowManager.LayoutParams
diff --git a/include/ui b/include/ui
deleted file mode 120000
index 2fb3147..0000000
--- a/include/ui
+++ /dev/null
@@ -1 +0,0 @@
-../libs/ui/include/ui
\ No newline at end of file
diff --git a/include/ui/DisplayInfo.h b/include/ui/DisplayInfo.h
new file mode 120000
index 0000000..9a195ea
--- /dev/null
+++ b/include/ui/DisplayInfo.h
@@ -0,0 +1 @@
+../../libs/ui/include/ui/DisplayInfo.h
\ No newline at end of file
diff --git a/include/ui/FloatRect.h b/include/ui/FloatRect.h
new file mode 120000
index 0000000..d7bd737
--- /dev/null
+++ b/include/ui/FloatRect.h
@@ -0,0 +1 @@
+../../libs/ui/include/ui/FloatRect.h
\ No newline at end of file
diff --git a/include/ui/PixelFormat.h b/include/ui/PixelFormat.h
new file mode 120000
index 0000000..4085433
--- /dev/null
+++ b/include/ui/PixelFormat.h
@@ -0,0 +1 @@
+../../libs/ui/include/ui/PixelFormat.h
\ No newline at end of file
diff --git a/include/ui/Point.h b/include/ui/Point.h
new file mode 120000
index 0000000..443938b
--- /dev/null
+++ b/include/ui/Point.h
@@ -0,0 +1 @@
+../../libs/ui/include/ui/Point.h
\ No newline at end of file
diff --git a/include/ui/PublicFormat.h b/include/ui/PublicFormat.h
new file mode 120000
index 0000000..7984c0e
--- /dev/null
+++ b/include/ui/PublicFormat.h
@@ -0,0 +1 @@
+../../libs/ui/include/ui/PublicFormat.h
\ No newline at end of file
diff --git a/include/ui/Rect.h b/include/ui/Rect.h
new file mode 120000
index 0000000..a99c5f2
--- /dev/null
+++ b/include/ui/Rect.h
@@ -0,0 +1 @@
+../../libs/ui/include/ui/Rect.h
\ No newline at end of file
diff --git a/include/ui/Region.h b/include/ui/Region.h
new file mode 120000
index 0000000..2e46e0f
--- /dev/null
+++ b/include/ui/Region.h
@@ -0,0 +1 @@
+../../libs/ui/include/ui/Region.h
\ No newline at end of file
diff --git a/include/ui/Size.h b/include/ui/Size.h
new file mode 120000
index 0000000..c0da99b
--- /dev/null
+++ b/include/ui/Size.h
@@ -0,0 +1 @@
+../../libs/ui/include/ui/Size.h
\ No newline at end of file
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 4f47db1..bac8b66 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -280,19 +280,31 @@
         std::condition_variable mCv;
     };
 
+    // Simple RAII object to ensure a function call immediately before going out of scope
+    class Defer {
+    public:
+        Defer(std::function<void()>&& f) : mF(std::move(f)) {}
+        ~Defer() { mF(); }
+    private:
+        std::function<void()> mF;
+    };
+
     const std::string name = String8(name16).c_str();
 
     sp<IBinder> out;
     if (!mTheRealServiceManager->getService(name, &out).isOk()) {
         return nullptr;
     }
-    if(out != nullptr) return out;
+    if (out != nullptr) return out;
 
     sp<Waiter> waiter = new Waiter;
     if (!mTheRealServiceManager->registerForNotifications(
             name, waiter).isOk()) {
         return nullptr;
     }
+    Defer unregister ([&] {
+        mTheRealServiceManager->unregisterForNotifications(name, waiter);
+    });
 
     while(true) {
         {
@@ -316,7 +328,7 @@
         if (!mTheRealServiceManager->getService(name, &out).isOk()) {
             return nullptr;
         }
-        if(out != nullptr) return out;
+        if (out != nullptr) return out;
 
         ALOGW("Waited one second for %s", name.c_str());
     }
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index 28ffa48..79d9b79 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -38,12 +38,32 @@
 
 // ----------------------------------------------------------------------
 
+/**
+ * If this is a local object and the descriptor matches, this will return the
+ * actual local object which is implementing the interface. Otherwise, this will
+ * return a proxy to the interface without checking the interface descriptor.
+ * This means that subsequent calls may fail with BAD_TYPE.
+ */
 template<typename INTERFACE>
 inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
 {
     return INTERFACE::asInterface(obj);
 }
 
+/**
+ * This is the same as interface_cast, except that it always checks to make sure
+ * the descriptor matches, and if it doesn't match, it will return nullptr.
+ */
+template<typename INTERFACE>
+inline sp<INTERFACE> checked_interface_cast(const sp<IBinder>& obj)
+{
+    if (obj->getInterfaceDescriptor() != INTERFACE::descriptor) {
+        return nullptr;
+    }
+
+    return interface_cast<INTERFACE>(obj);
+}
+
 // ----------------------------------------------------------------------
 
 template<typename INTERFACE>
@@ -89,7 +109,27 @@
 
 
 #define __IINTF_CONCAT(x, y) (x ## y)
+
+#ifndef DO_NOT_CHECK_MANUAL_BINDER_INTERFACES
+
 #define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
+    static_assert(internal::allowedManualInterface(NAME),               \
+                  "b/64223827: Manually written binder interfaces are " \
+                  "considered error prone and frequently have bugs. "   \
+                  "The preferred way to add interfaces is to define "   \
+                  "an .aidl file to auto-generate the interface. If "   \
+                  "an interface must be manually written, add its "     \
+                  "name to the whitelist.");                            \
+    DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME)    \
+
+#else
+
+#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
+    DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME)    \
+
+#endif
+
+#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME)\
     const ::android::StaticString16                                     \
         I##INTERFACE##_descriptor_static_str16(__IINTF_CONCAT(u, NAME));\
     const ::android::String16 I##INTERFACE::descriptor(                 \
@@ -172,6 +212,122 @@
 
 // ----------------------------------------------------------------------
 
+namespace internal {
+constexpr const char* const kManualInterfaces[] = {
+  "android.app.IActivityManager",
+  "android.app.IUidObserver",
+  "android.drm.IDrm",
+  "android.dvr.IVsyncCallback",
+  "android.dvr.IVsyncService",
+  "android.gfx.tests.ICallback",
+  "android.gfx.tests.IIPCTest",
+  "android.gfx.tests.ISafeInterfaceTest",
+  "android.graphicsenv.IGpuService",
+  "android.gui.DisplayEventConnection",
+  "android.gui.IConsumerListener",
+  "android.gui.IGraphicBufferConsumer",
+  "android.gui.IRegionSamplingListener",
+  "android.gui.ITransactionComposerListener",
+  "android.gui.SensorEventConnection",
+  "android.gui.SensorServer",
+  "android.hardware.ICamera",
+  "android.hardware.ICameraClient",
+  "android.hardware.ICameraRecordingProxy",
+  "android.hardware.ICameraRecordingProxyListener",
+  "android.hardware.ICrypto",
+  "android.hardware.IOMXObserver",
+  "android.hardware.ISoundTrigger",
+  "android.hardware.ISoundTriggerClient",
+  "android.hardware.ISoundTriggerHwService",
+  "android.hardware.IStreamListener",
+  "android.hardware.IStreamSource",
+  "android.input.IInputFlinger",
+  "android.input.ISetInputWindowsListener",
+  "android.media.IAudioFlinger",
+  "android.media.IAudioFlingerClient",
+  "android.media.IAudioPolicyService",
+  "android.media.IAudioPolicyServiceClient",
+  "android.media.IAudioService",
+  "android.media.IAudioTrack",
+  "android.media.IDataSource",
+  "android.media.IDrmClient",
+  "android.media.IEffect",
+  "android.media.IEffectClient",
+  "android.media.IMediaAnalyticsService",
+  "android.media.IMediaCodecList",
+  "android.media.IMediaDrmService",
+  "android.media.IMediaExtractor",
+  "android.media.IMediaExtractorService",
+  "android.media.IMediaHTTPConnection",
+  "android.media.IMediaHTTPService",
+  "android.media.IMediaLogService",
+  "android.media.IMediaMetadataRetriever",
+  "android.media.IMediaPlayer",
+  "android.media.IMediaPlayerClient",
+  "android.media.IMediaPlayerService",
+  "android.media.IMediaRecorder",
+  "android.media.IMediaRecorderClient",
+  "android.media.IMediaResourceMonitor",
+  "android.media.IMediaSource",
+  "android.media.IRemoteDisplay",
+  "android.media.IRemoteDisplayClient",
+  "android.media.IResourceManagerClient",
+  "android.media.IResourceManagerService",
+  "android.os.IComplexTypeInterface",
+  "android.os.IPermissionController",
+  "android.os.IPingResponder",
+  "android.os.IPowerManager",
+  "android.os.IProcessInfoService",
+  "android.os.ISchedulingPolicyService",
+  "android.os.IStringConstants",
+  "android.os.storage.IObbActionListener",
+  "android.os.storage.IStorageEventListener",
+  "android.os.storage.IStorageManager",
+  "android.os.storage.IStorageShutdownObserver",
+  "android.service.vr.IPersistentVrStateCallbacks",
+  "android.service.vr.IVrManager",
+  "android.service.vr.IVrStateCallbacks",
+  "android.ui.ISurfaceComposer",
+  "android.ui.ISurfaceComposerClient",
+  "android.utils.IMemory",
+  "android.utils.IMemoryHeap",
+  "com.android.car.procfsinspector.IProcfsInspector",
+  "com.android.internal.app.IAppOpsCallback",
+  "com.android.internal.app.IAppOpsService",
+  "com.android.internal.app.IBatteryStats",
+  "com.android.internal.os.IResultReceiver",
+  "com.android.internal.os.IShellCallback",
+  "drm.IDrmManagerService",
+  "drm.IDrmServiceListener",
+  "IAAudioClient",
+  "IAAudioService",
+  "VtsFuzzer",
+  nullptr,
+};
+
+constexpr const char* const kDownstreamManualInterfaces[] = {
+  // Add downstream interfaces here.
+  nullptr,
+};
+
+constexpr bool equals(const char* a, const char* b) {
+  if (*a != *b) return false;
+  if (*a == '\0') return true;
+  return equals(a + 1, b + 1);
+}
+
+constexpr bool inList(const char* a, const char* const* whitelist) {
+  if (*whitelist == nullptr) return false;
+  if (equals(a, *whitelist)) return true;
+  return inList(a, whitelist + 1);
+}
+
+constexpr bool allowedManualInterface(const char* name) {
+  return inList(name, kManualInterfaces) ||
+         inList(name, kDownstreamManualInterfaces);
+}
+
+} // namespace internal
 } // namespace android
 
 #endif // ANDROID_IINTERFACE_H
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 4d5c044..4560f22 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -34,6 +34,12 @@
 #include <android/binder_status.h>
 
 __BEGIN_DECLS
+
+#ifndef __ANDROID_API__
+#error Android builds must be compiled against a specific API. If this is an \
+ android platform host build, you must use libbinder_ndk_host_user.
+#endif
+
 #if __ANDROID_API__ >= 29
 
 // Also see TF_* in kernel's binder.h
diff --git a/libs/dumputils/dump_utils.cpp b/libs/dumputils/dump_utils.cpp
index 56b94c1..0477801 100644
--- a/libs/dumputils/dump_utils.cpp
+++ b/libs/dumputils/dump_utils.cpp
@@ -16,7 +16,9 @@
 #include <set>
 
 #include <android-base/file.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <android/hidl/manager/1.0/IServiceManager.h>
 #include <dumputils/dump_utils.h>
 #include <log/log.h>
@@ -68,13 +70,34 @@
         NULL,
 };
 
-bool should_dump_hal_interface(const char* interface) {
+/* list of extra hal interfaces to dump containing process during native dumps */
+// This is filled when dumpstate is called.
+static std::set<const std::string> extra_hal_interfaces_to_dump;
+
+static void read_extra_hals_to_dump_from_property() {
+    // extra hals to dump are already filled
+    if (extra_hal_interfaces_to_dump.size() > 0) {
+        return;
+    }
+    std::string value = android::base::GetProperty("ro.dump.hals.extra", "");
+    std::vector<std::string> tokens = android::base::Split(value, ",");
+    for (const auto &token : tokens) {
+        std::string trimmed_token = android::base::Trim(token);
+        if (trimmed_token.length() == 0) {
+            continue;
+        }
+        extra_hal_interfaces_to_dump.insert(trimmed_token);
+    }
+}
+
+// check if interface is included in either default hal list or extra hal list
+bool should_dump_hal_interface(const std::string& interface) {
     for (const char** i = hal_interfaces_to_dump; *i; i++) {
-        if (!strcmp(*i, interface)) {
+        if (interface == *i) {
             return true;
         }
     }
-    return false;
+    return extra_hal_interfaces_to_dump.find(interface) != extra_hal_interfaces_to_dump.end();
 }
 
 bool should_dump_native_traces(const char* path) {
@@ -94,13 +117,15 @@
     sp<IServiceManager> manager = IServiceManager::getService();
     std::set<int> pids;
 
+    read_extra_hals_to_dump_from_property();
+
     Return<void> ret = manager->debugDump([&](auto& hals) {
         for (const auto &info : hals) {
             if (info.pid == static_cast<int>(IServiceManager::PidConstant::NO_PID)) {
                 continue;
             }
 
-            if (!should_dump_hal_interface(info.interfaceName.c_str())) {
+            if (!should_dump_hal_interface(info.interfaceName)) {
                 continue;
             }
 
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
new file mode 100644
index 0000000..cb5305f
--- /dev/null
+++ b/libs/gralloc/types/Android.bp
@@ -0,0 +1,54 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+    name: "libgralloctypes",
+    defaults: ["libbinder_ndk_host_user"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-enum-compare",
+    ],
+    host_supported: true,
+
+    vendor_available: true,
+    vndk: {
+        enabled: true,
+        support_system_process: true,
+    },
+
+    srcs: [
+        "Gralloc4.cpp"
+    ],
+
+    shared_libs: [
+        "android.hardware.graphics.mapper@4.0",
+        "libhidlbase",
+        "liblog",
+        "vintf-graphics-common-ndk_platform",
+    ],
+
+    export_shared_lib_headers: [
+        "android.hardware.graphics.mapper@4.0",
+        "vintf-graphics-common-ndk_platform",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    sanitize: {
+        misc_undefined: ["integer"],
+    },
+}
diff --git a/libs/gralloc/types/Gralloc4.cpp b/libs/gralloc/types/Gralloc4.cpp
new file mode 100644
index 0000000..b762f1e
--- /dev/null
+++ b/libs/gralloc/types/Gralloc4.cpp
@@ -0,0 +1,916 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstring>
+#include <cinttypes>
+#include <limits>
+
+#include <hidl/HidlSupport.h>
+#include <log/log.h>
+
+#include "gralloctypes/Gralloc4.h"
+
+using android::hardware::hidl_vec;
+
+using aidl::android::hardware::graphics::common::BlendMode;
+using aidl::android::hardware::graphics::common::ChromaSiting;
+using aidl::android::hardware::graphics::common::Compression;
+using aidl::android::hardware::graphics::common::Dataspace;
+using aidl::android::hardware::graphics::common::ExtendableType;
+using aidl::android::hardware::graphics::common::Interlaced;
+using aidl::android::hardware::graphics::common::PlaneLayout;
+using aidl::android::hardware::graphics::common::PlaneLayoutComponent;
+using aidl::android::hardware::graphics::common::PlaneLayoutComponentType;
+using aidl::android::hardware::graphics::common::Rect;
+using aidl::android::hardware::graphics::common::StandardMetadataType;
+
+using MetadataType = android::hardware::graphics::mapper::V4_0::IMapper::MetadataType;
+
+namespace android {
+
+namespace gralloc4 {
+
+static inline bool hasAdditionOverflow(size_t a, size_t b) {
+    return a > SIZE_MAX - b;
+}
+
+/**
+ * OutputHidlVec represents the hidl_vec that is outputed when a type is encoded into a byte stream.
+ * This class is used to track the current state of a hidl_vec as it is filled with the encoded
+ * byte stream.
+ *
+ * This type is needed because hidl_vec's resize() allocates a new backing array every time.
+ * This type does not need an copies and only needs one resize operation.
+ */
+class OutputHidlVec {
+public:
+    OutputHidlVec(hidl_vec<uint8_t>* vec)
+        : mVec(vec) {}
+
+    status_t resize() {
+        if (!mVec) {
+            return BAD_VALUE;
+        }
+        mVec->resize(mNeededResize);
+        mResized = true;
+        return NO_ERROR;
+    }
+
+    status_t encode(const uint8_t* data, size_t size) {
+        if (!mVec) {
+            return BAD_VALUE;
+        }
+        if (!mResized) {
+            if (hasAdditionOverflow(mNeededResize, size)) {
+                clear();
+                return BAD_VALUE;
+            }
+            /**
+             * Update mNeededResize and return NO_ERROR here because if (!mResized), the
+             * caller hasn't called resize(). No data will be written into the mVec until
+             * the caller resizes. We can't resize here for the caller because hidl_vec::resize()
+             * allocates a new backing array every time.
+             */
+            mNeededResize += size;
+            return NO_ERROR;
+        }
+
+        if (hasAdditionOverflow(mOffset, size) || (mVec->size() < size + mOffset)) {
+            clear();
+            return BAD_VALUE;
+        }
+
+        std::copy(data, data + size, mVec->data() + mOffset);
+
+        mOffset += size;
+        return NO_ERROR;
+    }
+
+    void clear() {
+        if (mVec) {
+            mVec->resize(0);
+        }
+        mNeededResize = 0;
+        mResized = false;
+        mOffset = 0;
+    }
+
+private:
+    hidl_vec<uint8_t>* mVec;
+    size_t mNeededResize = 0;
+    size_t mResized = false;
+    size_t mOffset = 0;
+};
+
+/**
+ * InputHidlVec represents the hidl_vec byte stream that is inputed when a type is decoded.
+ * This class is used to track the current index of the byte stream of the hidl_vec as it is
+ * decoded.
+ */
+class InputHidlVec {
+public:
+    InputHidlVec(const hidl_vec<uint8_t>* vec)
+        : mVec(vec) {}
+
+    status_t decode(uint8_t* data, size_t size) {
+        if (!mVec || hasAdditionOverflow(mOffset, size) || mOffset + size > mVec->size()) {
+            return BAD_VALUE;
+        }
+
+        std::copy(mVec->data() + mOffset, mVec->data() + mOffset + size, data);
+
+        mOffset += size;
+        return NO_ERROR;
+    }
+
+    status_t decode(std::string* string, size_t size) {
+        if (!mVec || hasAdditionOverflow(mOffset, size) || mOffset + size > mVec->size()) {
+            return BAD_VALUE;
+        }
+
+        string->assign(mVec->data() + mOffset, mVec->data() + mOffset + size);
+
+        mOffset += size;
+        return NO_ERROR;
+    }
+
+    bool hasRemainingData() {
+        if (!mVec) {
+            return false;
+        }
+        return mVec->size() > mOffset;
+    }
+
+private:
+    const hidl_vec<uint8_t>* mVec;
+    size_t mOffset = 0;
+};
+
+/**
+ * EncodeHelper is a function type that encodes T into the OutputHidlVec.
+ */
+template<class T>
+using EncodeHelper = status_t(*)(const T&, OutputHidlVec*);
+
+/**
+ * DecodeHelper is a function type that decodes InputHidlVec into T.
+ */
+template<class T>
+using DecodeHelper = status_t(*)(InputHidlVec*, T*);
+
+/**
+ * ErrorHandler is a function type that is called when the corresponding DecodeHelper function
+ * fails. ErrorHandler cleans up the object T so the caller doesn't receive a partially created
+ * T.
+ */
+template<class T>
+using ErrorHandler = void(*)(T*);
+
+status_t encodeMetadataType(const MetadataType& input, OutputHidlVec* output);
+status_t validateMetadataType(InputHidlVec* input, const MetadataType& expectedMetadataType);
+
+/**
+ * encode is the main encoding function. It takes in T and uses the encodeHelper function to turn T
+ * into the hidl_vec byte stream.
+ *
+ * This function first calls the encodeHelper function to determine how large the hidl_vec
+ * needs to be. It resizes the hidl_vec. Finally, it reruns the encodeHelper function which
+ * encodes T into the hidl_vec byte stream.
+ */
+template <class T>
+status_t encode(const MetadataType& metadataType, const T& input, hidl_vec<uint8_t>* output,
+                EncodeHelper<T> encodeHelper) {
+    OutputHidlVec outputHidlVec{output};
+
+    status_t err = encodeMetadataType(metadataType, &outputHidlVec);
+    if (err) {
+        return err;
+    }
+
+    err = encodeHelper(input, &outputHidlVec);
+    if (err) {
+        return err;
+    }
+
+    err = outputHidlVec.resize();
+    if (err) {
+        return err;
+    }
+
+    err = encodeMetadataType(metadataType, &outputHidlVec);
+    if (err) {
+        return err;
+    }
+
+    return encodeHelper(input, &outputHidlVec);
+}
+
+/**
+ * decode is the main decode function. It takes in a hidl_vec and uses the decodeHelper function to
+ * turn the hidl_vec byte stream into T. If an error occurs, the errorHandler function cleans up
+ * T.
+ */
+template <class T>
+status_t decode(const MetadataType& metadataType, const hidl_vec<uint8_t>& input, T* output,
+                DecodeHelper<T> decodeHelper, ErrorHandler<T> errorHandler = nullptr) {
+    InputHidlVec inputHidlVec{&input};
+
+    status_t err = validateMetadataType(&inputHidlVec, metadataType);
+    if (err) {
+        return err;
+    }
+
+    err = decodeHelper(&inputHidlVec, output);
+    if (err) {
+        return err;
+    }
+
+    err = inputHidlVec.hasRemainingData();
+    if (err) {
+        if (errorHandler) {
+            errorHandler(output);
+        }
+        return BAD_VALUE;
+    }
+
+    return NO_ERROR;
+}
+
+/**
+ * Private helper functions
+ */
+template <class T>
+status_t encodeInteger(const T& input, OutputHidlVec* output) {
+    static_assert(std::is_same<T, uint32_t>::value || std::is_same<T, int32_t>::value ||
+                  std::is_same<T, uint64_t>::value || std::is_same<T, int64_t>::value);
+    if (!output) {
+        return BAD_VALUE;
+    }
+
+    const uint8_t* tmp = reinterpret_cast<const uint8_t*>(&input);
+    return output->encode(tmp, sizeof(input));
+}
+
+template <class T>
+status_t decodeInteger(InputHidlVec* input, T* output) {
+    static_assert(std::is_same<T, uint32_t>::value || std::is_same<T, int32_t>::value ||
+                  std::is_same<T, uint64_t>::value || std::is_same<T, int64_t>::value);
+    if (!output) {
+        return BAD_VALUE;
+    }
+
+    uint8_t* tmp = reinterpret_cast<uint8_t*>(output);
+    return input->decode(tmp, sizeof(*output));
+}
+
+status_t encodeString(const std::string& input, OutputHidlVec* output) {
+    if (!output) {
+        return BAD_VALUE;
+    }
+
+    status_t err = encodeInteger<int64_t>(input.size(), output);
+    if (err) {
+        return err;
+    }
+
+    return output->encode(reinterpret_cast<const uint8_t*>(input.data()), input.size());
+}
+
+status_t decodeString(InputHidlVec* input, std::string* output) {
+    if (!output) {
+        return BAD_VALUE;
+    }
+
+    int64_t size = 0;
+    status_t err = decodeInteger<int64_t>(input, &size);
+    if (err) {
+        return err;
+    }
+    if (size < 0) {
+        return BAD_VALUE;
+    }
+
+    return input->decode(output, size);
+}
+
+status_t encodeExtendableType(const ExtendableType& input, OutputHidlVec* output) {
+    status_t err = encodeString(input.name, output);
+    if (err) {
+        return err;
+    }
+
+    err = encodeInteger<int64_t>(input.value, output);
+    if (err) {
+        return err;
+    }
+
+    return NO_ERROR;
+}
+
+status_t decodeExtendableType(InputHidlVec* input, ExtendableType* output) {
+    status_t err = decodeString(input, &output->name);
+    if (err) {
+        return err;
+    }
+
+    err = decodeInteger<int64_t>(input, &output->value);
+    if (err) {
+        return err;
+    }
+
+    return NO_ERROR;
+}
+
+void clearExtendableType(ExtendableType* output) {
+    if (!output) {
+        return;
+    }
+    output->name.clear();
+    output->value = 0;
+}
+
+status_t encodeMetadataType(const MetadataType& input, OutputHidlVec* output) {
+    status_t err = encodeString(input.name, output);
+    if (err) {
+        return err;
+    }
+
+    err = encodeInteger<int64_t>(input.value, output);
+    if (err) {
+        return err;
+    }
+
+    return NO_ERROR;
+}
+
+status_t decodeMetadataType(InputHidlVec* input, MetadataType* output) {
+    std::string name;
+    status_t err = decodeString(input, &name);
+    if (err) {
+        return err;
+    }
+    output->name = name;
+
+    err = decodeInteger<int64_t>(input, &output->value);
+    if (err) {
+        return err;
+    }
+
+    return NO_ERROR;
+}
+
+status_t validateMetadataType(InputHidlVec* input, const MetadataType& expectedMetadataType) {
+    MetadataType receivedMetadataType;
+
+    status_t err = decodeMetadataType(input, &receivedMetadataType);
+    if (err) {
+        return err;
+    }
+
+    if (expectedMetadataType.name != receivedMetadataType.name) {
+        return BAD_VALUE;
+    }
+
+    if (receivedMetadataType.value != expectedMetadataType.value) {
+        return BAD_VALUE;
+    }
+
+    return NO_ERROR;
+}
+
+status_t encodeRect(const Rect& input, OutputHidlVec* output) {
+    status_t err = encodeInteger<int32_t>(static_cast<int32_t>(input.left), output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<int32_t>(static_cast<int32_t>(input.top), output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<int32_t>(static_cast<int32_t>(input.right), output);
+    if (err) {
+        return err;
+    }
+    return encodeInteger<int32_t>(static_cast<int32_t>(input.bottom), output);
+}
+
+status_t decodeRect(InputHidlVec* input, Rect* output) {
+    status_t err = decodeInteger<int32_t>(input, &output->left);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<int32_t>(input, &output->top);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<int32_t>(input, &output->right);
+    if (err) {
+        return err;
+    }
+    return decodeInteger<int32_t>(input, &output->bottom);
+}
+
+status_t encodePlaneLayoutComponent(const PlaneLayoutComponent& input, OutputHidlVec* output) {
+    if (!output) {
+        return BAD_VALUE;
+    }
+
+    status_t err = encodeExtendableType(input.type, output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<int64_t>(static_cast<int64_t>(input.offsetInBits), output);
+    if (err) {
+        return err;
+    }
+    return encodeInteger<int64_t>(static_cast<int64_t>(input.sizeInBits), output);
+}
+
+status_t decodePlaneLayoutComponent(InputHidlVec* input, PlaneLayoutComponent* output) {
+    if (!output) {
+        return BAD_VALUE;
+    }
+
+    status_t err = decodeExtendableType(input, &output->type);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<int64_t>(input, &output->offsetInBits);
+    if (err) {
+        return err;
+    }
+    return decodeInteger<int64_t>(input, &output->sizeInBits);
+}
+
+status_t encodePlaneLayoutComponents(const std::vector<PlaneLayoutComponent>& input, OutputHidlVec* output) {
+    if (!output) {
+        return BAD_VALUE;
+    }
+
+    status_t err = encodeInteger<int64_t>(static_cast<int64_t>(input.size()), output);
+    if (err) {
+        return err;
+    }
+
+    for (const auto& planeLayoutComponent: input) {
+        err = encodePlaneLayoutComponent(planeLayoutComponent, output);
+        if (err) {
+            return err;
+        }
+    }
+
+    return NO_ERROR;
+}
+
+status_t decodePlaneLayoutComponents(InputHidlVec* input, std::vector<PlaneLayoutComponent>* output) {
+    if (!output) {
+        return BAD_VALUE;
+    }
+
+    int64_t size = 0;
+    status_t err = decodeInteger<int64_t>(input, &size);
+    if (err) {
+        return err;
+    }
+    if (size < 0 || size > 10000) {
+        return BAD_VALUE;
+    }
+
+    output->resize(size);
+
+    for (auto& planeLayoutComponent : *output) {
+        err = decodePlaneLayoutComponent(input, &planeLayoutComponent);
+        if (err) {
+            return err;
+        }
+    }
+    return NO_ERROR;
+}
+
+status_t encodePlaneLayout(const PlaneLayout& input, OutputHidlVec* output) {
+    if (!output) {
+        return BAD_VALUE;
+    }
+
+    status_t err = encodePlaneLayoutComponents(input.components, output);
+    if (err) {
+        return err;
+    }
+
+    err = encodeInteger<int64_t>(static_cast<int32_t>(input.offsetInBytes), output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<int64_t>(static_cast<int32_t>(input.sampleIncrementInBits), output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<int64_t>(static_cast<int32_t>(input.strideInBytes), output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<int64_t>(static_cast<int32_t>(input.widthInSamples), output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<int64_t>(static_cast<int32_t>(input.heightInSamples), output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<int64_t>(static_cast<int32_t>(input.totalSizeInBytes), output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<int64_t>(static_cast<int32_t>(input.horizontalSubsampling), output);
+    if (err) {
+        return err;
+    }
+    err = encodeInteger<int64_t>(static_cast<int32_t>(input.verticalSubsampling), output);
+    if (err) {
+        return err;
+    }
+
+    return encodeRect(input.crop, output);
+}
+
+status_t decodePlaneLayout(InputHidlVec* input, PlaneLayout* output) {
+    if (!output) {
+        return BAD_VALUE;
+    }
+
+    status_t err = decodePlaneLayoutComponents(input, &output->components);
+    if (err) {
+        return err;
+    }
+
+    err = decodeInteger<int64_t>(input, &output->offsetInBytes);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<int64_t>(input, &output->sampleIncrementInBits);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<int64_t>(input, &output->strideInBytes);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<int64_t>(input, &output->widthInSamples);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<int64_t>(input, &output->heightInSamples);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<int64_t>(input, &output->totalSizeInBytes);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<int64_t>(input, &output->horizontalSubsampling);
+    if (err) {
+        return err;
+    }
+    err = decodeInteger<int64_t>(input, &output->verticalSubsampling);
+    if (err) {
+        return err;
+    }
+
+    return decodeRect(input, &output->crop);
+}
+
+status_t encodePlaneLayoutsHelper(const std::vector<PlaneLayout>& planeLayouts, OutputHidlVec* outOutputHidlVec) {
+    status_t err = encodeInteger<int64_t>(static_cast<int64_t>(planeLayouts.size()), outOutputHidlVec);
+    if (err) {
+        return err;
+    }
+
+    for (const auto& planeLayout : planeLayouts) {
+        err = encodePlaneLayout(planeLayout, outOutputHidlVec);
+        if (err) {
+            return err;
+        }
+    }
+
+    return NO_ERROR;
+}
+
+status_t decodePlaneLayoutsHelper(InputHidlVec* inputHidlVec, std::vector<PlaneLayout>* outPlaneLayouts) {
+    int64_t size = 0;
+    status_t err = decodeInteger<int64_t>(inputHidlVec, &size);
+    if (err) {
+        return err;
+    }
+    if (size < 0) {
+        return BAD_VALUE;
+    }
+
+    for (size_t i = 0; i < size; i++) {
+        outPlaneLayouts->emplace_back();
+        err = decodePlaneLayout(inputHidlVec, &outPlaneLayouts->back());
+        if (err) {
+            return err;
+        }
+    }
+    return NO_ERROR;
+}
+
+void clearPlaneLayouts(std::vector<PlaneLayout>* output) {
+    if (!output) {
+        return;
+    }
+    output->clear();
+}
+
+/**
+ * Public API functions
+ */
+PlaneLayoutComponentType getStandardPlaneLayoutComponentTypeValue(
+        const ExtendableType& planeLayoutComponentType) {
+    return static_cast<PlaneLayoutComponentType>(planeLayoutComponentType.value);
+}
+
+status_t encodeBufferId(uint64_t bufferId, hidl_vec<uint8_t>* outBufferId) {
+    return encode(MetadataType_BufferId, bufferId, outBufferId, encodeInteger);
+}
+
+status_t decodeBufferId(const hidl_vec<uint8_t>& bufferId, uint64_t* outBufferId) {
+    return decode(MetadataType_BufferId, bufferId, outBufferId, decodeInteger);
+}
+
+status_t encodeName(const std::string& name, hidl_vec<uint8_t>* outName) {
+    return encode(MetadataType_Name, name, outName, encodeString);
+}
+
+status_t decodeName(const hidl_vec<uint8_t>& name, std::string* outName) {
+    return decode(MetadataType_Name, name, outName, decodeString);
+}
+
+status_t encodeWidth(uint64_t width, hidl_vec<uint8_t>* outWidth) {
+    return encode(MetadataType_Width, width, outWidth, encodeInteger);
+}
+
+status_t decodeWidth(const hidl_vec<uint8_t>& width, uint64_t* outWidth) {
+    return decode(MetadataType_Width, width, outWidth, decodeInteger);
+}
+
+status_t encodeHeight(uint64_t height, hidl_vec<uint8_t>* outHeight) {
+    return encode(MetadataType_Height, height, outHeight, encodeInteger);
+}
+
+status_t decodeHeight(const hidl_vec<uint8_t>& height, uint64_t* outHeight) {
+    return decode(MetadataType_Height, height, outHeight, decodeInteger);
+}
+
+status_t encodeLayerCount(uint64_t layerCount, hidl_vec<uint8_t>* outLayerCount) {
+    return encode(MetadataType_LayerCount, layerCount, outLayerCount, encodeInteger);
+}
+
+status_t decodeLayerCount(const hidl_vec<uint8_t>& layerCount, uint64_t* outLayerCount) {
+    return decode(MetadataType_LayerCount, layerCount, outLayerCount, decodeInteger);
+}
+
+status_t encodePixelFormatRequested(const hardware::graphics::common::V1_2::PixelFormat& pixelFormatRequested,
+        hidl_vec<uint8_t>* outPixelFormatRequested) {
+    return encode(MetadataType_PixelFormatRequested, static_cast<int32_t>(pixelFormatRequested),
+                  outPixelFormatRequested, encodeInteger);
+}
+
+status_t decodePixelFormatRequested(const hidl_vec<uint8_t>& pixelFormatRequested,
+        hardware::graphics::common::V1_2::PixelFormat* outPixelFormatRequested) {
+    return decode(MetadataType_PixelFormatRequested, pixelFormatRequested,
+                  reinterpret_cast<int32_t*>(outPixelFormatRequested), decodeInteger);
+}
+
+status_t encodePixelFormatFourCC(uint32_t pixelFormatFourCC, hidl_vec<uint8_t>* outPixelFormatFourCC) {
+    return encode(MetadataType_PixelFormatFourCC, pixelFormatFourCC, outPixelFormatFourCC,
+                  encodeInteger);
+}
+
+status_t decodePixelFormatFourCC(const hidl_vec<uint8_t>& pixelFormatFourCC, uint32_t* outPixelFormatFourCC) {
+    return decode(MetadataType_PixelFormatFourCC, pixelFormatFourCC, outPixelFormatFourCC,
+                  decodeInteger);
+}
+
+status_t encodePixelFormatModifier(uint64_t pixelFormatModifier, hidl_vec<uint8_t>* outPixelFormatModifier) {
+    return encode(MetadataType_PixelFormatModifier, pixelFormatModifier, outPixelFormatModifier,
+                  encodeInteger);
+}
+
+status_t decodePixelFormatModifier(const hidl_vec<uint8_t>& pixelFormatModifier, uint64_t* outPixelFormatModifier) {
+    return decode(MetadataType_PixelFormatModifier, pixelFormatModifier, outPixelFormatModifier,
+                  decodeInteger);
+}
+
+status_t encodeUsage(uint64_t usage, hidl_vec<uint8_t>* outUsage) {
+    return encode(MetadataType_Usage, usage, outUsage, encodeInteger);
+}
+
+status_t decodeUsage(const hidl_vec<uint8_t>& usage, uint64_t* outUsage) {
+    return decode(MetadataType_Usage, usage, outUsage, decodeInteger);
+}
+
+status_t encodeAllocationSize(uint64_t allocationSize, hidl_vec<uint8_t>* outAllocationSize) {
+    return encode(MetadataType_AllocationSize, allocationSize, outAllocationSize, encodeInteger);
+}
+
+status_t decodeAllocationSize(const hidl_vec<uint8_t>& allocationSize, uint64_t* outAllocationSize) {
+    return decode(MetadataType_AllocationSize, allocationSize, outAllocationSize, decodeInteger);
+}
+
+status_t encodeProtectedContent(uint64_t protectedContent, hidl_vec<uint8_t>* outProtectedContent) {
+    return encode(MetadataType_ProtectedContent, protectedContent, outProtectedContent,
+                  encodeInteger);
+}
+
+status_t decodeProtectedContent(const hidl_vec<uint8_t>& protectedContent, uint64_t* outProtectedContent) {
+    return decode(MetadataType_ProtectedContent, protectedContent, outProtectedContent,
+                  decodeInteger);
+}
+
+status_t encodeCompression(const ExtendableType& compression, hidl_vec<uint8_t>* outCompression) {
+    return encode(MetadataType_Compression, compression, outCompression, encodeExtendableType);
+}
+
+status_t decodeCompression(const hidl_vec<uint8_t>& compression, ExtendableType* outCompression) {
+    return decode(MetadataType_Compression, compression, outCompression, decodeExtendableType,
+                  clearExtendableType);
+}
+
+status_t encodeInterlaced(const ExtendableType& interlaced, hidl_vec<uint8_t>* outInterlaced) {
+    return encode(MetadataType_Interlaced, interlaced, outInterlaced, encodeExtendableType);
+}
+
+status_t decodeInterlaced(const hidl_vec<uint8_t>& interlaced, ExtendableType* outInterlaced) {
+    return decode(MetadataType_Interlaced, interlaced, outInterlaced, decodeExtendableType,
+                  clearExtendableType);
+}
+
+status_t encodeChromaSiting(const ExtendableType& chromaSiting, hidl_vec<uint8_t>* outChromaSiting) {
+    return encode(MetadataType_ChromaSiting, chromaSiting, outChromaSiting, encodeExtendableType);
+}
+
+status_t decodeChromaSiting(const hidl_vec<uint8_t>& chromaSiting, ExtendableType* outChromaSiting) {
+    return decode(MetadataType_ChromaSiting, chromaSiting, outChromaSiting, decodeExtendableType,
+                  clearExtendableType);
+}
+
+status_t encodePlaneLayouts(const std::vector<PlaneLayout>& planeLayouts, hidl_vec<uint8_t>* outPlaneLayouts) {
+    return encode(MetadataType_PlaneLayouts, planeLayouts, outPlaneLayouts,
+                  encodePlaneLayoutsHelper);
+}
+
+status_t decodePlaneLayouts(const hidl_vec<uint8_t>& planeLayouts, std::vector<PlaneLayout>* outPlaneLayouts) {
+    return decode(MetadataType_PlaneLayouts, planeLayouts, outPlaneLayouts,
+                  decodePlaneLayoutsHelper, clearPlaneLayouts);
+}
+
+status_t encodeDataspace(const Dataspace& dataspace, hidl_vec<uint8_t>* outDataspace) {
+    return encode(MetadataType_Dataspace, static_cast<int32_t>(dataspace), outDataspace,
+                  encodeInteger);
+}
+
+status_t decodeDataspace(const hidl_vec<uint8_t>& dataspace, Dataspace* outDataspace) {
+    return decode(MetadataType_Dataspace, dataspace, reinterpret_cast<int32_t*>(outDataspace),
+                  decodeInteger);
+}
+
+status_t encodeBlendMode(const BlendMode& blendMode, hidl_vec<uint8_t>* outBlendMode) {
+    return encode(MetadataType_BlendMode, static_cast<int32_t>(blendMode), outBlendMode,
+                  encodeInteger);
+}
+
+status_t decodeBlendMode(const hidl_vec<uint8_t>& blendMode, BlendMode* outBlendMode) {
+    return decode(MetadataType_BlendMode, blendMode, reinterpret_cast<int32_t*>(outBlendMode),
+                  decodeInteger);
+}
+
+bool isStandardMetadataType(const MetadataType& metadataType) {
+    return !std::strncmp(metadataType.name.c_str(), GRALLOC4_STANDARD_METADATA_TYPE,
+                         metadataType.name.size());
+}
+
+bool isStandardCompression(const ExtendableType& compression) {
+    return !std::strncmp(compression.name.c_str(), GRALLOC4_STANDARD_COMPRESSION,
+                         compression.name.size());
+}
+
+bool isStandardInterlaced(const ExtendableType& interlaced) {
+    return !std::strncmp(interlaced.name.c_str(), GRALLOC4_STANDARD_INTERLACED,
+                         interlaced.name.size());
+}
+
+bool isStandardChromaSiting(const ExtendableType& chromaSiting) {
+    return !std::strncmp(chromaSiting.name.c_str(), GRALLOC4_STANDARD_CHROMA_SITING,
+                         chromaSiting.name.size());
+}
+
+bool isStandardPlaneLayoutComponentType(const ExtendableType& planeLayoutComponentType) {
+    return !std::strncmp(planeLayoutComponentType.name.c_str(), GRALLOC4_STANDARD_PLANE_LAYOUT_COMPONENT_TYPE,
+                         planeLayoutComponentType.name.size());
+}
+
+StandardMetadataType getStandardMetadataTypeValue(const MetadataType& metadataType) {
+    return static_cast<StandardMetadataType>(metadataType.value);
+}
+
+Compression getStandardCompressionValue(const ExtendableType& compression) {
+    return static_cast<Compression>(compression.value);
+}
+
+Interlaced getStandardInterlacedValue(const ExtendableType& interlaced) {
+    return static_cast<Interlaced>(interlaced.value);
+}
+
+ChromaSiting getStandardChromaSitingValue(const ExtendableType& chromaSiting) {
+    return static_cast<ChromaSiting>(chromaSiting.value);
+}
+
+std::string getCompressionName(const ExtendableType& compression) {
+    if (!isStandardCompression(compression)) {
+        std::ostringstream stream;
+        stream << compression.name << "#" << compression.value;
+        return stream.str();
+    }
+    switch (getStandardCompressionValue(compression)) {
+        case Compression::NONE:
+            return "None";
+        case Compression::DISPLAY_STREAM_COMPRESSION:
+            return "DisplayStreamCompression";
+    }
+}
+
+std::string getInterlacedName(const ExtendableType& interlaced) {
+    if (!isStandardInterlaced(interlaced)) {
+        std::ostringstream stream;
+        stream << interlaced.name << "#" << interlaced.value;
+        return stream.str();
+    }
+    switch (getStandardInterlacedValue(interlaced)) {
+        case Interlaced::NONE:
+            return "None";
+        case Interlaced::TOP_BOTTOM:
+            return "TopBottom";
+        case Interlaced::RIGHT_LEFT:
+            return "RightLeft";
+    }
+}
+
+std::string getChromaSitingName(const ExtendableType& chromaSiting) {
+    if (!isStandardChromaSiting(chromaSiting)) {
+        std::ostringstream stream;
+        stream << chromaSiting.name << "#" << chromaSiting.value;
+        return stream.str();
+    }
+    switch (getStandardChromaSitingValue(chromaSiting)) {
+        case ChromaSiting::NONE:
+            return "None";
+        case ChromaSiting::UNKNOWN:
+            return "Unknown";
+        case ChromaSiting::SITED_INTERSTITIAL:
+            return "SitedInterstitial";
+        case ChromaSiting::COSITED_HORIZONTAL:
+            return "CositedHorizontal";
+    }
+}
+
+std::string getPlaneLayoutComponentTypeName(const ExtendableType& planeLayoutComponentType) {
+    if (!isStandardPlaneLayoutComponentType(planeLayoutComponentType)) {
+        std::ostringstream stream;
+        stream << planeLayoutComponentType.name << "#" << planeLayoutComponentType.value;
+        return stream.str();
+    }
+    switch (getStandardPlaneLayoutComponentTypeValue(planeLayoutComponentType)) {
+        case PlaneLayoutComponentType::Y:
+            return "Y";
+        case PlaneLayoutComponentType::CB:
+            return "Cb";
+        case PlaneLayoutComponentType::CR:
+            return "Cr";
+        case PlaneLayoutComponentType::R:
+            return "R";
+        case PlaneLayoutComponentType::G:
+            return "G";
+        case PlaneLayoutComponentType::B:
+            return "B";
+        case PlaneLayoutComponentType::A:
+            return "A";
+    }
+}
+
+} // namespace gralloc4
+
+} // namespace android
diff --git a/libs/gralloc/types/fuzzer/Android.bp b/libs/gralloc/types/fuzzer/Android.bp
new file mode 100644
index 0000000..8444883
--- /dev/null
+++ b/libs/gralloc/types/fuzzer/Android.bp
@@ -0,0 +1,32 @@
+cc_fuzz {
+    name: "libgralloctypes_fuzzer",
+    defaults: ["libbinder_ndk_host_user"],
+    host_supported: true,
+
+    fuzz_config: {
+        cc: ["marissaw@google.com"],
+    },
+
+    srcs: [
+        "gralloctypes.cpp",
+        "main.cpp",
+        "util.cpp",
+    ],
+    static_libs: [
+        "libbase",
+        "libcgrouprc",
+        "libcgrouprc_format",
+        "libcutils",
+        "libgralloctypes",
+        "libhidlbase",
+        "liblog",
+        "libprocessgroup",
+        "libjsoncpp",
+        "libutils",
+    ],
+
+    // This flag enables verbose output in the fuzz target, and is very useful
+    // for debugging a failure. If you are trying to diagnose how a crash was
+    // produced, you may find uncommenting the below line very useful.
+    // cflags: ["-DENABLE_LOG_FUZZ"],
+}
diff --git a/libs/gralloc/types/fuzzer/gralloctypes.cpp b/libs/gralloc/types/fuzzer/gralloctypes.cpp
new file mode 100644
index 0000000..23c90b8
--- /dev/null
+++ b/libs/gralloc/types/fuzzer/gralloctypes.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define FUZZ_LOG_TAG "gralloctypes"
+
+#include <cstdint>
+
+#include <aidl/android/hardware/graphics/common/BlendMode.h>
+#include <aidl/android/hardware/graphics/common/Dataspace.h>
+#include <aidl/android/hardware/graphics/common/ExtendableType.h>
+#include <aidl/android/hardware/graphics/common/PlaneLayout.h>
+#include <android/hardware/graphics/common/1.2/types.h>
+#include <gralloctypes/Gralloc4.h>
+#include <hidl/HidlSupport.h>
+#include <utils/Errors.h>
+
+#include "gralloctypes.h"
+#include "util.h"
+
+using ::android::status_t;
+
+#define GRALLOCTYPES_DECODE(T, FUNC) \
+    [] (const ::android::hardware::hidl_vec<uint8_t>& vec, uint8_t /*data*/) {\
+        FUZZ_LOG() << "about to read " #T " using " #FUNC;\
+        T t;\
+        status_t err = FUNC(vec, &t);\
+        (void) err;\
+        FUZZ_LOG() << #T " done " /* << "err: " << err*/;\
+    }
+
+
+// clang-format off
+std::vector<GrallocTypesDecode> GRALLOCTYPES_DECODE_FUNCTIONS {
+    GRALLOCTYPES_DECODE(uint64_t, ::android::gralloc4::decodeBufferId),
+    GRALLOCTYPES_DECODE(std::string, ::android::gralloc4::decodeName),
+    GRALLOCTYPES_DECODE(uint64_t, ::android::gralloc4::decodeWidth),
+    GRALLOCTYPES_DECODE(uint64_t, ::android::gralloc4::decodeHeight),
+    GRALLOCTYPES_DECODE(uint64_t, ::android::gralloc4::decodeLayerCount),
+    GRALLOCTYPES_DECODE(::android::hardware::graphics::common::V1_2::PixelFormat, ::android::gralloc4::decodePixelFormatRequested),
+    GRALLOCTYPES_DECODE(uint32_t, ::android::gralloc4::decodePixelFormatFourCC),
+    GRALLOCTYPES_DECODE(uint64_t, ::android::gralloc4::decodePixelFormatModifier),
+    GRALLOCTYPES_DECODE(uint64_t, ::android::gralloc4::decodeUsage),
+    GRALLOCTYPES_DECODE(uint64_t, ::android::gralloc4::decodeAllocationSize),
+    GRALLOCTYPES_DECODE(uint64_t, ::android::gralloc4::decodeProtectedContent),
+    GRALLOCTYPES_DECODE(aidl::android::hardware::graphics::common::ExtendableType, ::android::gralloc4::decodeCompression),
+    GRALLOCTYPES_DECODE(aidl::android::hardware::graphics::common::ExtendableType, ::android::gralloc4::decodeInterlaced),
+    GRALLOCTYPES_DECODE(aidl::android::hardware::graphics::common::ExtendableType, ::android::gralloc4::decodeChromaSiting),
+    GRALLOCTYPES_DECODE(std::vector<aidl::android::hardware::graphics::common::PlaneLayout>, ::android::gralloc4::decodePlaneLayouts),
+    GRALLOCTYPES_DECODE(aidl::android::hardware::graphics::common::Dataspace, ::android::gralloc4::decodeDataspace),
+    GRALLOCTYPES_DECODE(aidl::android::hardware::graphics::common::BlendMode, ::android::gralloc4::decodeBlendMode),
+};
+// clang-format on
diff --git a/libs/gralloc/types/fuzzer/gralloctypes.h b/libs/gralloc/types/fuzzer/gralloctypes.h
new file mode 100644
index 0000000..b995fce
--- /dev/null
+++ b/libs/gralloc/types/fuzzer/gralloctypes.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <gralloctypes/Gralloc4.h>
+#include <hidl/HidlSupport.h>
+
+#include <vector>
+
+using GrallocTypesDecode = std::function<void(const ::android::hardware::hidl_vec<uint8_t>& vec, uint8_t data)>;
+
+extern std::vector<GrallocTypesDecode> GRALLOCTYPES_DECODE_FUNCTIONS;
diff --git a/libs/gralloc/types/fuzzer/main.cpp b/libs/gralloc/types/fuzzer/main.cpp
new file mode 100644
index 0000000..2807878
--- /dev/null
+++ b/libs/gralloc/types/fuzzer/main.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define FUZZ_LOG_TAG "main"
+
+#include "gralloctypes.h"
+#include "util.h"
+
+#include <android-base/logging.h>
+#include <log/log.h>
+
+#include <cstdlib>
+#include <ctime>
+
+void doFuzz(
+        const std::vector<GrallocTypesDecode>& decodes,
+        const std::vector<uint8_t>& input,
+        const std::vector<uint8_t>& instructions) {
+
+    ::android::hardware::hidl_vec<uint8_t> vec;
+    vec.setToExternal(const_cast<uint8_t*>(input.data()), input.size(), false /*shouldOwn*/);
+
+    // since we are only using a byte to index
+    CHECK(decodes.size() <= 255) << decodes.size();
+
+    for (size_t i = 0; i < instructions.size() - 1; i += 2) {
+        uint8_t a = instructions[i];
+        uint8_t decodeIdx = a % decodes.size();
+
+        uint8_t b = instructions[i + 1];
+
+        FUZZ_LOG() << "Instruction: " << (i / 2) + 1 << "/" << instructions.size() / 2
+                   << " cmd: " << static_cast<size_t>(a) << " (" << static_cast<size_t>(decodeIdx)
+                   << ") arg: " << static_cast<size_t>(b) << " size: " << vec.size();
+
+        decodes[decodeIdx](vec, b);
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    if (size <= 1) return 0;  // no use
+
+    // data to fill out parcel
+    size_t inputLen = size / 2;
+    std::vector<uint8_t> input(data, data + inputLen);
+    data += inputLen;
+    size -= inputLen;
+
+    // data to use to determine what to do
+    size_t instructionLen = size;
+    std::vector<uint8_t> instructions(data, data + instructionLen);
+    data += instructionLen;
+    size -= instructionLen;
+
+    CHECK(size == 0) << "size: " << size;
+
+    FUZZ_LOG() << "inputLen: " << inputLen << " instructionLen: " << instructionLen;
+    FUZZ_LOG() << "input: " << hexString(input);
+    FUZZ_LOG() << "instructions: " << hexString(instructions);
+
+    doFuzz(GRALLOCTYPES_DECODE_FUNCTIONS, input, instructions);
+    return 0;
+}
diff --git a/libs/gralloc/types/fuzzer/util.cpp b/libs/gralloc/types/fuzzer/util.cpp
new file mode 100644
index 0000000..479f406
--- /dev/null
+++ b/libs/gralloc/types/fuzzer/util.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define FUZZ_LOG_TAG "util"
+#include "util.h"
+
+#include <android-base/logging.h>
+
+#include <iomanip>
+#include <sstream>
+
+std::string hexString(const void* bytes, size_t len) {
+    if (bytes == nullptr) return "<null>";
+
+    const uint8_t* bytes8 = static_cast<const uint8_t*>(bytes);
+    char chars[] = "0123456789abcdef";
+    std::string result;
+    result.resize(len * 2);
+
+    for (size_t i = 0; i < len; i++) {
+        result[2 * i] = chars[bytes8[i] >> 4];
+        result[2 * i + 1] = chars[bytes8[i] & 0xf];
+    }
+
+    return result;
+}
+std::string hexString(const std::vector<uint8_t>& bytes) {
+    return hexString(bytes.data(), bytes.size());
+}
diff --git a/libs/gralloc/types/fuzzer/util.h b/libs/gralloc/types/fuzzer/util.h
new file mode 100644
index 0000000..aa504d2
--- /dev/null
+++ b/libs/gralloc/types/fuzzer/util.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#ifndef FUZZ_LOG_TAG
+#error "Must define FUZZ_LOG_TAG"
+#endif
+
+#define FUZZ_LOG() FuzzLog(FUZZ_LOG_TAG).log()
+
+#ifdef ENABLE_LOG_FUZZ
+class FuzzLog {
+public:
+    FuzzLog(const char* tag) : mTag(tag) {}
+    ~FuzzLog() { std::cout << mTag << ": " << mOs.str() << std::endl; }
+
+    std::stringstream& log() { return mOs; }
+
+private:
+    const char* mTag = nullptr;
+    std::stringstream mOs;
+};
+#else
+class FuzzLog {
+public:
+    FuzzLog(const char* /*tag*/) {}
+    template <typename T>
+    FuzzLog& operator<<(const T& /*t*/) {
+        return *this;
+    }
+    FuzzLog& log() { return *this; }
+};
+#endif
+
+std::string hexString(const void* bytes, size_t len);
+std::string hexString(const std::vector<uint8_t>& bytes);
diff --git a/libs/gralloc/types/include/gralloctypes/Gralloc4.h b/libs/gralloc/types/include/gralloctypes/Gralloc4.h
new file mode 100644
index 0000000..a5d3bb2
--- /dev/null
+++ b/libs/gralloc/types/include/gralloctypes/Gralloc4.h
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/graphics/common/BlendMode.h>
+#include <aidl/android/hardware/graphics/common/Dataspace.h>
+#include <aidl/android/hardware/graphics/common/ExtendableType.h>
+#include <aidl/android/hardware/graphics/common/Interlaced.h>
+#include <aidl/android/hardware/graphics/common/PlaneLayout.h>
+#include <aidl/android/hardware/graphics/common/ChromaSiting.h>
+#include <aidl/android/hardware/graphics/common/Compression.h>
+#include <aidl/android/hardware/graphics/common/Interlaced.h>
+#include <aidl/android/hardware/graphics/common/StandardMetadataType.h>
+#include <aidl/android/hardware/graphics/common/PlaneLayoutComponentType.h>
+
+#include <android/hardware/graphics/mapper/4.0/IMapper.h>
+
+namespace android {
+
+namespace gralloc4 {
+
+#define GRALLOC4_STANDARD_METADATA_TYPE "android.hardware.graphics.common.StandardMetadataType"
+#define GRALLOC4_STANDARD_CHROMA_SITING "android.hardware.graphics.common.ChromaSiting"
+#define GRALLOC4_STANDARD_COMPRESSION "android.hardware.graphics.common.Compression"
+#define GRALLOC4_STANDARD_INTERLACED "android.hardware.graphics.common.Interlaced"
+#define GRALLOC4_STANDARD_PLANE_LAYOUT_COMPONENT_TYPE \
+    "android.hardware.graphics.common.PlaneLayoutComponentType"
+
+/*---------------------------------------------------------------------------------------------*/
+/**
+ * Definitions of the standard buffer metadata types. It is recommended that everyone uses
+ * these definitions directly for standard buffer metadata types.
+ */
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_BufferId = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::BUFFER_ID)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_Name = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::NAME)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_Width = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::WIDTH)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_Height = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::HEIGHT)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_LayerCount = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::LAYER_COUNT)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_PixelFormatRequested = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::PIXEL_FORMAT_REQUESTED)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_PixelFormatFourCC = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::PIXEL_FORMAT_FOURCC)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_PixelFormatModifier = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::PIXEL_FORMAT_MODIFIER)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_Usage = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::USAGE)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_AllocationSize = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::ALLOCATION_SIZE)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_ProtectedContent = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::PROTECTED_CONTENT)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_Compression = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::COMPRESSION)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_Interlaced = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::INTERLACED)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_ChromaSiting = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::CHROMA_SITING)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_PlaneLayouts = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::PLANE_LAYOUTS)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_Dataspace = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::DATASPACE)
+};
+
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType MetadataType_BlendMode = {
+        GRALLOC4_STANDARD_METADATA_TYPE, static_cast<int64_t>(aidl::android::hardware::graphics::common::StandardMetadataType::BLEND_MODE)
+};
+
+/*---------------------------------------------------------------------------------------------*/
+
+/**
+ * Definitions of the standard compression strategies. It is recommended that everyone uses
+ * these definitions directly for standard compression strategies.
+ */
+static const aidl::android::hardware::graphics::common::ExtendableType Compression_None =
+        {GRALLOC4_STANDARD_COMPRESSION,
+         static_cast<int64_t>(aidl::android::hardware::graphics::common::Compression::NONE)};
+
+static const aidl::android::hardware::graphics::common::ExtendableType
+        Compression_DisplayStreamCompression =
+                {GRALLOC4_STANDARD_COMPRESSION,
+                 static_cast<int64_t>(aidl::android::hardware::graphics::common::Compression::
+                                              DISPLAY_STREAM_COMPRESSION)};
+
+/*---------------------------------------------------------------------------------------------*/
+
+/**
+ * Definitions of the standard interlaced strategies. It is recommended that everyone uses
+ * these definitions directly for standard interlaced strategies.
+ */
+static const aidl::android::hardware::graphics::common::ExtendableType Interlaced_None =
+        {GRALLOC4_STANDARD_INTERLACED,
+         static_cast<int64_t>(aidl::android::hardware::graphics::common::Interlaced::NONE)};
+
+static const aidl::android::hardware::graphics::common::ExtendableType Interlaced_TopBottom =
+        {GRALLOC4_STANDARD_INTERLACED,
+         static_cast<int64_t>(aidl::android::hardware::graphics::common::Interlaced::TOP_BOTTOM)};
+
+static const aidl::android::hardware::graphics::common::ExtendableType Interlaced_RightLeft =
+        {GRALLOC4_STANDARD_INTERLACED,
+         static_cast<int64_t>(aidl::android::hardware::graphics::common::Interlaced::RIGHT_LEFT)};
+
+/*---------------------------------------------------------------------------------------------*/
+
+/**
+ * Definitions of the standard chroma siting. It is recommended that everyone uses
+ * these definitions directly for standard chroma siting.
+ */
+static const aidl::android::hardware::graphics::common::ExtendableType ChromaSiting_None =
+        {GRALLOC4_STANDARD_CHROMA_SITING,
+         static_cast<int64_t>(aidl::android::hardware::graphics::common::ChromaSiting::NONE)};
+
+static const aidl::android::hardware::graphics::common::ExtendableType ChromaSiting_Unknown =
+        {GRALLOC4_STANDARD_CHROMA_SITING,
+         static_cast<int64_t>(aidl::android::hardware::graphics::common::ChromaSiting::UNKNOWN)};
+
+static const aidl::android::hardware::graphics::common::ExtendableType
+        ChromaSiting_SitedInterstitial = {GRALLOC4_STANDARD_CHROMA_SITING,
+                                          static_cast<int64_t>(
+                                                  aidl::android::hardware::graphics::common::
+                                                          ChromaSiting::SITED_INTERSTITIAL)};
+
+static const aidl::android::hardware::graphics::common::ExtendableType
+        ChromaSiting_CositedHorizontal = {GRALLOC4_STANDARD_CHROMA_SITING,
+                                          static_cast<int64_t>(
+                                                  aidl::android::hardware::graphics::common::
+                                                          ChromaSiting::COSITED_HORIZONTAL)};
+
+/*---------------------------------------------------------------------------------------------*/
+
+/**
+ * Definitions of the standard plane layout component types. It is recommended that everyone uses
+ * these definitions directly for standard plane layout component types
+ */
+static const aidl::android::hardware::graphics::common::ExtendableType PlaneLayoutComponentType_Y =
+        {GRALLOC4_STANDARD_PLANE_LAYOUT_COMPONENT_TYPE,
+         static_cast<int64_t>(
+                 aidl::android::hardware::graphics::common::PlaneLayoutComponentType::Y)};
+
+static const aidl::android::hardware::graphics::common::ExtendableType PlaneLayoutComponentType_CB =
+        {GRALLOC4_STANDARD_PLANE_LAYOUT_COMPONENT_TYPE,
+         static_cast<int64_t>(
+                 aidl::android::hardware::graphics::common::PlaneLayoutComponentType::CB)};
+
+static const aidl::android::hardware::graphics::common::ExtendableType PlaneLayoutComponentType_CR =
+        {GRALLOC4_STANDARD_PLANE_LAYOUT_COMPONENT_TYPE,
+         static_cast<int64_t>(
+                 aidl::android::hardware::graphics::common::PlaneLayoutComponentType::CR)};
+
+static const aidl::android::hardware::graphics::common::ExtendableType PlaneLayoutComponentType_R =
+        {GRALLOC4_STANDARD_PLANE_LAYOUT_COMPONENT_TYPE,
+         static_cast<int64_t>(
+                 aidl::android::hardware::graphics::common::PlaneLayoutComponentType::R)};
+
+static const aidl::android::hardware::graphics::common::ExtendableType PlaneLayoutComponentType_G =
+        {GRALLOC4_STANDARD_PLANE_LAYOUT_COMPONENT_TYPE,
+         static_cast<int64_t>(
+                 aidl::android::hardware::graphics::common::PlaneLayoutComponentType::G)};
+
+static const aidl::android::hardware::graphics::common::ExtendableType PlaneLayoutComponentType_B =
+        {GRALLOC4_STANDARD_PLANE_LAYOUT_COMPONENT_TYPE,
+         static_cast<int64_t>(
+                 aidl::android::hardware::graphics::common::PlaneLayoutComponentType::B)};
+
+static const aidl::android::hardware::graphics::common::ExtendableType PlaneLayoutComponentType_A =
+        {GRALLOC4_STANDARD_PLANE_LAYOUT_COMPONENT_TYPE,
+         static_cast<int64_t>(
+                 aidl::android::hardware::graphics::common::PlaneLayoutComponentType::A)};
+
+/*---------------------------------------------------------------------------------------------*/
+
+/**
+ * The functions below encode and decode standard metadata into a byte stream. It is STRONGLY
+ * recommended that both the vendor and system partitions use these functions when getting
+ * and setting metadata through gralloc 4 (IMapper 4.0).
+ */
+status_t encodeBufferId(uint64_t bufferId, android::hardware::hidl_vec<uint8_t>* outBufferId);
+status_t decodeBufferId(const android::hardware::hidl_vec<uint8_t>& bufferId, uint64_t* outBufferId);
+
+status_t encodeName(const std::string& name, android::hardware::hidl_vec<uint8_t>* outName);
+status_t decodeName(const android::hardware::hidl_vec<uint8_t>& name, std::string* outName);
+
+status_t encodeWidth(uint64_t width, android::hardware::hidl_vec<uint8_t>* outWidth);
+status_t decodeWidth(const android::hardware::hidl_vec<uint8_t>& width, uint64_t* outWidth);
+
+status_t encodeHeight(uint64_t height, android::hardware::hidl_vec<uint8_t>* outHeight);
+status_t decodeHeight(const android::hardware::hidl_vec<uint8_t>& height, uint64_t* outHeight);
+
+status_t encodeLayerCount(uint64_t layerCount, android::hardware::hidl_vec<uint8_t>* outLayerCount);
+status_t decodeLayerCount(const android::hardware::hidl_vec<uint8_t>& layerCount, uint64_t* outLayerCount);
+
+status_t encodePixelFormatRequested(const hardware::graphics::common::V1_2::PixelFormat& pixelFormatRequested, android::hardware::hidl_vec<uint8_t>* outPixelFormatRequested);
+status_t decodePixelFormatRequested(const android::hardware::hidl_vec<uint8_t>& pixelFormatRequested, hardware::graphics::common::V1_2::PixelFormat* outPixelFormatRequested);
+
+status_t encodePixelFormatFourCC(uint32_t pixelFormatFourCC, android::hardware::hidl_vec<uint8_t>* outPixelFormatFourCC);
+status_t decodePixelFormatFourCC(const android::hardware::hidl_vec<uint8_t>& pixelFormatFourCC, uint32_t* outPixelFormatFourCC);
+
+status_t encodePixelFormatModifier(uint64_t pixelFormatModifier, android::hardware::hidl_vec<uint8_t>* outPixelFormatModifier);
+status_t decodePixelFormatModifier(const android::hardware::hidl_vec<uint8_t>& pixelFormatModifier, uint64_t* outPixelFormatModifier);
+
+status_t encodeUsage(uint64_t usage, android::hardware::hidl_vec<uint8_t>* outUsage);
+status_t decodeUsage(const android::hardware::hidl_vec<uint8_t>& usage, uint64_t* outUsage);
+
+status_t encodeAllocationSize(uint64_t allocationSize, android::hardware::hidl_vec<uint8_t>* outAllocationSize);
+status_t decodeAllocationSize(const android::hardware::hidl_vec<uint8_t>& allocationSize, uint64_t* outAllocationSize);
+
+status_t encodeProtectedContent(uint64_t protectedContent, android::hardware::hidl_vec<uint8_t>* outProtectedContent);
+status_t decodeProtectedContent(const android::hardware::hidl_vec<uint8_t>& protectedContent, uint64_t* outProtectedContent);
+
+status_t encodeCompression(const aidl::android::hardware::graphics::common::ExtendableType& compression, android::hardware::hidl_vec<uint8_t>* outCompression);
+status_t decodeCompression(const android::hardware::hidl_vec<uint8_t>& compression, aidl::android::hardware::graphics::common::ExtendableType* outCompression);
+
+status_t encodeInterlaced(const aidl::android::hardware::graphics::common::ExtendableType& interlaced, android::hardware::hidl_vec<uint8_t>* outInterlaced);
+status_t decodeInterlaced(const android::hardware::hidl_vec<uint8_t>& interlaced, aidl::android::hardware::graphics::common::ExtendableType* outInterlaced);
+
+status_t encodeChromaSiting(const aidl::android::hardware::graphics::common::ExtendableType& chromaSiting, android::hardware::hidl_vec<uint8_t>* outChromaSiting);
+status_t decodeChromaSiting(const android::hardware::hidl_vec<uint8_t>& chromaSiting, aidl::android::hardware::graphics::common::ExtendableType* outChromaSiting);
+
+status_t encodePlaneLayouts(const std::vector<aidl::android::hardware::graphics::common::PlaneLayout>& planeLayouts, android::hardware::hidl_vec<uint8_t>* outPlaneLayouts);
+status_t decodePlaneLayouts(const android::hardware::hidl_vec<uint8_t>& planeLayouts, std::vector<aidl::android::hardware::graphics::common::PlaneLayout>* outPlaneLayouts);
+
+status_t encodeDataspace(const aidl::android::hardware::graphics::common::Dataspace& dataspace, android::hardware::hidl_vec<uint8_t>* outDataspace);
+status_t decodeDataspace(const android::hardware::hidl_vec<uint8_t>& dataspace, aidl::android::hardware::graphics::common::Dataspace* outDataspace);
+
+status_t encodeBlendMode(const aidl::android::hardware::graphics::common::BlendMode& blendMode, android::hardware::hidl_vec<uint8_t>* outBlendMode);
+status_t decodeBlendMode(const android::hardware::hidl_vec<uint8_t>& blendMode, aidl::android::hardware::graphics::common::BlendMode* outBlendMode);
+
+/**
+ * The functions below can be used to parse extendable types.
+ */
+bool isStandardMetadataType(
+        const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType& metadataType);
+bool isStandardCompression(
+        const aidl::android::hardware::graphics::common::ExtendableType& compression);
+bool isStandardInterlaced(
+        const aidl::android::hardware::graphics::common::ExtendableType& interlaced);
+bool isStandardChromaSiting(
+        const aidl::android::hardware::graphics::common::ExtendableType& chromaSiting);
+bool isStandardPlaneLayoutComponentType(
+        const aidl::android::hardware::graphics::common::ExtendableType& planeLayoutComponentType);
+
+aidl::android::hardware::graphics::common::StandardMetadataType getStandardMetadataTypeValue(
+        const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType& metadataType);
+aidl::android::hardware::graphics::common::Compression getStandardCompressionValue(
+        const aidl::android::hardware::graphics::common::ExtendableType& compression);
+aidl::android::hardware::graphics::common::Interlaced getStandardInterlacedValue(
+        const aidl::android::hardware::graphics::common::ExtendableType& interlaced);
+aidl::android::hardware::graphics::common::ChromaSiting getStandardChromaSitingValue(
+        const aidl::android::hardware::graphics::common::ExtendableType& chromaSiting);
+aidl::android::hardware::graphics::common::PlaneLayoutComponentType
+getStandardPlaneLayoutComponentTypeValue(
+        const aidl::android::hardware::graphics::common::ExtendableType& planeLayoutComponentType);
+
+/**
+ * The functions below return string representations of ExtendableTypes
+ */
+std::string getCompressionName(
+        const aidl::android::hardware::graphics::common::ExtendableType& compression);
+std::string getInterlacedName(
+        const aidl::android::hardware::graphics::common::ExtendableType& interlaced);
+std::string getChromaSitingName(
+        const aidl::android::hardware::graphics::common::ExtendableType& chromaSiting);
+std::string getPlaneLayoutComponentTypeName(
+        const aidl::android::hardware::graphics::common::ExtendableType& planeLayoutComponentType);
+
+} // namespace gralloc4
+
+} // namespace android
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 4f605e0..1ae148c 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -50,6 +50,7 @@
         "ConsumerBase.cpp",
         "CpuConsumer.cpp",
         "DebugEGLImageTracker.cpp",
+        "DisplayEventDispatcher.cpp",
         "DisplayEventReceiver.cpp",
         "GLConsumer.cpp",
         "GuiConfig.cpp",
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 3c31d74..b9cf9e1 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
+#undef LOG_TAG
+#define LOG_TAG "BLASTBufferQueue"
+
 #include <gui/BLASTBufferQueue.h>
 #include <gui/BufferItemConsumer.h>
+#include <gui/GLConsumer.h>
 
 #include <chrono>
 
@@ -24,8 +28,14 @@
 namespace android {
 
 BLASTBufferQueue::BLASTBufferQueue(const sp<SurfaceControl>& surface, int width, int height)
-      : mSurfaceControl(surface), mWidth(width), mHeight(height) {
+      : mSurfaceControl(surface),
+        mPendingCallbacks(0),
+        mWidth(width),
+        mHeight(height),
+        mNextTransaction(nullptr) {
     BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+    mConsumer->setMaxBufferCount(MAX_BUFFERS);
+    mProducer->setMaxDequeuedBufferCount(MAX_BUFFERS - 1);
     mBufferItemConsumer =
             new BufferItemConsumer(mConsumer, AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER, 1, true);
     mBufferItemConsumer->setName(String8("BLAST Consumer"));
@@ -34,6 +44,8 @@
     mBufferItemConsumer->setDefaultBufferSize(mWidth, mHeight);
     mBufferItemConsumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888);
     mBufferItemConsumer->setTransformHint(mSurfaceControl->getTransformHint());
+
+    mAcquired = false;
 }
 
 void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, int width, int height) {
@@ -59,20 +71,32 @@
                                            const std::vector<SurfaceControlStats>& stats) {
     std::unique_lock _lock{mMutex};
 
-    if (stats.size() > 0 && mNextCallbackBufferItem.mGraphicBuffer != nullptr) {
+    if (stats.size() > 0 && !mShadowQueue.empty()) {
         mBufferItemConsumer->releaseBuffer(mNextCallbackBufferItem,
                                            stats[0].previousReleaseFence
                                                    ? stats[0].previousReleaseFence
                                                    : Fence::NO_FENCE);
+        mAcquired = false;
         mNextCallbackBufferItem = BufferItem();
         mBufferItemConsumer->setTransformHint(stats[0].transformHint);
     }
-    mDequeueWaitCV.notify_all();
+    mPendingCallbacks--;
+    processNextBufferLocked();
+    mCallbackCV.notify_all();
     decStrong((void*)transactionCallbackThunk);
 }
 
-void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
-    std::unique_lock _lock{mMutex};
+void BLASTBufferQueue::processNextBufferLocked() {
+    if (mShadowQueue.empty()) {
+        return;
+    }
+
+    if (mAcquired) {
+        return;
+    }
+
+    BufferItem item = std::move(mShadowQueue.front());
+    mShadowQueue.pop();
 
     SurfaceComposerClient::Transaction localTransaction;
     bool applyTransaction = true;
@@ -83,11 +107,11 @@
         applyTransaction = false;
     }
 
-    int status = OK;
     mNextCallbackBufferItem = mLastSubmittedBufferItem;
-
     mLastSubmittedBufferItem = BufferItem();
-    status = mBufferItemConsumer->acquireBuffer(&mLastSubmittedBufferItem, -1, false);
+
+    status_t status = mBufferItemConsumer->acquireBuffer(&mLastSubmittedBufferItem, -1, false);
+    mAcquired = true;
     if (status != OK) {
         ALOGE("Failed to acquire?");
     }
@@ -99,7 +123,6 @@
         return;
     }
 
-
     // Ensure BLASTBufferQueue stays alive until we receive the transaction complete callback.
     incStrong((void*)transactionCallbackThunk);
 
@@ -109,20 +132,32 @@
     t->addTransactionCompletedCallback(transactionCallbackThunk, static_cast<void*>(this));
 
     t->setFrame(mSurfaceControl, {0, 0, (int32_t)buffer->getWidth(), (int32_t)buffer->getHeight()});
-    t->setCrop(mSurfaceControl, {0, 0, (int32_t)buffer->getWidth(), (int32_t)buffer->getHeight()});
+    t->setCrop(mSurfaceControl, computeCrop(mLastSubmittedBufferItem));
+    t->setTransform(mSurfaceControl, mLastSubmittedBufferItem.mTransform);
 
     if (applyTransaction) {
-        ALOGE("Apply transaction");
         t->apply();
-
-        if (mNextCallbackBufferItem.mGraphicBuffer != nullptr) {
-            mDequeueWaitCV.wait_for(_lock, 5000ms);
-        }
     }
 }
 
+Rect BLASTBufferQueue::computeCrop(const BufferItem& item) {
+    if (item.mScalingMode == NATIVE_WINDOW_SCALING_MODE_SCALE_CROP) {
+        return GLConsumer::scaleDownCrop(item.mCrop, mWidth, mHeight);
+    }
+    return item.mCrop;
+}
+
+void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
+    std::lock_guard _lock{mMutex};
+
+    // add to shadow queue
+    mShadowQueue.push(item);
+    processNextBufferLocked();
+    mPendingCallbacks++;
+}
+
 void BLASTBufferQueue::setNextTransaction(SurfaceComposerClient::Transaction* t) {
-    std::unique_lock _lock{mMutex};
+    std::lock_guard _lock{mMutex};
     mNextTransaction = t;
 }
 
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
new file mode 100644
index 0000000..54f383e
--- /dev/null
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2015 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 "DisplayEventDispatcher"
+
+#include <cinttypes>
+#include <cstdint>
+
+#include <gui/DisplayEventDispatcher.h>
+#include <gui/DisplayEventReceiver.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+
+#include <utils/Timers.h>
+
+namespace android {
+
+// Number of events to read at a time from the DisplayEventDispatcher pipe.
+// The value should be large enough that we can quickly drain the pipe
+// using just a few large reads.
+static const size_t EVENT_BUFFER_SIZE = 100;
+
+DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper,
+                                               ISurfaceComposer::VsyncSource vsyncSource,
+                                               ISurfaceComposer::ConfigChanged configChanged)
+      : mLooper(looper), mReceiver(vsyncSource, configChanged), mWaitingForVsync(false) {
+    ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this);
+}
+
+status_t DisplayEventDispatcher::initialize() {
+    status_t result = mReceiver.initCheck();
+    if (result) {
+        ALOGW("Failed to initialize display event receiver, status=%d", result);
+        return result;
+    }
+
+    int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT, this, NULL);
+    if (rc < 0) {
+        return UNKNOWN_ERROR;
+    }
+    return OK;
+}
+
+void DisplayEventDispatcher::dispose() {
+    ALOGV("dispatcher %p ~ Disposing display event dispatcher.", this);
+
+    if (!mReceiver.initCheck()) {
+        mLooper->removeFd(mReceiver.getFd());
+    }
+}
+
+status_t DisplayEventDispatcher::scheduleVsync() {
+    if (!mWaitingForVsync) {
+        ALOGV("dispatcher %p ~ Scheduling vsync.", this);
+
+        // Drain all pending events.
+        nsecs_t vsyncTimestamp;
+        PhysicalDisplayId vsyncDisplayId;
+        uint32_t vsyncCount;
+        if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
+            ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "", this,
+                  ns2ms(static_cast<nsecs_t>(vsyncTimestamp)));
+        }
+
+        status_t status = mReceiver.requestNextVsync();
+        if (status) {
+            ALOGW("Failed to request next vsync, status=%d", status);
+            return status;
+        }
+
+        mWaitingForVsync = true;
+    }
+    return OK;
+}
+
+int DisplayEventDispatcher::handleEvent(int, int events, void*) {
+    if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+        ALOGE("Display event receiver pipe was closed or an error occurred.  "
+              "events=0x%x",
+              events);
+        return 0; // remove the callback
+    }
+
+    if (!(events & Looper::EVENT_INPUT)) {
+        ALOGW("Received spurious callback for unhandled poll event.  "
+              "events=0x%x",
+              events);
+        return 1; // keep the callback
+    }
+
+    // Drain all pending events, keep the last vsync.
+    nsecs_t vsyncTimestamp;
+    PhysicalDisplayId vsyncDisplayId;
+    uint32_t vsyncCount;
+    if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
+        ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64
+              ", displayId=%" ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ", count=%d",
+              this, ns2ms(vsyncTimestamp), vsyncDisplayId, vsyncCount);
+        mWaitingForVsync = false;
+        dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);
+    }
+
+    return 1; // keep the callback
+}
+
+bool DisplayEventDispatcher::processPendingEvents(nsecs_t* outTimestamp,
+                                                  PhysicalDisplayId* outDisplayId,
+                                                  uint32_t* outCount) {
+    bool gotVsync = false;
+    DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+    ssize_t n;
+    while ((n = mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+        ALOGV("dispatcher %p ~ Read %d events.", this, int(n));
+        for (ssize_t i = 0; i < n; i++) {
+            const DisplayEventReceiver::Event& ev = buf[i];
+            switch (ev.header.type) {
+                case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
+                    // Later vsync events will just overwrite the info from earlier
+                    // ones. That's fine, we only care about the most recent.
+                    gotVsync = true;
+                    *outTimestamp = ev.header.timestamp;
+                    *outDisplayId = ev.header.displayId;
+                    *outCount = ev.vsync.count;
+                    break;
+                case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
+                    dispatchHotplug(ev.header.timestamp, ev.header.displayId, ev.hotplug.connected);
+                    break;
+                case DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED:
+                    dispatchConfigChanged(ev.header.timestamp, ev.header.displayId,
+                                          ev.config.configId);
+                    break;
+                default:
+                    ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
+                    break;
+            }
+        }
+    }
+    if (n < 0) {
+        ALOGW("Failed to get events from display event dispatcher, status=%d", status_t(n));
+    }
+    return gotVsync;
+}
+} // namespace android
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 5805797..546757b 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -905,6 +905,85 @@
         return reply.readInt32();
     }
 
+    virtual status_t setDesiredDisplayConfigSpecs(const sp<IBinder>& displayToken,
+                                                  int32_t defaultModeId, float minRefreshRate,
+                                                  float maxRefreshRate) {
+        Parcel data, reply;
+        status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+        if (result != NO_ERROR) {
+            ALOGE("setDesiredDisplayConfigSpecs: failed to writeInterfaceToken: %d", result);
+            return result;
+        }
+        result = data.writeStrongBinder(displayToken);
+        if (result != NO_ERROR) {
+            ALOGE("setDesiredDisplayConfigSpecs: failed to write display token: %d", result);
+            return result;
+        }
+        result = data.writeInt32(defaultModeId);
+        if (result != NO_ERROR) {
+            ALOGE("setDesiredDisplayConfigSpecs failed to write defaultModeId: %d", result);
+            return result;
+        }
+        result = data.writeFloat(minRefreshRate);
+        if (result != NO_ERROR) {
+            ALOGE("setDesiredDisplayConfigSpecs failed to write minRefreshRate: %d", result);
+            return result;
+        }
+        result = data.writeFloat(maxRefreshRate);
+        if (result != NO_ERROR) {
+            ALOGE("setDesiredDisplayConfigSpecs failed to write maxRefreshRate: %d", result);
+            return result;
+        }
+
+        result = remote()->transact(BnSurfaceComposer::SET_DESIRED_DISPLAY_CONFIG_SPECS, data,
+                                    &reply);
+        if (result != NO_ERROR) {
+            ALOGE("setDesiredDisplayConfigSpecs failed to transact: %d", result);
+            return result;
+        }
+        return reply.readInt32();
+    }
+
+    virtual status_t getDesiredDisplayConfigSpecs(const sp<IBinder>& displayToken,
+                                                  int32_t* outDefaultModeId,
+                                                  float* outMinRefreshRate,
+                                                  float* outMaxRefreshRate) {
+        if (!outDefaultModeId || !outMinRefreshRate || !outMaxRefreshRate) return BAD_VALUE;
+        Parcel data, reply;
+        status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+        if (result != NO_ERROR) {
+            ALOGE("getDesiredDisplayConfigSpecs failed to writeInterfaceToken: %d", result);
+            return result;
+        }
+        result = data.writeStrongBinder(displayToken);
+        if (result != NO_ERROR) {
+            ALOGE("getDesiredDisplayConfigSpecs failed to writeStrongBinder: %d", result);
+            return result;
+        }
+        result = remote()->transact(BnSurfaceComposer::GET_DESIRED_DISPLAY_CONFIG_SPECS, data,
+                                    &reply);
+        if (result != NO_ERROR) {
+            ALOGE("getDesiredDisplayConfigSpecs failed to transact: %d", result);
+            return result;
+        }
+        result = reply.readInt32(outDefaultModeId);
+        if (result != NO_ERROR) {
+            ALOGE("getDesiredDisplayConfigSpecs failed to read defaultModeId: %d", result);
+            return result;
+        }
+        result = reply.readFloat(outMinRefreshRate);
+        if (result != NO_ERROR) {
+            ALOGE("getDesiredDisplayConfigSpecs failed to read minRefreshRate: %d", result);
+            return result;
+        }
+        result = reply.readFloat(outMaxRefreshRate);
+        if (result != NO_ERROR) {
+            ALOGE("getDesiredDisplayConfigSpecs failed to read maxRefreshRate: %d", result);
+            return result;
+        }
+        return reply.readInt32();
+    }
+
     virtual status_t getDisplayBrightnessSupport(const sp<IBinder>& displayToken,
                                                  bool* outSupport) const {
         Parcel data, reply;
@@ -978,6 +1057,35 @@
         }
         return NO_ERROR;
     }
+
+    virtual status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor,
+                                             float lightPosY, float lightPosZ, float lightRadius) {
+        Parcel data, reply;
+        status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+        if (error != NO_ERROR) {
+            ALOGE("setGlobalShadowSettings: failed to write interface token: %d", error);
+            return error;
+        }
+
+        std::vector<float> shadowConfig = {ambientColor.r, ambientColor.g, ambientColor.b,
+                                           ambientColor.a, spotColor.r,    spotColor.g,
+                                           spotColor.b,    spotColor.a,    lightPosY,
+                                           lightPosZ,      lightRadius};
+
+        error = data.writeFloatVector(shadowConfig);
+        if (error != NO_ERROR) {
+            ALOGE("setGlobalShadowSettings: failed to write shadowConfig: %d", error);
+            return error;
+        }
+
+        error = remote()->transact(BnSurfaceComposer::SET_GLOBAL_SHADOW_SETTINGS, data, &reply,
+                                   IBinder::FLAG_ONEWAY);
+        if (error != NO_ERROR) {
+            ALOGE("setGlobalShadowSettings: failed to transact: %d", error);
+            return error;
+        }
+        return NO_ERROR;
+    }
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -1554,6 +1662,72 @@
             reply->writeInt32(result);
             return result;
         }
+        case SET_DESIRED_DISPLAY_CONFIG_SPECS: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            sp<IBinder> displayToken = data.readStrongBinder();
+            int32_t defaultModeId;
+            status_t result = data.readInt32(&defaultModeId);
+            if (result != NO_ERROR) {
+                ALOGE("setDesiredDisplayConfigSpecs: failed to read defaultModeId: %d", result);
+                return result;
+            }
+            float minRefreshRate;
+            result = data.readFloat(&minRefreshRate);
+            if (result != NO_ERROR) {
+                ALOGE("setDesiredDisplayConfigSpecs: failed to read minRefreshRate: %d", result);
+                return result;
+            }
+            float maxRefreshRate;
+            result = data.readFloat(&maxRefreshRate);
+            if (result != NO_ERROR) {
+                ALOGE("setDesiredDisplayConfigSpecs: failed to read maxRefreshRate: %d", result);
+                return result;
+            }
+            result = setDesiredDisplayConfigSpecs(displayToken, defaultModeId, minRefreshRate,
+                                                  maxRefreshRate);
+            if (result != NO_ERROR) {
+                ALOGE("setDesiredDisplayConfigSpecs: failed to call setDesiredDisplayConfigSpecs: "
+                      "%d",
+                      result);
+                return result;
+            }
+            reply->writeInt32(result);
+            return result;
+        }
+        case GET_DESIRED_DISPLAY_CONFIG_SPECS: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            sp<IBinder> displayToken = data.readStrongBinder();
+            int32_t defaultModeId;
+            float minRefreshRate;
+            float maxRefreshRate;
+
+            status_t result = getDesiredDisplayConfigSpecs(displayToken, &defaultModeId,
+                                                           &minRefreshRate, &maxRefreshRate);
+            if (result != NO_ERROR) {
+                ALOGE("getDesiredDisplayConfigSpecs: failed to get getDesiredDisplayConfigSpecs: "
+                      "%d",
+                      result);
+                return result;
+            }
+
+            result = reply->writeInt32(defaultModeId);
+            if (result != NO_ERROR) {
+                ALOGE("getDesiredDisplayConfigSpecs: failed to write defaultModeId: %d", result);
+                return result;
+            }
+            result = reply->writeFloat(minRefreshRate);
+            if (result != NO_ERROR) {
+                ALOGE("getDesiredDisplayConfigSpecs: failed to write minRefreshRate: %d", result);
+                return result;
+            }
+            result = reply->writeFloat(maxRefreshRate);
+            if (result != NO_ERROR) {
+                ALOGE("getDesiredDisplayConfigSpecs: failed to write maxRefreshRate: %d", result);
+                return result;
+            }
+            reply->writeInt32(result);
+            return result;
+        }
         case GET_DISPLAY_BRIGHTNESS_SUPPORT: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IBinder> displayToken;
@@ -1593,6 +1767,25 @@
             }
             return notifyPowerHint(hintId);
         }
+        case SET_GLOBAL_SHADOW_SETTINGS: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+
+            std::vector<float> shadowConfig;
+            status_t error = data.readFloatVector(&shadowConfig);
+            if (error != NO_ERROR || shadowConfig.size() != 11) {
+                ALOGE("setGlobalShadowSettings: failed to read shadowConfig: %d", error);
+                return error;
+            }
+
+            half4 ambientColor = {shadowConfig[0], shadowConfig[1], shadowConfig[2],
+                                  shadowConfig[3]};
+            half4 spotColor = {shadowConfig[4], shadowConfig[5], shadowConfig[6], shadowConfig[7]};
+            float lightPosY = shadowConfig[8];
+            float lightPosZ = shadowConfig[9];
+            float lightRadius = shadowConfig[10];
+            return setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ,
+                                           lightRadius);
+        }
         default: {
             return BBinder::onTransact(code, data, reply, flags);
         }
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index e392bc5..e033f93 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -109,7 +109,7 @@
             return err;
         }
     }
-
+    output.writeFloat(shadowRadius);
     return NO_ERROR;
 }
 
@@ -187,6 +187,7 @@
         input.readInt64Vector(&callbackIds);
         listeners.emplace_back(listener, callbackIds);
     }
+    shadowRadius = input.readFloat();
     return NO_ERROR;
 }
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index a538e14..9d7d7d0 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -189,48 +189,49 @@
 }
 
 void TransactionCompletedListener::onTransactionCompleted(ListenerStats listenerStats) {
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::unordered_map<CallbackId, CallbackTranslation> callbacksMap;
+    {
+        std::lock_guard<std::mutex> lock(mMutex);
 
-    /* This listener knows all the sp<IBinder> to sp<SurfaceControl> for all its registered
-     * callbackIds, except for when Transactions are merged together. This probably cannot be
-     * solved before this point because the Transactions could be merged together and applied in a
-     * different process.
-     *
-     * Fortunately, we get all the callbacks for this listener for the same frame together at the
-     * same time. This means if any Transactions were merged together, we will get their callbacks
-     * at the same time. We can combine all the sp<IBinder> to sp<SurfaceControl> maps for all the
-     * callbackIds to generate one super map that contains all the sp<IBinder> to sp<SurfaceControl>
-     * that could possibly exist for the callbacks.
-     */
-    std::unordered_map<sp<IBinder>, sp<SurfaceControl>, SurfaceComposerClient::IBinderHash>
-            surfaceControls;
-    for (const auto& transactionStats : listenerStats.transactionStats) {
-        for (auto callbackId : transactionStats.callbackIds) {
-            auto& [callbackFunction, callbackSurfaceControls] = mCallbacks[callbackId];
-            surfaceControls.insert(callbackSurfaceControls.begin(), callbackSurfaceControls.end());
+        /* This listener knows all the sp<IBinder> to sp<SurfaceControl> for all its registered
+         * callbackIds, except for when Transactions are merged together. This probably cannot be
+         * solved before this point because the Transactions could be merged together and applied in
+         * a different process.
+         *
+         * Fortunately, we get all the callbacks for this listener for the same frame together at
+         * the same time. This means if any Transactions were merged together, we will get their
+         * callbacks at the same time. We can combine all the sp<IBinder> to sp<SurfaceControl> maps
+         * for all the callbackIds to generate one super map that contains all the sp<IBinder> to
+         * sp<SurfaceControl> that could possibly exist for the callbacks.
+         */
+        callbacksMap = mCallbacks;
+        for (const auto& transactionStats : listenerStats.transactionStats) {
+            for (auto& callbackId : transactionStats.callbackIds) {
+                mCallbacks.erase(callbackId);
+            }
         }
     }
-
     for (const auto& transactionStats : listenerStats.transactionStats) {
         for (auto callbackId : transactionStats.callbackIds) {
-            auto& [callbackFunction, callbackSurfaceControls] = mCallbacks[callbackId];
+            auto& [callbackFunction, callbackSurfaceControls] = callbacksMap[callbackId];
             if (!callbackFunction) {
                 ALOGE("cannot call null callback function, skipping");
                 continue;
             }
             std::vector<SurfaceControlStats> surfaceControlStats;
             for (const auto& surfaceStats : transactionStats.surfaceStats) {
-                surfaceControlStats.emplace_back(surfaceControls[surfaceStats.surfaceControl],
-                                                 surfaceStats.acquireTime,
-                                                 surfaceStats.previousReleaseFence,
-                                                 surfaceStats.transformHint);
-                surfaceControls[surfaceStats.surfaceControl]->setTransformHint(
-                        surfaceStats.transformHint);
+                surfaceControlStats
+                        .emplace_back(callbacksMap[callbackId]
+                                              .surfaceControls[surfaceStats.surfaceControl],
+                                      surfaceStats.acquireTime, surfaceStats.previousReleaseFence,
+                                      surfaceStats.transformHint);
+                callbacksMap[callbackId]
+                        .surfaceControls[surfaceStats.surfaceControl]
+                        ->setTransformHint(surfaceStats.transformHint);
             }
 
             callbackFunction(transactionStats.latchTime, transactionStats.presentFence,
                              surfaceControlStats);
-            mCallbacks.erase(callbackId);
         }
     }
 }
@@ -1616,6 +1617,26 @@
                                                                            outAllowedConfigs);
 }
 
+status_t SurfaceComposerClient::setDesiredDisplayConfigSpecs(const sp<IBinder>& displayToken,
+                                                             int32_t defaultModeId,
+                                                             float minRefreshRate,
+                                                             float maxRefreshRate) {
+    return ComposerService::getComposerService()->setDesiredDisplayConfigSpecs(displayToken,
+                                                                               defaultModeId,
+                                                                               minRefreshRate,
+                                                                               maxRefreshRate);
+}
+
+status_t SurfaceComposerClient::getDesiredDisplayConfigSpecs(const sp<IBinder>& displayToken,
+                                                             int32_t* outDefaultModeId,
+                                                             float* outMinRefreshRate,
+                                                             float* outMaxRefreshRate) {
+    return ComposerService::getComposerService()->getDesiredDisplayConfigSpecs(displayToken,
+                                                                               outDefaultModeId,
+                                                                               outMinRefreshRate,
+                                                                               outMaxRefreshRate);
+}
+
 status_t SurfaceComposerClient::getDisplayColorModes(const sp<IBinder>& display,
         Vector<ColorMode>* outColorModes) {
     return ComposerService::getComposerService()->getDisplayColorModes(display, outColorModes);
@@ -1726,6 +1747,14 @@
     return ComposerService::getComposerService()->notifyPowerHint(hintId);
 }
 
+status_t SurfaceComposerClient::setGlobalShadowSettings(const half4& ambientColor,
+                                                        const half4& spotColor, float lightPosY,
+                                                        float lightPosZ, float lightRadius) {
+    return ComposerService::getComposerService()->setGlobalShadowSettings(ambientColor, spotColor,
+                                                                          lightPosY, lightPosZ,
+                                                                          lightRadius);
+}
+
 // ----------------------------------------------------------------------------
 
 status_t ScreenshotClient::capture(const sp<IBinder>& display, const ui::Dataspace reqDataSpace,
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 6320556..dd0b470 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -27,6 +27,7 @@
 #include <utils/RefBase.h>
 
 #include <system/window.h>
+#include <thread>
 
 namespace android {
 
@@ -50,7 +51,6 @@
     void setNextTransaction(SurfaceComposerClient::Transaction *t);
 
     void update(const sp<SurfaceControl>& surface, int width, int height);
-    
 
     virtual ~BLASTBufferQueue() = default;
 
@@ -61,32 +61,36 @@
     BLASTBufferQueue& operator = (const BLASTBufferQueue& rhs);
     BLASTBufferQueue(const BLASTBufferQueue& rhs);
 
-    sp<SurfaceControl> mSurfaceControl;
-    
-    mutable std::mutex mMutex;
+    void processNextBufferLocked() REQUIRES(mMutex);
+    Rect computeCrop(const BufferItem& item);
 
-    static const int MAX_BUFFERS = 2;
+    sp<SurfaceControl> mSurfaceControl;
+
+    std::mutex mMutex;
+    std::condition_variable mCallbackCV;
+    uint64_t mPendingCallbacks GUARDED_BY(mMutex);
+
+    static const int MAX_BUFFERS = 3;
     struct BufferInfo {
         sp<GraphicBuffer> buffer;
         int fence;
     };
-    
-    int mDequeuedBuffers = 0;
 
-    int mWidth;
-    int mHeight;
+    std::queue<const BufferItem> mShadowQueue GUARDED_BY(mMutex);
+    bool mAcquired GUARDED_BY(mMutex);
 
-    BufferItem mLastSubmittedBufferItem;
-    BufferItem mNextCallbackBufferItem;
-    sp<Fence> mLastFence;
+    int mWidth GUARDED_BY(mMutex);
+    int mHeight GUARDED_BY(mMutex);
 
-    std::condition_variable mDequeueWaitCV;
+    BufferItem mLastSubmittedBufferItem GUARDED_BY(mMutex);
+    BufferItem mNextCallbackBufferItem GUARDED_BY(mMutex);
+    sp<Fence> mLastFence GUARDED_BY(mMutex);
 
     sp<IGraphicBufferConsumer> mConsumer;
     sp<IGraphicBufferProducer> mProducer;
     sp<BufferItemConsumer> mBufferItemConsumer;
 
-    SurfaceComposerClient::Transaction* mNextTransaction = nullptr;
+    SurfaceComposerClient::Transaction* mNextTransaction GUARDED_BY(mMutex);
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
new file mode 100644
index 0000000..f0b7ff5
--- /dev/null
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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 <gui/DisplayEventReceiver.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+
+namespace android {
+
+class DisplayEventDispatcher : public LooperCallback {
+public:
+    explicit DisplayEventDispatcher(
+            const sp<Looper>& looper,
+            ISurfaceComposer::VsyncSource vsyncSource = ISurfaceComposer::eVsyncSourceApp,
+            ISurfaceComposer::ConfigChanged configChanged =
+                    ISurfaceComposer::eConfigChangedSuppress);
+
+    status_t initialize();
+    void dispose();
+    status_t scheduleVsync();
+
+protected:
+    virtual ~DisplayEventDispatcher() = default;
+
+private:
+    sp<Looper> mLooper;
+    DisplayEventReceiver mReceiver;
+    bool mWaitingForVsync;
+
+    virtual void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count) = 0;
+    virtual void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId,
+                                 bool connected) = 0;
+    virtual void dispatchConfigChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
+                                       int32_t configId) = 0;
+
+    virtual int handleEvent(int receiveFd, int events, void* data);
+    bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId,
+                              uint32_t* outCount);
+};
+} // namespace android
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index abe1e3f..25ce1ca 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -286,36 +286,6 @@
     virtual status_t attachBuffer(int* outSlot,
             const sp<GraphicBuffer>& buffer) = 0;
 
-    // queueBuffer indicates that the client has finished filling in the
-    // contents of the buffer associated with slot and transfers ownership of
-    // that slot back to the server.
-    //
-    // It is not valid to call queueBuffer on a slot that is not owned
-    // by the client or one for which a buffer associated via requestBuffer
-    // (an attempt to do so will fail with a return value of BAD_VALUE).
-    //
-    // In addition, the input must be described by the client (as documented
-    // below). Any other properties (zero point, etc)
-    // are client-dependent, and should be documented by the client.
-    //
-    // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
-    //
-    // Upon success, the output will be filled with meaningful values
-    // (refer to the documentation below).
-    //
-    // Return of a value other than NO_ERROR means an error has occurred:
-    // * NO_INIT - the buffer queue has been abandoned or the producer is not
-    //             connected.
-    // * BAD_VALUE - one of the below conditions occurred:
-    //              * fence was NULL
-    //              * scaling mode was unknown
-    //              * both in async mode and buffer count was less than the
-    //                max numbers of buffers that can be allocated at once
-    //              * slot index was out of range (see above).
-    //              * the slot was not in the dequeued state
-    //              * the slot was enqueued without requesting a buffer
-    //              * crop rect is out of bounds of the buffer dimensions
-
     struct QueueBufferInput : public Flattenable<QueueBufferInput> {
         friend class Flattenable<QueueBufferInput>;
         explicit inline QueueBufferInput(const Parcel& parcel);
@@ -415,6 +385,35 @@
         int maxBufferCount{0};
     };
 
+    // queueBuffer indicates that the client has finished filling in the
+    // contents of the buffer associated with slot and transfers ownership of
+    // that slot back to the server.
+    //
+    // It is not valid to call queueBuffer on a slot that is not owned
+    // by the client or one for which a buffer associated via requestBuffer
+    // (an attempt to do so will fail with a return value of BAD_VALUE).
+    //
+    // In addition, the input must be described by the client (as documented
+    // below). Any other properties (zero point, etc)
+    // are client-dependent, and should be documented by the client.
+    //
+    // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
+    //
+    // Upon success, the output will be filled with meaningful values
+    // (refer to the documentation below).
+    //
+    // Return of a value other than NO_ERROR means an error has occurred:
+    // * NO_INIT - the buffer queue has been abandoned or the producer is not
+    //             connected.
+    // * BAD_VALUE - one of the below conditions occurred:
+    //              * fence was NULL
+    //              * scaling mode was unknown
+    //              * both in async mode and buffer count was less than the
+    //                max numbers of buffers that can be allocated at once
+    //              * slot index was out of range (see above).
+    //              * the slot was not in the dequeued state
+    //              * the slot was enqueued without requesting a buffer
+    //              * crop rect is out of bounds of the buffer dimensions
     virtual status_t queueBuffer(int slot, const QueueBufferInput& input,
             QueueBufferOutput* output) = 0;
 
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index f2bae98..345425d 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -25,6 +25,8 @@
 
 #include <gui/ITransactionCompletedListener.h>
 
+#include <math/vec4.h>
+
 #include <ui/ConfigStoreTypes.h>
 #include <ui/DisplayedFrameStats.h>
 #include <ui/FrameStats.h>
@@ -399,6 +401,19 @@
     virtual status_t getAllowedDisplayConfigs(const sp<IBinder>& displayToken,
                                               std::vector<int32_t>* outAllowedConfigs) = 0;
     /*
+     * Sets the refresh rate boundaries for display configuration.
+     * For all other parameters, default configuration is used. The index for the default is
+     * corresponding to the configs returned from getDisplayConfigs().
+     */
+    virtual status_t setDesiredDisplayConfigSpecs(const sp<IBinder>& displayToken,
+                                                  int32_t defaultModeId, float minRefreshRate,
+                                                  float maxRefreshRate) = 0;
+
+    virtual status_t getDesiredDisplayConfigSpecs(const sp<IBinder>& displayToken,
+                                                  int32_t* outDefaultModeId,
+                                                  float* outMinRefreshRate,
+                                                  float* outMaxRefreshRate) = 0;
+    /*
      * Gets whether brightness operations are supported on a display.
      *
      * displayToken
@@ -439,6 +454,28 @@
      * Returns NO_ERROR upon success.
      */
     virtual status_t notifyPowerHint(int32_t hintId) = 0;
+
+    /*
+     * Sets the global configuration for all the shadows drawn by SurfaceFlinger. Shadow follows
+     * material design guidelines.
+     *
+     * ambientColor
+     *      Color to the ambient shadow. The alpha is premultiplied.
+     *
+     * spotColor
+     *      Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow
+     *      depends on the light position.
+     *
+     * lightPosY/lightPosZ
+     *      Position of the light used to cast the spot shadow. The X value is always the display
+     *      width / 2.
+     *
+     * lightRadius
+     *      Radius of the light casting the shadow.
+     */
+    virtual status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor,
+                                             float lightPosY, float lightPosZ,
+                                             float lightRadius) = 0;
 };
 
 // ----------------------------------------------------------------------------
@@ -488,10 +525,13 @@
         REMOVE_REGION_SAMPLING_LISTENER,
         SET_ALLOWED_DISPLAY_CONFIGS,
         GET_ALLOWED_DISPLAY_CONFIGS,
+        SET_DESIRED_DISPLAY_CONFIG_SPECS,
+        GET_DESIRED_DISPLAY_CONFIG_SPECS,
         GET_DISPLAY_BRIGHTNESS_SUPPORT,
         SET_DISPLAY_BRIGHTNESS,
         CAPTURE_SCREEN_BY_ID,
         NOTIFY_POWER_HINT,
+        SET_GLOBAL_SHADOW_SETTINGS,
         // Always append new enum to the end.
     };
 
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 37387ac..2c0b143 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -129,6 +129,22 @@
     static status_t getAllowedDisplayConfigs(const sp<IBinder>& displayToken,
                                              std::vector<int32_t>* outAllowedConfigs);
 
+    // Sets the refresh rate boundaries for display configuration.
+    // For all other parameters, default configuration is used. The index for the default is
+    // corresponting to the configs returned from getDisplayConfigs().
+    static status_t setDesiredDisplayConfigSpecs(const sp<IBinder>& displayToken,
+                                                 int32_t defaultModeId, float minRefreshRate,
+                                                 float maxRefreshRate);
+    // Gets the refresh rate boundaries for display configuration.
+    // For all other parameters, default configuration is used. The index for the default is
+    // corresponting to the configs returned from getDisplayConfigs().
+    // The reason is passed in for telemetry tracking, and it corresponds to the list of all
+    // the policy rules that were used.
+    static status_t getDesiredDisplayConfigSpecs(const sp<IBinder>& displayToken,
+                                                 int32_t* outDefaultModeId,
+                                                 float* outMinRefreshRate,
+                                                 float* outMaxRefreshRate);
+
     // Gets the list of supported color modes for the given display
     static status_t getDisplayColorModes(const sp<IBinder>& display,
             Vector<ui::ColorMode>* outColorModes);
@@ -214,6 +230,27 @@
      */
     static status_t notifyPowerHint(int32_t hintId);
 
+    /*
+     * Sets the global configuration for all the shadows drawn by SurfaceFlinger. Shadow follows
+     * material design guidelines.
+     *
+     * ambientColor
+     *      Color to the ambient shadow. The alpha is premultiplied.
+     *
+     * spotColor
+     *      Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow
+     *      depends on the light position.
+     *
+     * lightPosY/lightPosZ
+     *      Position of the light used to cast the spot shadow. The X value is always the display
+     *      width / 2.
+     *
+     * lightRadius
+     *      Radius of the light casting the shadow.
+     */
+    static status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor,
+                                            float lightPosY, float lightPosZ, float lightRadius);
+
     // ------------------------------------------------------------------------
     // surface creation / destruction
 
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index ff22913..48a5cb6 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -19,6 +19,8 @@
 #include <gui/BLASTBufferQueue.h>
 
 #include <android/hardware/graphics/common/1.2/types.h>
+#include <gui/BufferQueueCore.h>
+#include <gui/BufferQueueProducer.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/IProducerListener.h>
 #include <gui/SurfaceComposerClient.h>
@@ -65,9 +67,11 @@
         return mBlastBufferQueueAdapter->mSurfaceControl;
     }
 
-    void waitForCallback() {
+    void waitForCallbacks() {
         std::unique_lock lock{mBlastBufferQueueAdapter->mMutex};
-        mBlastBufferQueueAdapter->mDequeueWaitCV.wait_for(lock, 1s);
+        while (mBlastBufferQueueAdapter->mPendingCallbacks > 0) {
+            mBlastBufferQueueAdapter->mCallbackCV.wait(lock);
+        }
     }
 
 private:
@@ -116,10 +120,21 @@
                 .apply();
     }
 
-    void fillBuffer(uint32_t* bufData, uint32_t width, uint32_t height, uint32_t stride, uint8_t r,
-                    uint8_t g, uint8_t b) {
-        for (uint32_t row = 0; row < height; row++) {
-            for (uint32_t col = 0; col < width; col++) {
+    void setUpProducer(BLASTBufferQueueHelper adapter, sp<IGraphicBufferProducer>& producer) {
+        auto igbProducer = adapter.getIGraphicBufferProducer();
+        ASSERT_NE(nullptr, igbProducer.get());
+        IGraphicBufferProducer::QueueBufferOutput qbOutput;
+        ASSERT_EQ(NO_ERROR,
+                  igbProducer->connect(new DummyProducerListener, NATIVE_WINDOW_API_CPU, false,
+                                       &qbOutput));
+        ASSERT_NE(ui::Transform::orientation_flags::ROT_INVALID, qbOutput.transformHint);
+        producer = igbProducer;
+    }
+
+    void fillBuffer(uint32_t* bufData, Rect rect, uint32_t stride, uint8_t r, uint8_t g,
+                    uint8_t b) {
+        for (uint32_t row = rect.top; row < rect.bottom; row++) {
+            for (uint32_t col = rect.left; col < rect.right; col++) {
                 uint8_t* pixel = (uint8_t*)(bufData + (row * stride) + col);
                 *pixel = r;
                 *(pixel + 1) = g;
@@ -129,7 +144,25 @@
         }
     }
 
-    void checkScreenCapture(uint8_t r, uint8_t g, uint8_t b) {
+    void fillQuadrants(sp<GraphicBuffer>& buf) {
+        const auto bufWidth = buf->getWidth();
+        const auto bufHeight = buf->getHeight();
+        uint32_t* bufData;
+        buf->lock(static_cast<uint32_t>(GraphicBuffer::USAGE_SW_WRITE_OFTEN),
+                  reinterpret_cast<void**>(&bufData));
+        fillBuffer(bufData, Rect(0, 0, bufWidth / 2, bufHeight / 2), buf->getStride(), 0, 0, 0);
+        fillBuffer(bufData, Rect(bufWidth / 2, 0, bufWidth, bufHeight / 2), buf->getStride(), 255,
+                   0, 0);
+        fillBuffer(bufData, Rect(bufWidth / 2, bufHeight / 2, bufWidth, bufHeight),
+                   buf->getStride(), 0, 255, 0);
+        fillBuffer(bufData, Rect(0, bufHeight / 2, bufWidth / 2, bufHeight), buf->getStride(), 0, 0,
+                   255);
+        buf->unlock();
+    }
+
+    void checkScreenCapture(uint8_t r, uint8_t g, uint8_t b, Rect region, int32_t border = 0,
+                            bool outsideRegion = false) {
+        const auto epsilon = 3;
         const auto width = mScreenCaptureBuf->getWidth();
         const auto height = mScreenCaptureBuf->getHeight();
         const auto stride = mScreenCaptureBuf->getStride();
@@ -141,9 +174,23 @@
         for (uint32_t row = 0; row < height; row++) {
             for (uint32_t col = 0; col < width; col++) {
                 uint8_t* pixel = (uint8_t*)(bufData + (row * stride) + col);
-                EXPECT_EQ(r, *(pixel));
-                EXPECT_EQ(g, *(pixel + 1));
-                EXPECT_EQ(b, *(pixel + 2));
+                bool inRegion;
+                if (!outsideRegion) {
+                    inRegion = row >= region.top + border && row < region.bottom - border &&
+                            col >= region.left + border && col < region.right - border;
+                } else {
+                    inRegion = row >= region.top - border && row < region.bottom + border &&
+                            col >= region.left - border && col < region.right + border;
+                }
+                if (!outsideRegion && inRegion) {
+                    EXPECT_GE(epsilon, abs(r - *(pixel)));
+                    EXPECT_GE(epsilon, abs(g - *(pixel + 1)));
+                    EXPECT_GE(epsilon, abs(b - *(pixel + 2)));
+                } else if (outsideRegion && !inRegion) {
+                    EXPECT_GE(epsilon, abs(r - *(pixel)));
+                    EXPECT_GE(epsilon, abs(g - *(pixel + 1)));
+                    EXPECT_GE(epsilon, abs(b - *(pixel + 2)));
+                }
             }
         }
         mScreenCaptureBuf->unlock();
@@ -195,14 +242,8 @@
     uint8_t b = 0;
 
     BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
-    auto igbProducer = adapter.getIGraphicBufferProducer();
-    ASSERT_NE(nullptr, igbProducer.get());
-    IGraphicBufferProducer::QueueBufferOutput qbOutput;
-    ASSERT_EQ(NO_ERROR,
-              igbProducer->connect(new DummyProducerListener, NATIVE_WINDOW_API_CPU, false,
-                                   &qbOutput));
-    ASSERT_EQ(NO_ERROR, igbProducer->setMaxDequeuedBufferCount(3));
-    ASSERT_NE(ui::Transform::orientation_flags::ROT_INVALID, qbOutput.transformHint);
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
 
     int slot;
     sp<Fence> fence;
@@ -216,9 +257,10 @@
     uint32_t* bufData;
     buf->lock(static_cast<uint32_t>(GraphicBuffer::USAGE_SW_WRITE_OFTEN),
               reinterpret_cast<void**>(&bufData));
-    fillBuffer(bufData, buf->getWidth(), buf->getHeight(), buf->getStride(), r, g, b);
+    fillBuffer(bufData, Rect(buf->getWidth(), buf->getHeight()), buf->getStride(), r, g, b);
     buf->unlock();
 
+    IGraphicBufferProducer::QueueBufferOutput qbOutput;
     IGraphicBufferProducer::QueueBufferInput input(systemTime(), false, HAL_DATASPACE_UNKNOWN,
                                                    Rect(mDisplayWidth, mDisplayHeight),
                                                    NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
@@ -226,7 +268,7 @@
     igbProducer->queueBuffer(slot, input, &qbOutput);
     ASSERT_NE(ui::Transform::orientation_flags::ROT_INVALID, qbOutput.transformHint);
 
-    adapter.waitForCallback();
+    adapter.waitForCallbacks();
 
     // capture screen and verify that it is red
     bool capturedSecureLayers;
@@ -235,6 +277,345 @@
                                        ui::Dataspace::V0_SRGB, ui::PixelFormat::RGBA_8888, Rect(),
                                        mDisplayWidth, mDisplayHeight,
                                        /*useIdentityTransform*/ false));
-    ASSERT_NO_FATAL_FAILURE(checkScreenCapture(r, g, b));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+}
+
+TEST_F(BLASTBufferQueueTest, TripleBuffering) {
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+
+    std::vector<std::pair<int, sp<Fence>>> allocated;
+    for (int i = 0; i < 3; i++) {
+        int slot;
+        sp<Fence> fence;
+        sp<GraphicBuffer> buf;
+        auto ret = igbProducer->dequeueBuffer(&slot, &fence, mDisplayWidth, mDisplayHeight,
+                                              PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                              nullptr, nullptr);
+        ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, ret);
+        ASSERT_EQ(OK, igbProducer->requestBuffer(slot, &buf));
+        allocated.push_back({slot, fence});
+    }
+    for (int i = 0; i < allocated.size(); i++) {
+        igbProducer->cancelBuffer(allocated[i].first, allocated[i].second);
+    }
+
+    for (int i = 0; i < 10; i++) {
+        int slot;
+        sp<Fence> fence;
+        sp<GraphicBuffer> buf;
+        auto ret = igbProducer->dequeueBuffer(&slot, &fence, mDisplayWidth, mDisplayHeight,
+                                              PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                              nullptr, nullptr);
+        ASSERT_EQ(NO_ERROR, ret);
+        IGraphicBufferProducer::QueueBufferOutput qbOutput;
+        IGraphicBufferProducer::QueueBufferInput input(systemTime(), false, HAL_DATASPACE_UNKNOWN,
+                                                       Rect(mDisplayWidth, mDisplayHeight),
+                                                       NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
+                                                       Fence::NO_FENCE);
+        igbProducer->queueBuffer(slot, input, &qbOutput);
+    }
+    adapter.waitForCallbacks();
+}
+
+TEST_F(BLASTBufferQueueTest, SetCrop_Item) {
+    uint8_t r = 255;
+    uint8_t g = 0;
+    uint8_t b = 0;
+
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+    int slot;
+    sp<Fence> fence;
+    sp<GraphicBuffer> buf;
+    auto ret = igbProducer->dequeueBuffer(&slot, &fence, mDisplayWidth, mDisplayHeight,
+                                          PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                          nullptr, nullptr);
+    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, ret);
+    ASSERT_EQ(OK, igbProducer->requestBuffer(slot, &buf));
+
+    uint32_t* bufData;
+    buf->lock(static_cast<uint32_t>(GraphicBuffer::USAGE_SW_WRITE_OFTEN),
+              reinterpret_cast<void**>(&bufData));
+    fillBuffer(bufData, Rect(buf->getWidth(), buf->getHeight() / 2), buf->getStride(), r, g, b);
+    buf->unlock();
+
+    IGraphicBufferProducer::QueueBufferOutput qbOutput;
+    IGraphicBufferProducer::QueueBufferInput input(systemTime(), false, HAL_DATASPACE_UNKNOWN,
+                                                   Rect(mDisplayWidth, mDisplayHeight / 2),
+                                                   NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
+                                                   Fence::NO_FENCE);
+    igbProducer->queueBuffer(slot, input, &qbOutput);
+    ASSERT_NE(ui::Transform::orientation_flags::ROT_INVALID, qbOutput.transformHint);
+
+    adapter.waitForCallbacks();
+    // capture screen and verify that it is red
+    bool capturedSecureLayers;
+    ASSERT_EQ(NO_ERROR,
+              mComposer->captureScreen(mDisplayToken, &mScreenCaptureBuf, capturedSecureLayers,
+                                       ui::Dataspace::V0_SRGB, ui::PixelFormat::RGBA_8888, Rect(),
+                                       mDisplayWidth, mDisplayHeight,
+                                       /*useIdentityTransform*/ false));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+}
+
+TEST_F(BLASTBufferQueueTest, SetCrop_ScalingModeScaleCrop) {
+    uint8_t r = 255;
+    uint8_t g = 0;
+    uint8_t b = 0;
+
+    int32_t bufferSideLength =
+            (mDisplayWidth < mDisplayHeight) ? mDisplayWidth / 2 : mDisplayHeight / 2;
+    int32_t finalCropSideLength = bufferSideLength / 2;
+
+    auto bg = mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
+                                     ISurfaceComposerClient::eFXSurfaceColor);
+    ASSERT_NE(nullptr, bg.get());
+    Transaction t;
+    t.setLayerStack(bg, 0)
+            .setCrop_legacy(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
+            .setColor(bg, half3{0, 0, 0})
+            .setLayer(bg, 0)
+            .apply();
+
+    BLASTBufferQueueHelper adapter(mSurfaceControl, bufferSideLength, bufferSideLength);
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+    int slot;
+    sp<Fence> fence;
+    sp<GraphicBuffer> buf;
+    auto ret = igbProducer->dequeueBuffer(&slot, &fence, bufferSideLength, bufferSideLength,
+                                          PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                          nullptr, nullptr);
+    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, ret);
+    ASSERT_EQ(OK, igbProducer->requestBuffer(slot, &buf));
+
+    uint32_t* bufData;
+    buf->lock(static_cast<uint32_t>(GraphicBuffer::USAGE_SW_WRITE_OFTEN),
+              reinterpret_cast<void**>(&bufData));
+    fillBuffer(bufData, Rect(buf->getWidth(), buf->getHeight()), buf->getStride(), 0, 0, 0);
+    fillBuffer(bufData,
+               Rect(finalCropSideLength / 2, 0, buf->getWidth() - finalCropSideLength / 2,
+                    buf->getHeight()),
+               buf->getStride(), r, g, b);
+    buf->unlock();
+
+    IGraphicBufferProducer::QueueBufferOutput qbOutput;
+    IGraphicBufferProducer::QueueBufferInput input(systemTime(), false, HAL_DATASPACE_UNKNOWN,
+                                                   Rect(bufferSideLength, finalCropSideLength),
+                                                   NATIVE_WINDOW_SCALING_MODE_SCALE_CROP, 0,
+                                                   Fence::NO_FENCE);
+    igbProducer->queueBuffer(slot, input, &qbOutput);
+    ASSERT_NE(ui::Transform::orientation_flags::ROT_INVALID, qbOutput.transformHint);
+
+    adapter.waitForCallbacks();
+    // capture screen and verify that it is red
+    bool capturedSecureLayers;
+    ASSERT_EQ(NO_ERROR,
+              mComposer->captureScreen(mDisplayToken, &mScreenCaptureBuf, capturedSecureLayers,
+                                       ui::Dataspace::V0_SRGB, ui::PixelFormat::RGBA_8888, Rect(),
+                                       mDisplayWidth, mDisplayHeight,
+                                       /*useIdentityTransform*/ false));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(r, g, b,
+                               {0, 0, (int32_t)bufferSideLength, (int32_t)bufferSideLength}));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(0, 0, 0,
+                               {0, 0, (int32_t)bufferSideLength, (int32_t)bufferSideLength},
+                               /*border*/ 0, /*outsideRegion*/ true));
+}
+
+class BLASTBufferQueueTransformTest : public BLASTBufferQueueTest {
+public:
+    void test(uint32_t tr) {
+        BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+        sp<IGraphicBufferProducer> igbProducer;
+        setUpProducer(adapter, igbProducer);
+
+        auto bufWidth = mDisplayWidth;
+        auto bufHeight = mDisplayHeight;
+        int slot;
+        sp<Fence> fence;
+        sp<GraphicBuffer> buf;
+
+        auto ret = igbProducer->dequeueBuffer(&slot, &fence, bufWidth, bufHeight,
+                                              PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                              nullptr, nullptr);
+        ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, ret);
+        ASSERT_EQ(OK, igbProducer->requestBuffer(slot, &buf));
+
+        fillQuadrants(buf);
+
+        IGraphicBufferProducer::QueueBufferOutput qbOutput;
+        IGraphicBufferProducer::QueueBufferInput input(systemTime(), false, HAL_DATASPACE_UNKNOWN,
+                                                       Rect(bufWidth, bufHeight),
+                                                       NATIVE_WINDOW_SCALING_MODE_FREEZE, tr,
+                                                       Fence::NO_FENCE);
+        igbProducer->queueBuffer(slot, input, &qbOutput);
+        ASSERT_NE(ui::Transform::orientation_flags::ROT_INVALID, qbOutput.transformHint);
+
+        adapter.waitForCallbacks();
+        bool capturedSecureLayers;
+        ASSERT_EQ(NO_ERROR,
+                  mComposer->captureScreen(mDisplayToken, &mScreenCaptureBuf, capturedSecureLayers,
+                                           ui::Dataspace::V0_SRGB, ui::PixelFormat::RGBA_8888,
+                                           Rect(), mDisplayWidth, mDisplayHeight,
+                                           /*useIdentityTransform*/ false));
+        switch (tr) {
+            case ui::Transform::ROT_0:
+                ASSERT_NO_FATAL_FAILURE(checkScreenCapture(0, 0, 0,
+                                                           {0, 0, (int32_t)mDisplayWidth / 2,
+                                                            (int32_t)mDisplayHeight / 2},
+                                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(255, 0, 0,
+                                           {(int32_t)mDisplayWidth / 2, 0, (int32_t)mDisplayWidth,
+                                            (int32_t)mDisplayHeight / 2},
+                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 255, 0,
+                                           {(int32_t)mDisplayWidth / 2, (int32_t)mDisplayHeight / 2,
+                                            (int32_t)mDisplayWidth, (int32_t)mDisplayHeight},
+                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 0, 255,
+                                           {0, (int32_t)mDisplayHeight / 2,
+                                            (int32_t)mDisplayWidth / 2, (int32_t)mDisplayHeight},
+                                           1));
+                break;
+            case ui::Transform::FLIP_H:
+                ASSERT_NO_FATAL_FAILURE(checkScreenCapture(255, 0, 0,
+                                                           {0, 0, (int32_t)mDisplayWidth / 2,
+                                                            (int32_t)mDisplayHeight / 2},
+                                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 0, 0,
+                                           {(int32_t)mDisplayWidth / 2, 0, (int32_t)mDisplayWidth,
+                                            (int32_t)mDisplayHeight / 2},
+                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 0, 255,
+                                           {(int32_t)mDisplayWidth / 2, (int32_t)mDisplayHeight / 2,
+                                            (int32_t)mDisplayWidth, (int32_t)mDisplayHeight},
+                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 255, 0,
+                                           {0, (int32_t)mDisplayHeight / 2,
+                                            (int32_t)mDisplayWidth / 2, (int32_t)mDisplayHeight},
+                                           1));
+                break;
+            case ui::Transform::FLIP_V:
+                ASSERT_NO_FATAL_FAILURE(checkScreenCapture(0, 0, 255,
+                                                           {0, 0, (int32_t)mDisplayWidth / 2,
+                                                            (int32_t)mDisplayHeight / 2},
+                                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 255, 0,
+                                           {(int32_t)mDisplayWidth / 2, 0, (int32_t)mDisplayWidth,
+                                            (int32_t)mDisplayHeight / 2},
+                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(255, 0, 0,
+                                           {(int32_t)mDisplayWidth / 2, (int32_t)mDisplayHeight / 2,
+                                            (int32_t)mDisplayWidth, (int32_t)mDisplayHeight},
+                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 0, 0,
+                                           {0, (int32_t)mDisplayHeight / 2,
+                                            (int32_t)mDisplayWidth / 2, (int32_t)mDisplayHeight},
+                                           1));
+                break;
+            case ui::Transform::ROT_90:
+                ASSERT_NO_FATAL_FAILURE(checkScreenCapture(0, 0, 255,
+                                                           {0, 0, (int32_t)mDisplayWidth / 2,
+                                                            (int32_t)mDisplayHeight / 2},
+                                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 0, 0,
+                                           {(int32_t)mDisplayWidth / 2, 0, (int32_t)mDisplayWidth,
+                                            (int32_t)mDisplayHeight / 2},
+                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(255, 0, 0,
+                                           {(int32_t)mDisplayWidth / 2, (int32_t)mDisplayHeight / 2,
+                                            (int32_t)mDisplayWidth, (int32_t)mDisplayHeight},
+                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 255, 0,
+                                           {0, (int32_t)mDisplayHeight / 2,
+                                            (int32_t)mDisplayWidth / 2, (int32_t)mDisplayHeight},
+                                           1));
+                break;
+            case ui::Transform::ROT_180:
+                ASSERT_NO_FATAL_FAILURE(checkScreenCapture(0, 255, 0,
+                                                           {0, 0, (int32_t)mDisplayWidth / 2,
+                                                            (int32_t)mDisplayHeight / 2},
+                                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 0, 255,
+                                           {(int32_t)mDisplayWidth / 2, 0, (int32_t)mDisplayWidth,
+                                            (int32_t)mDisplayHeight / 2},
+                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 0, 0,
+                                           {(int32_t)mDisplayWidth / 2, (int32_t)mDisplayHeight / 2,
+                                            (int32_t)mDisplayWidth, (int32_t)mDisplayHeight},
+                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(255, 0, 0,
+                                           {0, (int32_t)mDisplayHeight / 2,
+                                            (int32_t)mDisplayWidth / 2, (int32_t)mDisplayHeight},
+                                           1));
+                break;
+            case ui::Transform::ROT_270:
+                ASSERT_NO_FATAL_FAILURE(checkScreenCapture(255, 0, 0,
+                                                           {0, 0, (int32_t)mDisplayWidth / 2,
+                                                            (int32_t)mDisplayHeight / 2},
+                                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 255, 0,
+                                           {(int32_t)mDisplayWidth / 2, 0, (int32_t)mDisplayWidth,
+                                            (int32_t)mDisplayHeight / 2},
+                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 0, 255,
+                                           {(int32_t)mDisplayWidth / 2, (int32_t)mDisplayHeight / 2,
+                                            (int32_t)mDisplayWidth, (int32_t)mDisplayHeight},
+                                           1));
+                ASSERT_NO_FATAL_FAILURE(
+                        checkScreenCapture(0, 0, 0,
+                                           {0, (int32_t)mDisplayHeight / 2,
+                                            (int32_t)mDisplayWidth / 2, (int32_t)mDisplayHeight},
+                                           1));
+        }
+    }
+};
+
+TEST_F(BLASTBufferQueueTransformTest, setTransform_ROT_0) {
+    test(ui::Transform::ROT_0);
+}
+
+TEST_F(BLASTBufferQueueTransformTest, setTransform_FLIP_H) {
+    test(ui::Transform::FLIP_H);
+}
+
+TEST_F(BLASTBufferQueueTransformTest, setTransform_FLIP_V) {
+    test(ui::Transform::FLIP_V);
+}
+
+TEST_F(BLASTBufferQueueTransformTest, setTransform_ROT_90) {
+    test(ui::Transform::ROT_90);
+}
+
+TEST_F(BLASTBufferQueueTransformTest, setTransform_ROT_180) {
+    test(ui::Transform::ROT_180);
+}
+
+TEST_F(BLASTBufferQueueTransformTest, setTransform_ROT_270) {
+    test(ui::Transform::ROT_270);
 }
 } // namespace android
diff --git a/libs/gui/tests/IGraphicBufferProducer_test.cpp b/libs/gui/tests/IGraphicBufferProducer_test.cpp
index aef7aed..103f775 100644
--- a/libs/gui/tests/IGraphicBufferProducer_test.cpp
+++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp
@@ -543,7 +543,6 @@
     // Should now be able to dequeue up to minBuffers times
     DequeueBufferResult result;
     for (int i = 0; i < minBuffers; ++i) {
-
         EXPECT_EQ(OK, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION &
                 (dequeueBuffer(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT,
                               TEST_PRODUCER_USAGE_BITS, &result)))
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index a4fdb35..3d90369 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -831,8 +831,25 @@
                                       std::vector<int32_t>* /*outAllowedConfigs*/) override {
         return NO_ERROR;
     }
+    status_t setDesiredDisplayConfigSpecs(const sp<IBinder>& /*displayToken*/,
+                                          int32_t /*defaultModeId*/, float /*minRefreshRate*/,
+                                          float /*maxRefreshRate*/) override {
+        return NO_ERROR;
+    }
+    status_t getDesiredDisplayConfigSpecs(const sp<IBinder>& /*displayToken*/,
+                                          int32_t* /*outDefaultModeId*/,
+                                          float* /*outMinRefreshRate*/,
+                                          float* /*outMaxRefreshRate*/) override {
+        return NO_ERROR;
+    };
     status_t notifyPowerHint(int32_t /*hintId*/) override { return NO_ERROR; }
 
+    status_t setGlobalShadowSettings(const half4& /*ambientColor*/, const half4& /*spotColor*/,
+                                     float /*lightPosY*/, float /*lightPosZ*/,
+                                     float /*lightRadius*/) override {
+        return NO_ERROR;
+    }
+
 protected:
     IBinder* onAsBinder() override { return nullptr; }
 
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 34b305e..8ccbc7f 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -20,6 +20,7 @@
 #include <limits.h>
 
 #include <input/Input.h>
+#include <input/InputDevice.h>
 #include <input/InputEventLabels.h>
 
 #ifdef __ANDROID__
@@ -41,6 +42,21 @@
 
 // --- InputEvent ---
 
+const char* inputEventTypeToString(int32_t type) {
+    switch (type) {
+        case AINPUT_EVENT_TYPE_KEY: {
+            return "KEY";
+        }
+        case AINPUT_EVENT_TYPE_MOTION: {
+            return "MOTION";
+        }
+        case AINPUT_EVENT_TYPE_FOCUS: {
+            return "FOCUS";
+        }
+    }
+    return "UNKNOWN";
+}
+
 void InputEvent::initialize(int32_t deviceId, int32_t source, int32_t displayId) {
     mDeviceId = deviceId;
     mSource = source;
@@ -587,6 +603,20 @@
     return getAxisByLabel(label);
 }
 
+// --- FocusEvent ---
+
+void FocusEvent::initialize(bool hasFocus, bool inTouchMode) {
+    InputEvent::initialize(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN,
+                           ADISPLAY_ID_NONE);
+    mHasFocus = hasFocus;
+    mInTouchMode = inTouchMode;
+}
+
+void FocusEvent::initialize(const FocusEvent& from) {
+    InputEvent::initialize(from);
+    mHasFocus = from.mHasFocus;
+    mInTouchMode = from.mInTouchMode;
+}
 
 // --- PooledInputEventFactory ---
 
@@ -595,43 +625,52 @@
 }
 
 PooledInputEventFactory::~PooledInputEventFactory() {
-    for (size_t i = 0; i < mKeyEventPool.size(); i++) {
-        delete mKeyEventPool.itemAt(i);
-    }
-    for (size_t i = 0; i < mMotionEventPool.size(); i++) {
-        delete mMotionEventPool.itemAt(i);
-    }
 }
 
 KeyEvent* PooledInputEventFactory::createKeyEvent() {
-    if (!mKeyEventPool.isEmpty()) {
-        KeyEvent* event = mKeyEventPool.top();
-        mKeyEventPool.pop();
-        return event;
+    if (mKeyEventPool.empty()) {
+        return new KeyEvent();
     }
-    return new KeyEvent();
+    KeyEvent* event = mKeyEventPool.front().release();
+    mKeyEventPool.pop();
+    return event;
 }
 
 MotionEvent* PooledInputEventFactory::createMotionEvent() {
-    if (!mMotionEventPool.isEmpty()) {
-        MotionEvent* event = mMotionEventPool.top();
-        mMotionEventPool.pop();
-        return event;
+    if (mMotionEventPool.empty()) {
+        return new MotionEvent();
     }
-    return new MotionEvent();
+    MotionEvent* event = mMotionEventPool.front().release();
+    mMotionEventPool.pop();
+    return event;
+}
+
+FocusEvent* PooledInputEventFactory::createFocusEvent() {
+    if (mFocusEventPool.empty()) {
+        return new FocusEvent();
+    }
+    FocusEvent* event = mFocusEventPool.front().release();
+    mFocusEventPool.pop();
+    return event;
 }
 
 void PooledInputEventFactory::recycle(InputEvent* event) {
     switch (event->getType()) {
     case AINPUT_EVENT_TYPE_KEY:
         if (mKeyEventPool.size() < mMaxPoolSize) {
-            mKeyEventPool.push(static_cast<KeyEvent*>(event));
+            mKeyEventPool.push(std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(event)));
             return;
         }
         break;
     case AINPUT_EVENT_TYPE_MOTION:
         if (mMotionEventPool.size() < mMaxPoolSize) {
-            mMotionEventPool.push(static_cast<MotionEvent*>(event));
+            mMotionEventPool.push(std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event)));
+            return;
+        }
+        break;
+    case AINPUT_EVENT_TYPE_FOCUS:
+        if (mFocusEventPool.size() < mMaxPoolSize) {
+            mFocusEventPool.push(std::unique_ptr<FocusEvent>(static_cast<FocusEvent*>(event)));
             return;
         }
         break;
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index a5dd3c0..200e1f3 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -14,7 +14,7 @@
 static constexpr bool DEBUG_CHANNEL_LIFECYCLE = false;
 
 // Log debug messages about transport actions
-#define DEBUG_TRANSPORT_ACTIONS 0
+static constexpr bool DEBUG_TRANSPORT_ACTIONS = false;
 
 // Log debug messages about touch event resampling
 #define DEBUG_RESAMPLING 0
@@ -88,6 +88,10 @@
     return (source & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
 }
 
+inline static const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
 // --- InputMessage ---
 
 bool InputMessage::isValid(size_t actualSize) const {
@@ -99,6 +103,8 @@
                 return body.motion.pointerCount > 0 && body.motion.pointerCount <= MAX_POINTERS;
             case Type::FINISHED:
                 return true;
+            case Type::FOCUS:
+                return true;
         }
     }
     return false;
@@ -112,6 +118,8 @@
             return sizeof(Header) + body.motion.size();
         case Type::FINISHED:
             return sizeof(Header) + body.finished.size();
+        case Type::FOCUS:
+            return sizeof(Header) + body.focus.size();
     }
     return sizeof(Header);
 }
@@ -216,8 +224,10 @@
             msg->body.finished.handled = body.finished.handled;
             break;
         }
-        default: {
-            LOG_FATAL("Unexpected message type %i", header.type);
+        case InputMessage::Type::FOCUS: {
+            msg->body.focus.seq = body.focus.seq;
+            msg->body.focus.hasFocus = body.focus.hasFocus;
+            msg->body.focus.inTouchMode = body.focus.inTouchMode;
             break;
         }
     }
@@ -432,14 +442,13 @@
                 mChannel->getName().c_str(), keyCode);
         ATRACE_NAME(message.c_str());
     }
-#if DEBUG_TRANSPORT_ACTIONS
-    ALOGD("channel '%s' publisher ~ publishKeyEvent: seq=%u, deviceId=%d, source=0x%x, "
-            "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d,"
-            "downTime=%" PRId64 ", eventTime=%" PRId64,
-            mChannel->getName().c_str(), seq,
-            deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount,
-            downTime, eventTime);
-#endif
+    if (DEBUG_TRANSPORT_ACTIONS) {
+        ALOGD("channel '%s' publisher ~ publishKeyEvent: seq=%u, deviceId=%d, source=0x%x, "
+              "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d,"
+              "downTime=%" PRId64 ", eventTime=%" PRId64,
+              mChannel->getName().c_str(), seq, deviceId, source, action, flags, keyCode, scanCode,
+              metaState, repeatCount, downTime, eventTime);
+    }
 
     if (!seq) {
         ALOGE("Attempted to publish a key event with sequence number 0.");
@@ -476,18 +485,18 @@
                 mChannel->getName().c_str(), action);
         ATRACE_NAME(message.c_str());
     }
-#if DEBUG_TRANSPORT_ACTIONS
-    ALOGD("channel '%s' publisher ~ publishMotionEvent: seq=%u, deviceId=%d, source=0x%x, "
-            "displayId=%" PRId32 ", "
-            "action=0x%x, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, "
-            "metaState=0x%x, buttonState=0x%x, classification=%s, xOffset=%f, yOffset=%f, "
-            "xPrecision=%f, yPrecision=%f, downTime=%" PRId64 ", eventTime=%" PRId64 ", "
-            "pointerCount=%" PRIu32,
-            mChannel->getName().c_str(), seq,
-            deviceId, source, displayId, action, actionButton, flags, edgeFlags, metaState,
-            buttonState, motionClassificationToString(classification),
-            xOffset, yOffset, xPrecision, yPrecision, downTime, eventTime, pointerCount);
-#endif
+    if (DEBUG_TRANSPORT_ACTIONS) {
+        ALOGD("channel '%s' publisher ~ publishMotionEvent: seq=%u, deviceId=%d, source=0x%x, "
+              "displayId=%" PRId32 ", "
+              "action=0x%x, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, "
+              "metaState=0x%x, buttonState=0x%x, classification=%s, xOffset=%f, yOffset=%f, "
+              "xPrecision=%f, yPrecision=%f, downTime=%" PRId64 ", eventTime=%" PRId64 ", "
+              "pointerCount=%" PRIu32,
+              mChannel->getName().c_str(), seq, deviceId, source, displayId, action, actionButton,
+              flags, edgeFlags, metaState, buttonState,
+              motionClassificationToString(classification), xOffset, yOffset, xPrecision,
+              yPrecision, downTime, eventTime, pointerCount);
+    }
 
     if (!seq) {
         ALOGE("Attempted to publish a motion event with sequence number 0.");
@@ -530,11 +539,27 @@
     return mChannel->sendMessage(&msg);
 }
 
+status_t InputPublisher::publishFocusEvent(uint32_t seq, bool hasFocus, bool inTouchMode) {
+    if (ATRACE_ENABLED()) {
+        std::string message =
+                StringPrintf("publishFocusEvent(inputChannel=%s, hasFocus=%s, inTouchMode=%s)",
+                             mChannel->getName().c_str(), toString(hasFocus),
+                             toString(inTouchMode));
+        ATRACE_NAME(message.c_str());
+    }
+
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::FOCUS;
+    msg.body.focus.seq = seq;
+    msg.body.focus.hasFocus = hasFocus ? 1 : 0;
+    msg.body.focus.inTouchMode = inTouchMode ? 1 : 0;
+    return mChannel->sendMessage(&msg);
+}
+
 status_t InputPublisher::receiveFinishedSignal(uint32_t* outSeq, bool* outHandled) {
-#if DEBUG_TRANSPORT_ACTIONS
-    ALOGD("channel '%s' publisher ~ receiveFinishedSignal",
-            mChannel->getName().c_str());
-#endif
+    if (DEBUG_TRANSPORT_ACTIONS) {
+        ALOGD("channel '%s' publisher ~ receiveFinishedSignal", mChannel->getName().c_str());
+    }
 
     InputMessage msg;
     status_t result = mChannel->receiveMessage(&msg);
@@ -549,7 +574,7 @@
         return UNKNOWN_ERROR;
     }
     *outSeq = msg.body.finished.seq;
-    *outHandled = msg.body.finished.handled;
+    *outHandled = msg.body.finished.handled == 1;
     return OK;
 }
 
@@ -567,12 +592,12 @@
     return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true);
 }
 
-status_t InputConsumer::consume(InputEventFactoryInterface* factory,
-        bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
-#if DEBUG_TRANSPORT_ACTIONS
-    ALOGD("channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
-            mChannel->getName().c_str(), consumeBatches ? "true" : "false", frameTime);
-#endif
+status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
+                                nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
+    if (DEBUG_TRANSPORT_ACTIONS) {
+        ALOGD("channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
+              mChannel->getName().c_str(), toString(consumeBatches), frameTime);
+    }
 
     *outSeq = 0;
     *outEvent = nullptr;
@@ -592,10 +617,10 @@
                 if (consumeBatches || result != WOULD_BLOCK) {
                     result = consumeBatch(factory, frameTime, outSeq, outEvent);
                     if (*outEvent) {
-#if DEBUG_TRANSPORT_ACTIONS
-                        ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
-                                mChannel->getName().c_str(), *outSeq);
-#endif
+                        if (DEBUG_TRANSPORT_ACTIONS) {
+                            ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
+                                  mChannel->getName().c_str(), *outSeq);
+                        }
                         break;
                     }
                 }
@@ -611,10 +636,10 @@
                 initializeKeyEvent(keyEvent, &mMsg);
                 *outSeq = mMsg.body.key.seq;
                 *outEvent = keyEvent;
-#if DEBUG_TRANSPORT_ACTIONS
-            ALOGD("channel '%s' consumer ~ consumed key event, seq=%u",
-                    mChannel->getName().c_str(), *outSeq);
-#endif
+                if (DEBUG_TRANSPORT_ACTIONS) {
+                    ALOGD("channel '%s' consumer ~ consumed key event, seq=%u",
+                          mChannel->getName().c_str(), *outSeq);
+                }
             break;
             }
 
@@ -624,10 +649,10 @@
                     Batch& batch = mBatches.editItemAt(batchIndex);
                     if (canAddSample(batch, &mMsg)) {
                         batch.samples.push(mMsg);
-#if DEBUG_TRANSPORT_ACTIONS
-                    ALOGD("channel '%s' consumer ~ appended to batch event",
-                            mChannel->getName().c_str());
-#endif
+                        if (DEBUG_TRANSPORT_ACTIONS) {
+                            ALOGD("channel '%s' consumer ~ appended to batch event",
+                                  mChannel->getName().c_str());
+                        }
                     break;
                     } else if (isPointerEvent(mMsg.body.motion.source) &&
                                mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) {
@@ -649,47 +674,58 @@
                         if (result) {
                             return result;
                         }
-#if DEBUG_TRANSPORT_ACTIONS
-                    ALOGD("channel '%s' consumer ~ consumed batch event and "
-                            "deferred current event, seq=%u",
-                            mChannel->getName().c_str(), *outSeq);
-#endif
+                        if (DEBUG_TRANSPORT_ACTIONS) {
+                            ALOGD("channel '%s' consumer ~ consumed batch event and "
+                                  "deferred current event, seq=%u",
+                                  mChannel->getName().c_str(), *outSeq);
+                        }
                     break;
                     }
                 }
 
-            // Start a new batch if needed.
-            if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE
-                    || mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
-                mBatches.push();
-                Batch& batch = mBatches.editTop();
-                batch.samples.push(mMsg);
-#if DEBUG_TRANSPORT_ACTIONS
-                ALOGD("channel '%s' consumer ~ started batch event",
-                        mChannel->getName().c_str());
-#endif
+                // Start a new batch if needed.
+                if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE ||
+                    mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+                    mBatches.push();
+                    Batch& batch = mBatches.editTop();
+                    batch.samples.push(mMsg);
+                    if (DEBUG_TRANSPORT_ACTIONS) {
+                        ALOGD("channel '%s' consumer ~ started batch event",
+                              mChannel->getName().c_str());
+                    }
+                    break;
+                }
+
+                MotionEvent* motionEvent = factory->createMotionEvent();
+                if (!motionEvent) return NO_MEMORY;
+
+                updateTouchState(mMsg);
+                initializeMotionEvent(motionEvent, &mMsg);
+                *outSeq = mMsg.body.motion.seq;
+                *outEvent = motionEvent;
+
+                if (DEBUG_TRANSPORT_ACTIONS) {
+                    ALOGD("channel '%s' consumer ~ consumed motion event, seq=%u",
+                          mChannel->getName().c_str(), *outSeq);
+                }
                 break;
             }
 
-            MotionEvent* motionEvent = factory->createMotionEvent();
-            if (! motionEvent) return NO_MEMORY;
-
-            updateTouchState(mMsg);
-            initializeMotionEvent(motionEvent, &mMsg);
-            *outSeq = mMsg.body.motion.seq;
-            *outEvent = motionEvent;
-
-#if DEBUG_TRANSPORT_ACTIONS
-            ALOGD("channel '%s' consumer ~ consumed motion event, seq=%u",
-                    mChannel->getName().c_str(), *outSeq);
-#endif
-            break;
+            case InputMessage::Type::FINISHED: {
+                LOG_ALWAYS_FATAL("Consumed a FINISHED message, which should never be seen by "
+                                 "InputConsumer!");
+                break;
             }
 
-        default:
-            ALOGE("channel '%s' consumer ~ Received unexpected message of type %d",
-                    mChannel->getName().c_str(), mMsg.header.type);
-            return UNKNOWN_ERROR;
+            case InputMessage::Type::FOCUS: {
+                FocusEvent* focusEvent = factory->createFocusEvent();
+                if (!focusEvent) return NO_MEMORY;
+
+                initializeFocusEvent(focusEvent, &mMsg);
+                *outSeq = mMsg.body.focus.seq;
+                *outEvent = focusEvent;
+                break;
+            }
         }
     }
     return OK;
@@ -1014,10 +1050,10 @@
 }
 
 status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
-#if DEBUG_TRANSPORT_ACTIONS
-    ALOGD("channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
-            mChannel->getName().c_str(), seq, handled ? "true" : "false");
-#endif
+    if (DEBUG_TRANSPORT_ACTIONS) {
+        ALOGD("channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
+              mChannel->getName().c_str(), seq, toString(handled));
+    }
 
     if (!seq) {
         ALOGE("Attempted to send a finished signal with sequence number 0.");
@@ -1066,7 +1102,7 @@
     InputMessage msg;
     msg.header.type = InputMessage::Type::FINISHED;
     msg.body.finished.seq = seq;
-    msg.body.finished.handled = handled;
+    msg.body.finished.handled = handled ? 1 : 0;
     return mChannel->sendMessage(&msg);
 }
 
@@ -1114,6 +1150,10 @@
             msg->body.key.eventTime);
 }
 
+void InputConsumer::initializeFocusEvent(FocusEvent* event, const InputMessage* msg) {
+    event->initialize(msg->body.focus.hasFocus == 1, msg->body.focus.inTouchMode == 1);
+}
+
 void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) {
     uint32_t pointerCount = msg->body.motion.pointerCount;
     PointerProperties pointerProperties[pointerCount];
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index a362f32..2fc77e9 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -60,6 +60,7 @@
 
     void PublishAndConsumeKeyEvent();
     void PublishAndConsumeMotionEvent();
+    void PublishAndConsumeFocusEvent();
 };
 
 TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
@@ -256,6 +257,43 @@
             << "publisher receiveFinishedSignal should have set handled to consumer's reply";
 }
 
+void InputPublisherAndConsumerTest::PublishAndConsumeFocusEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 15;
+    constexpr bool hasFocus = true;
+    constexpr bool inTouchMode = true;
+
+    status = mPublisher->publishFocusEvent(seq, hasFocus, inTouchMode);
+    ASSERT_EQ(OK, status) << "publisher publishKeyEvent should return OK";
+
+    uint32_t consumeSeq;
+    InputEvent* event;
+    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
+    ASSERT_EQ(OK, status) << "consumer consume should return OK";
+
+    ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event";
+    ASSERT_EQ(AINPUT_EVENT_TYPE_FOCUS, event->getType())
+            << "consumer should have returned a focus event";
+
+    FocusEvent* focusEvent = static_cast<FocusEvent*>(event);
+    EXPECT_EQ(seq, consumeSeq);
+    EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
+    EXPECT_EQ(inTouchMode, focusEvent->getInTouchMode());
+
+    status = mConsumer->sendFinishedSignal(seq, true);
+    ASSERT_EQ(OK, status) << "consumer sendFinishedSignal should return OK";
+
+    uint32_t finishedSeq = 0;
+    bool handled = false;
+    status = mPublisher->receiveFinishedSignal(&finishedSeq, &handled);
+    ASSERT_EQ(OK, status) << "publisher receiveFinishedSignal should return OK";
+    ASSERT_EQ(seq, finishedSeq)
+            << "publisher receiveFinishedSignal should have returned the original sequence number";
+    ASSERT_TRUE(handled)
+            << "publisher receiveFinishedSignal should have set handled to consumer's reply";
+}
+
 TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_EndToEnd) {
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
 }
@@ -264,6 +302,10 @@
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
 }
 
+TEST_F(InputPublisherAndConsumerTest, PublishFocusEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeFocusEvent());
+}
+
 TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) {
     status_t status;
     const size_t pointerCount = 1;
@@ -322,6 +364,7 @@
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeFocusEvent());
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
 }
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index 8d8cf06..9ab0dba 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -69,8 +69,29 @@
   CHECK_OFFSET(InputMessage::Body::Motion, pointerCount, 88);
   CHECK_OFFSET(InputMessage::Body::Motion, pointers, 96);
 
+  CHECK_OFFSET(InputMessage::Body::Focus, seq, 0);
+  CHECK_OFFSET(InputMessage::Body::Focus, hasFocus, 4);
+  CHECK_OFFSET(InputMessage::Body::Focus, inTouchMode, 6);
+
   CHECK_OFFSET(InputMessage::Body::Finished, seq, 0);
   CHECK_OFFSET(InputMessage::Body::Finished, handled, 4);
 }
 
+void TestHeaderSize() {
+    static_assert(sizeof(InputMessage::Header) == 8);
+}
+
+/**
+ * We cannot use the Body::size() method here because it is not static for
+ * the Motion type, where "pointerCount" variable affects the size and can change at runtime.
+ */
+void TestBodySize() {
+    static_assert(sizeof(InputMessage::Body::Key) == 64);
+    static_assert(sizeof(InputMessage::Body::Motion) ==
+                  offsetof(InputMessage::Body::Motion, pointers) +
+                          sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS);
+    static_assert(sizeof(InputMessage::Body::Finished) == 8);
+    static_assert(sizeof(InputMessage::Body::Focus) == 8);
+}
+
 } // namespace android
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index 63e0734..05ff93e 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -22,7 +22,7 @@
 #include <thread>
 
 #include <android/choreographer.h>
-#include <androidfw/DisplayEventDispatcher.h>
+#include <gui/DisplayEventDispatcher.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
 #include <utils/Looper.h>
diff --git a/libs/nativedisplay/ADisplay.cpp b/libs/nativedisplay/ADisplay.cpp
index 6665635..6566538 100644
--- a/libs/nativedisplay/ADisplay.cpp
+++ b/libs/nativedisplay/ADisplay.cpp
@@ -18,6 +18,7 @@
 #include <gui/SurfaceComposerClient.h>
 #include <ui/DisplayInfo.h>
 #include <ui/GraphicTypes.h>
+#include <ui/PixelFormat.h>
 
 #include <algorithm>
 #include <optional>
@@ -82,6 +83,16 @@
     ADisplayType type;
 
     /**
+     * The preferred WCG dataspace
+     */
+    ADataSpace wcgDataspace;
+
+    /**
+     * The preferred WCG pixel format
+     */
+    AHardwareBuffer_Format wcgPixelFormat;
+
+    /**
      * Number of supported configs
      */
     size_t numConfigs;
@@ -151,6 +162,17 @@
 
     const std::optional<PhysicalDisplayId> internalId =
             SurfaceComposerClient::getInternalDisplayId();
+    ui::Dataspace defaultDataspace;
+    ui::PixelFormat defaultPixelFormat;
+    ui::Dataspace wcgDataspace;
+    ui::PixelFormat wcgPixelFormat;
+
+    const status_t status =
+            SurfaceComposerClient::getCompositionPreference(&defaultDataspace, &defaultPixelFormat,
+                                                            &wcgDataspace, &wcgPixelFormat);
+    if (status != NO_ERROR) {
+        return status;
+    }
 
     // Here we allocate all our required memory in one block. The layout is as
     // follows:
@@ -176,7 +198,12 @@
         const std::vector<DisplayConfigImpl>& configs = configsPerDisplay[i];
         memcpy(configData, configs.data(), sizeof(DisplayConfigImpl) * configs.size());
 
-        displayData[i] = DisplayImpl{id, type, configs.size(), configData};
+        displayData[i] = DisplayImpl{id,
+                                     type,
+                                     static_cast<ADataSpace>(wcgDataspace),
+                                     static_cast<AHardwareBuffer_Format>(wcgPixelFormat),
+                                     configs.size(),
+                                     configData};
         impls[i] = displayData + i;
         // Advance the configData pointer so that future configs are written to
         // the correct display.
@@ -210,6 +237,17 @@
     return reinterpret_cast<DisplayImpl*>(display)->type;
 }
 
+void ADisplay_getPreferredWideColorFormat(ADisplay* display, ADataSpace* outDataspace,
+                                          AHardwareBuffer_Format* outPixelFormat) {
+    CHECK_NOT_NULL(display);
+    CHECK_NOT_NULL(outDataspace);
+    CHECK_NOT_NULL(outPixelFormat);
+
+    DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
+    *outDataspace = impl->wcgDataspace;
+    *outPixelFormat = impl->wcgPixelFormat;
+}
+
 int ADisplay_getCurrentConfig(ADisplay* display, ADisplayConfig** outConfig) {
     CHECK_NOT_NULL(display);
 
diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp
index 45b935a..7a497ea 100644
--- a/libs/nativedisplay/Android.bp
+++ b/libs/nativedisplay/Android.bp
@@ -46,9 +46,9 @@
     ],
 
     shared_libs: [
-        "libandroidfw",
         "libgui",
         "liblog",
+        "libnativewindow",
         "libui",
         "libutils",
     ],
diff --git a/libs/nativedisplay/include/apex/display.h b/libs/nativedisplay/include/apex/display.h
index 7af452a..9be401e 100644
--- a/libs/nativedisplay/include/apex/display.h
+++ b/libs/nativedisplay/include/apex/display.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <android/data_space.h>
+#include <android/hardware_buffer.h>
 #include <inttypes.h>
 
 __BEGIN_DECLS
@@ -72,6 +74,12 @@
 ADisplayType ADisplay_getDisplayType(ADisplay* display);
 
 /**
+ * Queries the display's preferred WCG format
+ */
+void ADisplay_getPreferredWideColorFormat(ADisplay* display, ADataSpace* outDataspace,
+                                          AHardwareBuffer_Format* outPixelFormat);
+
+/**
  * Gets the current display configuration for the given display.
  *
  * Memory is *not* allocated for the caller. As such, the returned output
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 136ad0d..348377e 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -52,6 +52,8 @@
         "gl/GLExtensions.cpp",
         "gl/GLFramebuffer.cpp",
         "gl/GLImage.cpp",
+        "gl/GLShadowVertexGenerator.cpp",
+        "gl/GLSkiaShadowPort.cpp",
         "gl/ImageManager.cpp",
         "gl/Program.cpp",
         "gl/ProgramCache.cpp",
diff --git a/libs/renderengine/Mesh.cpp b/libs/renderengine/Mesh.cpp
index f5387f2..ed2f45f 100644
--- a/libs/renderengine/Mesh.cpp
+++ b/libs/renderengine/Mesh.cpp
@@ -21,38 +21,46 @@
 namespace android {
 namespace renderengine {
 
-Mesh::Mesh(Primitive primitive, size_t vertexCount, size_t vertexSize, size_t texCoordSize)
+Mesh::Mesh(Primitive primitive, size_t vertexCount, size_t vertexSize, size_t texCoordSize,
+           size_t cropCoordsSize, size_t shadowColorSize, size_t shadowParamsSize,
+           size_t indexCount)
       : mVertexCount(vertexCount),
         mVertexSize(vertexSize),
         mTexCoordsSize(texCoordSize),
-        mPrimitive(primitive) {
+        mCropCoordsSize(cropCoordsSize),
+        mShadowColorSize(shadowColorSize),
+        mShadowParamsSize(shadowParamsSize),
+        mPrimitive(primitive),
+        mIndexCount(indexCount) {
     if (vertexCount == 0) {
         mVertices.resize(1);
         mVertices[0] = 0.0f;
         mStride = 0;
         return;
     }
-
-    const size_t CROP_COORD_SIZE = 2;
-    size_t stride = vertexSize + texCoordSize + CROP_COORD_SIZE;
+    size_t stride = vertexSize + texCoordSize + cropCoordsSize + shadowColorSize + shadowParamsSize;
     size_t remainder = (stride * vertexCount) / vertexCount;
     // Since all of the input parameters are unsigned, if stride is less than
     // either vertexSize or texCoordSize, it must have overflowed. remainder
     // will be equal to stride as long as stride * vertexCount doesn't overflow.
     if ((stride < vertexSize) || (remainder != stride)) {
-        ALOGE("Overflow in Mesh(..., %zu, %zu, %zu, %zu)", vertexCount, vertexSize, texCoordSize,
-              CROP_COORD_SIZE);
+        ALOGE("Overflow in Mesh(..., %zu, %zu, %zu, %zu, %zu, %zu)", vertexCount, vertexSize,
+              texCoordSize, cropCoordsSize, shadowColorSize, shadowParamsSize);
         mVertices.resize(1);
         mVertices[0] = 0.0f;
         mVertexCount = 0;
         mVertexSize = 0;
         mTexCoordsSize = 0;
+        mCropCoordsSize = 0;
+        mShadowColorSize = 0;
+        mShadowParamsSize = 0;
         mStride = 0;
         return;
     }
 
     mVertices.resize(stride * vertexCount);
     mStride = stride;
+    mIndices.resize(indexCount);
 }
 
 Mesh::Primitive Mesh::getPrimitive() const {
@@ -80,6 +88,28 @@
     return mVertices.data() + mVertexSize + mTexCoordsSize;
 }
 
+float const* Mesh::getShadowColor() const {
+    return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize;
+}
+float* Mesh::getShadowColor() {
+    return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize;
+}
+
+float const* Mesh::getShadowParams() const {
+    return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize + mShadowColorSize;
+}
+float* Mesh::getShadowParams() {
+    return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize + mShadowColorSize;
+}
+
+uint16_t const* Mesh::getIndices() const {
+    return mIndices.data();
+}
+
+uint16_t* Mesh::getIndices() {
+    return mIndices.data();
+}
+
 size_t Mesh::getVertexCount() const {
     return mVertexCount;
 }
@@ -92,6 +122,14 @@
     return mTexCoordsSize;
 }
 
+size_t Mesh::getShadowColorSize() const {
+    return mShadowColorSize;
+}
+
+size_t Mesh::getShadowParamsSize() const {
+    return mShadowParamsSize;
+}
+
 size_t Mesh::getByteStride() const {
     return mStride * sizeof(float);
 }
@@ -100,5 +138,9 @@
     return mStride;
 }
 
+size_t Mesh::getIndexCount() const {
+    return mIndexCount;
+}
+
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index 15d025b..0748dfb 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -46,6 +46,7 @@
 #include "GLExtensions.h"
 #include "GLFramebuffer.h"
 #include "GLImage.h"
+#include "GLShadowVertexGenerator.h"
 #include "Program.h"
 #include "ProgramCache.h"
 
@@ -566,7 +567,10 @@
                                            float alpha) {
     size_t c;
     Rect const* r = region.getArray(&c);
-    Mesh mesh(Mesh::TRIANGLES, c * 6, 2);
+    Mesh mesh = Mesh::Builder()
+                        .setPrimitive(Mesh::TRIANGLES)
+                        .setVertices(c * 6 /* count */, 2 /* size */)
+                        .build();
     Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
     for (size_t i = 0; i < c; i++, r++) {
         position[i * 6 + 0].x = r->left;
@@ -981,8 +985,15 @@
         fillRegionWithColor(display.clearRegion, 0.0, 0.0, 0.0, 1.0);
     }
 
-    Mesh mesh(Mesh::TRIANGLE_FAN, 4, 2, 2);
+    Mesh mesh = Mesh::Builder()
+                        .setPrimitive(Mesh::TRIANGLE_FAN)
+                        .setVertices(4 /* count */, 2 /* size */)
+                        .setTexCoords(2 /* size */)
+                        .setCropCoords(2 /* size */)
+                        .build();
     for (auto layer : layers) {
+        mState.maxMasteringLuminance = layer.source.buffer.maxMasteringLuminance;
+        mState.maxContentLuminance = layer.source.buffer.maxContentLuminance;
         mState.projectionMatrix = projectionMatrix * layer.geometry.positionTransform;
 
         const FloatRect bounds = layer.geometry.boundaries;
@@ -998,7 +1009,6 @@
         bool usePremultipliedAlpha = true;
         bool disableTexture = true;
         bool isOpaque = false;
-
         if (layer.source.buffer.buffer != nullptr) {
             disableTexture = false;
             isOpaque = layer.source.buffer.isOpaque;
@@ -1036,10 +1046,14 @@
         }
         setSourceDataSpace(layer.sourceDataspace);
 
+        if (layer.shadow.length > 0.0f) {
+            handleShadow(layer.geometry.boundaries, layer.geometry.roundedCornersRadius,
+                         layer.shadow);
+        }
         // We only want to do a special handling for rounded corners when having rounded corners
         // is the only reason it needs to turn on blending, otherwise, we handle it like the
         // usual way since it needs to turn on blending anyway.
-        if (layer.geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) {
+        else if (layer.geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) {
             handleRoundedCorners(display, layer, mesh);
         } else {
             drawMesh(mesh);
@@ -1177,13 +1191,23 @@
                               mesh.getByteStride(), mesh.getCropCoords());
     }
 
+    if (mState.drawShadows) {
+        glEnableVertexAttribArray(Program::shadowColor);
+        glVertexAttribPointer(Program::shadowColor, mesh.getShadowColorSize(), GL_FLOAT, GL_FALSE,
+                              mesh.getByteStride(), mesh.getShadowColor());
+
+        glEnableVertexAttribArray(Program::shadowParams);
+        glVertexAttribPointer(Program::shadowParams, mesh.getShadowParamsSize(), GL_FLOAT, GL_FALSE,
+                              mesh.getByteStride(), mesh.getShadowParams());
+    }
+
+    Description managedState = mState;
     // By default, DISPLAY_P3 is the only supported wide color output. However,
     // when HDR content is present, hardware composer may be able to handle
     // BT2020 data space, in that case, the output data space is set to be
     // BT2020_HLG or BT2020_PQ respectively. In GPU fall back we need
     // to respect this and convert non-HDR content to HDR format.
     if (mUseColorManagement) {
-        Description managedState = mState;
         Dataspace inputStandard = static_cast<Dataspace>(mDataSpace & Dataspace::STANDARD_MASK);
         Dataspace inputTransfer = static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK);
         Dataspace outputStandard =
@@ -1274,27 +1298,25 @@
             managedState.outputTransferFunction =
                     Description::dataSpaceToTransferFunction(outputTransfer);
         }
+    }
 
-        ProgramCache::getInstance().useProgram(mInProtectedContext ? mProtectedEGLContext
-                                                                   : mEGLContext,
-                                               managedState);
+    ProgramCache::getInstance().useProgram(mInProtectedContext ? mProtectedEGLContext : mEGLContext,
+                                           managedState);
 
-        glDrawArrays(mesh.getPrimitive(), 0, mesh.getVertexCount());
-
-        if (outputDebugPPMs) {
-            static uint64_t managedColorFrameCount = 0;
-            std::ostringstream out;
-            out << "/data/texture_out" << managedColorFrameCount++;
-            writePPM(out.str().c_str(), mVpWidth, mVpHeight);
-        }
+    if (mState.drawShadows) {
+        glDrawElements(mesh.getPrimitive(), mesh.getIndexCount(), GL_UNSIGNED_SHORT,
+                       mesh.getIndices());
     } else {
-        ProgramCache::getInstance().useProgram(mInProtectedContext ? mProtectedEGLContext
-                                                                   : mEGLContext,
-                                               mState);
-
         glDrawArrays(mesh.getPrimitive(), 0, mesh.getVertexCount());
     }
 
+    if (mUseColorManagement && outputDebugPPMs) {
+        static uint64_t managedColorFrameCount = 0;
+        std::ostringstream out;
+        out << "/data/texture_out" << managedColorFrameCount++;
+        writePPM(out.str().c_str(), mVpWidth, mVpHeight);
+    }
+
     if (mesh.getTexCoordsSize()) {
         glDisableVertexAttribArray(Program::texCoords);
     }
@@ -1302,6 +1324,11 @@
     if (mState.cornerRadius > 0.0f) {
         glDisableVertexAttribArray(Program::cropCoords);
     }
+
+    if (mState.drawShadows) {
+        glDisableVertexAttribArray(Program::shadowColor);
+        glDisableVertexAttribArray(Program::shadowParams);
+    }
 }
 
 size_t GLESRenderEngine::getMaxTextureSize() const {
@@ -1536,6 +1563,35 @@
     }
 }
 
+void GLESRenderEngine::handleShadow(const FloatRect& casterRect, float casterCornerRadius,
+                                    const ShadowSettings& settings) {
+    ATRACE_CALL();
+    const float casterZ = settings.length / 2.0f;
+    const GLShadowVertexGenerator shadows(casterRect, casterCornerRadius, casterZ,
+                                          settings.casterIsTranslucent, settings.ambientColor,
+                                          settings.spotColor, settings.lightPos,
+                                          settings.lightRadius);
+
+    // setup mesh for both shadows
+    Mesh mesh = Mesh::Builder()
+                        .setPrimitive(Mesh::TRIANGLES)
+                        .setVertices(shadows.getVertexCount(), 2 /* size */)
+                        .setShadowAttrs()
+                        .setIndices(shadows.getIndexCount())
+                        .build();
+
+    Mesh::VertexArray<vec2> position = mesh.getPositionArray<vec2>();
+    Mesh::VertexArray<vec4> shadowColor = mesh.getShadowColorArray<vec4>();
+    Mesh::VertexArray<vec3> shadowParams = mesh.getShadowParamsArray<vec3>();
+    shadows.fillVertices(position, shadowColor, shadowParams);
+    shadows.fillIndices(mesh.getIndicesArray());
+
+    mState.cornerRadius = 0.0f;
+    mState.drawShadows = true;
+    drawMesh(mesh);
+    mState.drawShadows = false;
+}
+
 } // namespace gl
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
index 397bc16..f41eda2 100644
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ b/libs/renderengine/gl/GLESRenderEngine.h
@@ -148,6 +148,8 @@
     bool waitFence(base::unique_fd fenceFd);
     void clearWithColor(float red, float green, float blue, float alpha);
     void fillRegionWithColor(const Region& region, float red, float green, float blue, float alpha);
+    void handleShadow(const FloatRect& casterRect, float casterCornerRadius,
+                      const ShadowSettings& shadowSettings);
     void setupLayerBlending(bool premultipliedAlpha, bool opaque, bool disableTexture,
                             const half4& color, float cornerRadius);
     void setupLayerTexturing(const Texture& texture);
diff --git a/libs/renderengine/gl/GLShadowVertexGenerator.cpp b/libs/renderengine/gl/GLShadowVertexGenerator.cpp
new file mode 100644
index 0000000..3181f9b
--- /dev/null
+++ b/libs/renderengine/gl/GLShadowVertexGenerator.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <renderengine/Mesh.h>
+
+#include <math/vec4.h>
+
+#include <ui/Rect.h>
+#include <ui/Transform.h>
+
+#include "GLShadowVertexGenerator.h"
+
+namespace android {
+namespace renderengine {
+namespace gl {
+
+GLShadowVertexGenerator::GLShadowVertexGenerator(const FloatRect& casterRect,
+                                                 float casterCornerRadius, float casterZ,
+                                                 bool casterIsTranslucent, const vec4& ambientColor,
+                                                 const vec4& spotColor, const vec3& lightPosition,
+                                                 float lightRadius) {
+    mDrawAmbientShadow = ambientColor.a > 0.f;
+    mDrawSpotShadow = spotColor.a > 0.f;
+
+    // Generate geometries and find number of vertices to generate
+    if (mDrawAmbientShadow) {
+        mAmbientShadowGeometry = getAmbientShadowGeometry(casterRect, casterCornerRadius, casterZ,
+                                                          casterIsTranslucent, ambientColor);
+        mAmbientShadowVertexCount = getVertexCountForGeometry(*mAmbientShadowGeometry.get());
+        mAmbientShadowIndexCount = getIndexCountForGeometry(*mAmbientShadowGeometry.get());
+    } else {
+        mAmbientShadowVertexCount = 0;
+        mAmbientShadowIndexCount = 0;
+    }
+
+    if (mDrawSpotShadow) {
+        mSpotShadowGeometry =
+                getSpotShadowGeometry(casterRect, casterCornerRadius, casterZ, casterIsTranslucent,
+                                      spotColor, lightPosition, lightRadius);
+        mSpotShadowVertexCount = getVertexCountForGeometry(*mSpotShadowGeometry.get());
+        mSpotShadowIndexCount = getIndexCountForGeometry(*mSpotShadowGeometry.get());
+    } else {
+        mSpotShadowVertexCount = 0;
+        mSpotShadowIndexCount = 0;
+    }
+}
+
+size_t GLShadowVertexGenerator::getVertexCount() const {
+    return mAmbientShadowVertexCount + mSpotShadowVertexCount;
+}
+
+size_t GLShadowVertexGenerator::getIndexCount() const {
+    return mAmbientShadowIndexCount + mSpotShadowIndexCount;
+}
+
+void GLShadowVertexGenerator::fillVertices(Mesh::VertexArray<vec2>& position,
+                                           Mesh::VertexArray<vec4>& color,
+                                           Mesh::VertexArray<vec3>& params) const {
+    if (mDrawAmbientShadow) {
+        fillVerticesForGeometry(*mAmbientShadowGeometry.get(), mAmbientShadowVertexCount, position,
+                                color, params);
+    }
+    if (mDrawSpotShadow) {
+        fillVerticesForGeometry(*mSpotShadowGeometry.get(), mSpotShadowVertexCount,
+                                Mesh::VertexArray<vec2>(position, mAmbientShadowVertexCount),
+                                Mesh::VertexArray<vec4>(color, mAmbientShadowVertexCount),
+                                Mesh::VertexArray<vec3>(params, mAmbientShadowVertexCount));
+    }
+}
+
+void GLShadowVertexGenerator::fillIndices(uint16_t* indices) const {
+    if (mDrawAmbientShadow) {
+        fillIndicesForGeometry(*mAmbientShadowGeometry.get(), mAmbientShadowIndexCount,
+                               0 /* starting vertex offset */, indices);
+    }
+    if (mDrawSpotShadow) {
+        fillIndicesForGeometry(*mSpotShadowGeometry.get(), mSpotShadowIndexCount,
+                               mAmbientShadowVertexCount /* starting vertex offset */,
+                               &(indices[mAmbientShadowIndexCount]));
+    }
+}
+
+} // namespace gl
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/gl/GLShadowVertexGenerator.h b/libs/renderengine/gl/GLShadowVertexGenerator.h
new file mode 100644
index 0000000..112f976
--- /dev/null
+++ b/libs/renderengine/gl/GLShadowVertexGenerator.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <math/vec4.h>
+#include <ui/Rect.h>
+
+#include "GLSkiaShadowPort.h"
+
+namespace android {
+namespace renderengine {
+
+class Mesh;
+
+namespace gl {
+
+/**
+ * Generates gl attributes required to draw shadow spot and/or ambient shadows.
+ *
+ * Each shadow can support different colors. This class generates three vertex attributes for
+ * each shadow, its position, color and shadow params(offset and distance). These can be sent
+ * using a single glDrawElements call.
+ */
+class GLShadowVertexGenerator {
+public:
+    GLShadowVertexGenerator(const FloatRect& casterRect, float casterCornerRadius, float casterZ,
+                            bool casterIsTranslucent, const vec4& ambientColor,
+                            const vec4& spotColor, const vec3& lightPosition, float lightRadius);
+    ~GLShadowVertexGenerator() = default;
+
+    size_t getVertexCount() const;
+    size_t getIndexCount() const;
+    void fillVertices(Mesh::VertexArray<vec2>& position, Mesh::VertexArray<vec4>& color,
+                      Mesh::VertexArray<vec3>& params) const;
+    void fillIndices(uint16_t* indices) const;
+
+private:
+    bool mDrawAmbientShadow;
+    std::unique_ptr<Geometry> mAmbientShadowGeometry;
+    int mAmbientShadowVertexCount = 0;
+    int mAmbientShadowIndexCount = 0;
+
+    bool mDrawSpotShadow;
+    std::unique_ptr<Geometry> mSpotShadowGeometry;
+    int mSpotShadowVertexCount = 0;
+    int mSpotShadowIndexCount = 0;
+};
+
+} // namespace gl
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/gl/GLSkiaShadowPort.cpp b/libs/renderengine/gl/GLSkiaShadowPort.cpp
new file mode 100644
index 0000000..224ce6c
--- /dev/null
+++ b/libs/renderengine/gl/GLSkiaShadowPort.cpp
@@ -0,0 +1,649 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <math/vec4.h>
+
+#include <renderengine/Mesh.h>
+
+#include <ui/Rect.h>
+#include <ui/Transform.h>
+
+#include <utils/Log.h>
+
+#include "GLSkiaShadowPort.h"
+
+namespace android {
+namespace renderengine {
+namespace gl {
+
+/**
+ * The shadow geometry logic and vertex generation code has been ported from skia shadow
+ * fast path OpenGL implementation to draw shadows around rects and rounded rects including
+ * circles.
+ *
+ * path: skia/src/gpu/GrRenderTargetContext.cpp GrRenderTargetContext::drawFastShadow
+ *
+ * Modifications made:
+ * - Switched to using std lib math functions
+ * - Fall off function is implemented in vertex shader rather than a shadow texture
+ * - Removed transformations applied on the caster rect since the caster will be in local
+ *   coordinate space and will be transformed by the vertex shader.
+ */
+
+static inline float divide_and_pin(float numer, float denom, float min, float max) {
+    if (denom == 0.0f) return min;
+    return std::clamp(numer / denom, min, max);
+}
+
+static constexpr auto SK_ScalarSqrt2 = 1.41421356f;
+static constexpr auto kAmbientHeightFactor = 1.0f / 128.0f;
+static constexpr auto kAmbientGeomFactor = 64.0f;
+// Assuming that we have a light height of 600 for the spot shadow,
+// the spot values will reach their maximum at a height of approximately 292.3077.
+// We'll round up to 300 to keep it simple.
+static constexpr auto kMaxAmbientRadius = 300 * kAmbientHeightFactor * kAmbientGeomFactor;
+
+inline float AmbientBlurRadius(float height) {
+    return std::min(height * kAmbientHeightFactor * kAmbientGeomFactor, kMaxAmbientRadius);
+}
+inline float AmbientRecipAlpha(float height) {
+    return 1.0f + std::max(height * kAmbientHeightFactor, 0.0f);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Circle Data
+//
+// We have two possible cases for geometry for a circle:
+
+// In the case of a normal fill, we draw geometry for the circle as an octagon.
+static const uint16_t gFillCircleIndices[] = {
+        // enter the octagon
+        // clang-format off
+         0, 1, 8, 1, 2, 8,
+         2, 3, 8, 3, 4, 8,
+         4, 5, 8, 5, 6, 8,
+         6, 7, 8, 7, 0, 8,
+        // clang-format on
+};
+
+// For stroked circles, we use two nested octagons.
+static const uint16_t gStrokeCircleIndices[] = {
+        // enter the octagon
+        // clang-format off
+         0, 1,  9, 0,  9,  8,
+         1, 2, 10, 1, 10,  9,
+         2, 3, 11, 2, 11, 10,
+         3, 4, 12, 3, 12, 11,
+         4, 5, 13, 4, 13, 12,
+         5, 6, 14, 5, 14, 13,
+         6, 7, 15, 6, 15, 14,
+         7, 0,  8, 7,  8, 15,
+        // clang-format on
+};
+
+#define SK_ARRAY_COUNT(a) (sizeof(a) / sizeof((a)[0]))
+static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices);
+static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices);
+static const int kVertsPerStrokeCircle = 16;
+static const int kVertsPerFillCircle = 9;
+
+static int circle_type_to_vert_count(bool stroked) {
+    return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
+}
+
+static int circle_type_to_index_count(bool stroked) {
+    return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
+}
+
+static const uint16_t* circle_type_to_indices(bool stroked) {
+    return stroked ? gStrokeCircleIndices : gFillCircleIndices;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RoundRect Data
+//
+// The geometry for a shadow roundrect is similar to a 9-patch:
+//    ____________
+//   |_|________|_|
+//   | |        | |
+//   | |        | |
+//   | |        | |
+//   |_|________|_|
+//   |_|________|_|
+//
+// However, each corner is rendered as a fan rather than a simple quad, as below. (The diagram
+// shows the upper part of the upper left corner. The bottom triangle would similarly be split
+// into two triangles.)
+//    ________
+//   |\  \   |
+//   |  \ \  |
+//   |    \\ |
+//   |      \|
+//   --------
+//
+// The center of the fan handles the curve of the corner. For roundrects where the stroke width
+// is greater than the corner radius, the outer triangles blend from the curve to the straight
+// sides. Otherwise these triangles will be degenerate.
+//
+// In the case where the stroke width is greater than the corner radius and the
+// blur radius (overstroke), we add additional geometry to mark out the rectangle in the center.
+// This rectangle extends the coverage values of the center edges of the 9-patch.
+//    ____________
+//   |_|________|_|
+//   | |\ ____ /| |
+//   | | |    | | |
+//   | | |____| | |
+//   |_|/______\|_|
+//   |_|________|_|
+//
+// For filled rrects we reuse the stroke geometry but add an additional quad to the center.
+
+static const uint16_t gRRectIndices[] = {
+        // clang-format off
+     // overstroke quads
+     // we place this at the beginning so that we can skip these indices when rendering as filled
+     0, 6, 25, 0, 25, 24,
+     6, 18, 27, 6, 27, 25,
+     18, 12, 26, 18, 26, 27,
+     12, 0, 24, 12, 24, 26,
+
+     // corners
+     0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5,
+     6, 11, 10, 6, 10, 9, 6, 9, 8, 6, 8, 7,
+     12, 17, 16, 12, 16, 15, 12, 15, 14, 12, 14, 13,
+     18, 19, 20, 18, 20, 21, 18, 21, 22, 18, 22, 23,
+
+     // edges
+     0, 5, 11, 0, 11, 6,
+     6, 7, 19, 6, 19, 18,
+     18, 23, 17, 18, 17, 12,
+     12, 13, 1, 12, 1, 0,
+
+     // fill quad
+     // we place this at the end so that we can skip these indices when rendering as stroked
+     0, 6, 18, 0, 18, 12,
+        // clang-format on
+};
+
+// overstroke count
+static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gRRectIndices) - 6;
+// simple stroke count skips overstroke indices
+static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6 * 4;
+// fill count adds final quad to stroke count
+static const int kIndicesPerFillRRect = kIndicesPerStrokeRRect + 6;
+static const int kVertsPerStrokeRRect = 24;
+static const int kVertsPerOverstrokeRRect = 28;
+static const int kVertsPerFillRRect = 24;
+
+static int rrect_type_to_vert_count(RRectType type) {
+    switch (type) {
+        case kFill_RRectType:
+            return kVertsPerFillRRect;
+        case kStroke_RRectType:
+            return kVertsPerStrokeRRect;
+        case kOverstroke_RRectType:
+            return kVertsPerOverstrokeRRect;
+    }
+    ALOGE("Invalid rect type: %d", type);
+    return -1;
+}
+
+static int rrect_type_to_index_count(RRectType type) {
+    switch (type) {
+        case kFill_RRectType:
+            return kIndicesPerFillRRect;
+        case kStroke_RRectType:
+            return kIndicesPerStrokeRRect;
+        case kOverstroke_RRectType:
+            return kIndicesPerOverstrokeRRect;
+    }
+    ALOGE("Invalid rect type: %d", type);
+    return -1;
+}
+
+static const uint16_t* rrect_type_to_indices(RRectType type) {
+    switch (type) {
+        case kFill_RRectType:
+        case kStroke_RRectType:
+            return gRRectIndices + 6 * 4;
+        case kOverstroke_RRectType:
+            return gRRectIndices;
+    }
+    ALOGE("Invalid rect type: %d", type);
+    return nullptr;
+}
+
+static void fillInCircleVerts(const Geometry& args, bool isStroked,
+                              Mesh::VertexArray<vec2>& position,
+                              Mesh::VertexArray<vec4>& shadowColor,
+                              Mesh::VertexArray<vec3>& shadowParams) {
+    vec4 color = args.fColor;
+    float outerRadius = args.fOuterRadius;
+    float innerRadius = args.fInnerRadius;
+    float blurRadius = args.fBlurRadius;
+    float distanceCorrection = outerRadius / blurRadius;
+
+    const FloatRect& bounds = args.fDevBounds;
+
+    // The inner radius in the vertex data must be specified in normalized space.
+    innerRadius = innerRadius / outerRadius;
+
+    vec2 center = vec2(bounds.getWidth() / 2.0f, bounds.getHeight() / 2.0f);
+    float halfWidth = 0.5f * bounds.getWidth();
+    float octOffset = 0.41421356237f; // sqrt(2) - 1
+    int vertexCount = 0;
+
+    position[vertexCount] = center + vec2(-octOffset * halfWidth, -halfWidth);
+    shadowColor[vertexCount] = color;
+    shadowParams[vertexCount] = vec3(-octOffset, -1, distanceCorrection);
+    vertexCount++;
+
+    position[vertexCount] = center + vec2(octOffset * halfWidth, -halfWidth);
+    shadowColor[vertexCount] = color;
+    shadowParams[vertexCount] = vec3(octOffset, -1, distanceCorrection);
+    vertexCount++;
+
+    position[vertexCount] = center + vec2(halfWidth, -octOffset * halfWidth);
+    shadowColor[vertexCount] = color;
+    shadowParams[vertexCount] = vec3(1, -octOffset, distanceCorrection);
+    vertexCount++;
+
+    position[vertexCount] = center + vec2(halfWidth, octOffset * halfWidth);
+    shadowColor[vertexCount] = color;
+    shadowParams[vertexCount] = vec3(1, octOffset, distanceCorrection);
+    vertexCount++;
+
+    position[vertexCount] = center + vec2(octOffset * halfWidth, halfWidth);
+    shadowColor[vertexCount] = color;
+    shadowParams[vertexCount] = vec3(octOffset, 1, distanceCorrection);
+    vertexCount++;
+
+    position[vertexCount] = center + vec2(-octOffset * halfWidth, halfWidth);
+    shadowColor[vertexCount] = color;
+    shadowParams[vertexCount] = vec3(-octOffset, 1, distanceCorrection);
+    vertexCount++;
+
+    position[vertexCount] = center + vec2(-halfWidth, octOffset * halfWidth);
+    shadowColor[vertexCount] = color;
+    shadowParams[vertexCount] = vec3(-1, octOffset, distanceCorrection);
+    vertexCount++;
+
+    position[vertexCount] = center + vec2(-halfWidth, -octOffset * halfWidth);
+    shadowColor[vertexCount] = color;
+    shadowParams[vertexCount] = vec3(-1, -octOffset, distanceCorrection);
+    vertexCount++;
+
+    if (isStroked) {
+        // compute the inner ring
+
+        // cosine and sine of pi/8
+        float c = 0.923579533f;
+        float s = 0.382683432f;
+        float r = args.fInnerRadius;
+
+        position[vertexCount] = center + vec2(-s * r, -c * r);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(-s * innerRadius, -c * innerRadius, distanceCorrection);
+        vertexCount++;
+
+        position[vertexCount] = center + vec2(s * r, -c * r);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(s * innerRadius, -c * innerRadius, distanceCorrection);
+        vertexCount++;
+
+        position[vertexCount] = center + vec2(c * r, -s * r);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(c * innerRadius, -s * innerRadius, distanceCorrection);
+        vertexCount++;
+
+        position[vertexCount] = center + vec2(c * r, s * r);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(c * innerRadius, s * innerRadius, distanceCorrection);
+        vertexCount++;
+
+        position[vertexCount] = center + vec2(s * r, c * r);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(s * innerRadius, c * innerRadius, distanceCorrection);
+        vertexCount++;
+
+        position[vertexCount] = center + vec2(-s * r, c * r);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(-s * innerRadius, c * innerRadius, distanceCorrection);
+        vertexCount++;
+
+        position[vertexCount] = center + vec2(-c * r, s * r);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(-c * innerRadius, s * innerRadius, distanceCorrection);
+        vertexCount++;
+
+        position[vertexCount] = center + vec2(-c * r, -s * r);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(-c * innerRadius, -s * innerRadius, distanceCorrection);
+        vertexCount++;
+    } else {
+        // filled
+        position[vertexCount] = center;
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
+        vertexCount++;
+    }
+}
+
+static void fillInRRectVerts(const Geometry& args, Mesh::VertexArray<vec2>& position,
+                             Mesh::VertexArray<vec4>& shadowColor,
+                             Mesh::VertexArray<vec3>& shadowParams) {
+    vec4 color = args.fColor;
+    float outerRadius = args.fOuterRadius;
+
+    const FloatRect& bounds = args.fDevBounds;
+
+    float umbraInset = args.fUmbraInset;
+    float minDim = 0.5f * std::min(bounds.getWidth(), bounds.getHeight());
+    if (umbraInset > minDim) {
+        umbraInset = minDim;
+    }
+
+    float xInner[4] = {bounds.left + umbraInset, bounds.right - umbraInset,
+                       bounds.left + umbraInset, bounds.right - umbraInset};
+    float xMid[4] = {bounds.left + outerRadius, bounds.right - outerRadius,
+                     bounds.left + outerRadius, bounds.right - outerRadius};
+    float xOuter[4] = {bounds.left, bounds.right, bounds.left, bounds.right};
+    float yInner[4] = {bounds.top + umbraInset, bounds.top + umbraInset, bounds.bottom - umbraInset,
+                       bounds.bottom - umbraInset};
+    float yMid[4] = {bounds.top + outerRadius, bounds.top + outerRadius,
+                     bounds.bottom - outerRadius, bounds.bottom - outerRadius};
+    float yOuter[4] = {bounds.top, bounds.top, bounds.bottom, bounds.bottom};
+
+    float blurRadius = args.fBlurRadius;
+
+    // In the case where we have to inset more for the umbra, our two triangles in the
+    // corner get skewed to a diamond rather than a square. To correct for that,
+    // we also skew the vectors we send to the shader that help define the circle.
+    // By doing so, we end up with a quarter circle in the corner rather than the
+    // elliptical curve.
+
+    // This is a bit magical, but it gives us the correct results at extrema:
+    //   a) umbraInset == outerRadius produces an orthogonal vector
+    //   b) outerRadius == 0 produces a diagonal vector
+    // And visually the corner looks correct.
+    vec2 outerVec = vec2(outerRadius - umbraInset, -outerRadius - umbraInset);
+    outerVec = normalize(outerVec);
+    // We want the circle edge to fall fractionally along the diagonal at
+    //      (sqrt(2)*(umbraInset - outerRadius) + outerRadius)/sqrt(2)*umbraInset
+    //
+    // Setting the components of the diagonal offset to the following value will give us that.
+    float diagVal = umbraInset / (SK_ScalarSqrt2 * (outerRadius - umbraInset) - outerRadius);
+    vec2 diagVec = vec2(diagVal, diagVal);
+    float distanceCorrection = umbraInset / blurRadius;
+
+    int vertexCount = 0;
+    // build corner by corner
+    for (int i = 0; i < 4; ++i) {
+        // inner point
+        position[vertexCount] = vec2(xInner[i], yInner[i]);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
+        vertexCount++;
+
+        // outer points
+        position[vertexCount] = vec2(xOuter[i], yInner[i]);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(0, -1, distanceCorrection);
+        vertexCount++;
+
+        position[vertexCount] = vec2(xOuter[i], yMid[i]);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(outerVec.x, outerVec.y, distanceCorrection);
+        vertexCount++;
+
+        position[vertexCount] = vec2(xOuter[i], yOuter[i]);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(diagVec.x, diagVec.y, distanceCorrection);
+        vertexCount++;
+
+        position[vertexCount] = vec2(xMid[i], yOuter[i]);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(outerVec.x, outerVec.y, distanceCorrection);
+        vertexCount++;
+
+        position[vertexCount] = vec2(xInner[i], yOuter[i]);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(0, -1, distanceCorrection);
+        vertexCount++;
+    }
+
+    // Add the additional vertices for overstroked rrects.
+    // Effectively this is an additional stroked rrect, with its
+    // parameters equal to those in the center of the 9-patch. This will
+    // give constant values across this inner ring.
+    if (kOverstroke_RRectType == args.fType) {
+        float inset = umbraInset + args.fInnerRadius;
+
+        // TL
+        position[vertexCount] = vec2(bounds.left + inset, bounds.top + inset);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
+        vertexCount++;
+
+        // TR
+        position[vertexCount] = vec2(bounds.right - inset, bounds.top + inset);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
+        vertexCount++;
+
+        // BL
+        position[vertexCount] = vec2(bounds.left + inset, bounds.bottom - inset);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
+        vertexCount++;
+
+        // BR
+        position[vertexCount] = vec2(bounds.right - inset, bounds.bottom - inset);
+        shadowColor[vertexCount] = color;
+        shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
+        vertexCount++;
+    }
+}
+
+int getVertexCountForGeometry(const Geometry& shadowGeometry) {
+    if (shadowGeometry.fIsCircle) {
+        return circle_type_to_vert_count(shadowGeometry.fType);
+    }
+
+    return rrect_type_to_vert_count(shadowGeometry.fType);
+}
+
+int getIndexCountForGeometry(const Geometry& shadowGeometry) {
+    if (shadowGeometry.fIsCircle) {
+        return circle_type_to_index_count(kStroke_RRectType == shadowGeometry.fType);
+    }
+
+    return rrect_type_to_index_count(shadowGeometry.fType);
+}
+
+void fillVerticesForGeometry(const Geometry& shadowGeometry, int /* vertexCount */,
+                             Mesh::VertexArray<vec2> position, Mesh::VertexArray<vec4> shadowColor,
+                             Mesh::VertexArray<vec3> shadowParams) {
+    if (shadowGeometry.fIsCircle) {
+        fillInCircleVerts(shadowGeometry, shadowGeometry.fIsStroked, position, shadowColor,
+                          shadowParams);
+    } else {
+        fillInRRectVerts(shadowGeometry, position, shadowColor, shadowParams);
+    }
+}
+
+void fillIndicesForGeometry(const Geometry& shadowGeometry, int indexCount,
+                            int startingVertexOffset, uint16_t* indices) {
+    if (shadowGeometry.fIsCircle) {
+        const uint16_t* primIndices = circle_type_to_indices(shadowGeometry.fIsStroked);
+        for (int i = 0; i < indexCount; ++i) {
+            indices[i] = primIndices[i] + startingVertexOffset;
+        }
+    } else {
+        const uint16_t* primIndices = rrect_type_to_indices(shadowGeometry.fType);
+        for (int i = 0; i < indexCount; ++i) {
+            indices[i] = primIndices[i] + startingVertexOffset;
+        }
+    }
+}
+
+inline void GetSpotParams(float occluderZ, float lightX, float lightY, float lightZ,
+                          float lightRadius, float& blurRadius, float& scale, vec2& translate) {
+    float zRatio = divide_and_pin(occluderZ, lightZ - occluderZ, 0.0f, 0.95f);
+    blurRadius = lightRadius * zRatio;
+    scale = divide_and_pin(lightZ, lightZ - occluderZ, 1.0f, 1.95f);
+    translate.x = -zRatio * lightX;
+    translate.y = -zRatio * lightY;
+}
+
+static std::unique_ptr<Geometry> getShadowGeometry(const vec4& color, const FloatRect& devRect,
+                                                   float devRadius, float blurRadius,
+                                                   float insetWidth) {
+    // An insetWidth > 1/2 rect width or height indicates a simple fill.
+    const bool isCircle = ((devRadius >= devRect.getWidth()) && (devRadius >= devRect.getHeight()));
+
+    FloatRect bounds = devRect;
+    float innerRadius = 0.0f;
+    float outerRadius = devRadius;
+    float umbraInset;
+
+    RRectType type = kFill_RRectType;
+    if (isCircle) {
+        umbraInset = 0;
+    } else {
+        umbraInset = std::max(outerRadius, blurRadius);
+    }
+
+    // If stroke is greater than width or height, this is still a fill,
+    // otherwise we compute stroke params.
+    if (isCircle) {
+        innerRadius = devRadius - insetWidth;
+        type = innerRadius > 0 ? kStroke_RRectType : kFill_RRectType;
+    } else {
+        if (insetWidth <= 0.5f * std::min(devRect.getWidth(), devRect.getHeight())) {
+            // We don't worry about a real inner radius, we just need to know if we
+            // need to create overstroke vertices.
+            innerRadius = std::max(insetWidth - umbraInset, 0.0f);
+            type = innerRadius > 0 ? kOverstroke_RRectType : kStroke_RRectType;
+        }
+    }
+    const bool isStroked = (kStroke_RRectType == type);
+    return std::make_unique<Geometry>(Geometry{color, outerRadius, umbraInset, innerRadius,
+                                               blurRadius, bounds, type, isCircle, isStroked});
+}
+
+std::unique_ptr<Geometry> getAmbientShadowGeometry(const FloatRect& casterRect,
+                                                   float casterCornerRadius, float casterZ,
+                                                   bool casterIsTranslucent,
+                                                   const vec4& ambientColor) {
+    float devSpaceInsetWidth = AmbientBlurRadius(casterZ);
+    const float umbraRecipAlpha = AmbientRecipAlpha(casterZ);
+    const float devSpaceAmbientBlur = devSpaceInsetWidth * umbraRecipAlpha;
+
+    // Outset the shadow rrect to the border of the penumbra
+    float ambientPathOutset = devSpaceInsetWidth;
+    FloatRect outsetRect(casterRect);
+    outsetRect.left -= ambientPathOutset;
+    outsetRect.top -= ambientPathOutset;
+    outsetRect.right += ambientPathOutset;
+    outsetRect.bottom += ambientPathOutset;
+
+    float outsetRad = casterCornerRadius + ambientPathOutset;
+    if (casterIsTranslucent) {
+        // set a large inset to force a fill
+        devSpaceInsetWidth = outsetRect.getWidth();
+    }
+
+    return getShadowGeometry(ambientColor, outsetRect, std::abs(outsetRad), devSpaceAmbientBlur,
+                             std::abs(devSpaceInsetWidth));
+}
+
+std::unique_ptr<Geometry> getSpotShadowGeometry(const FloatRect& casterRect,
+                                                float casterCornerRadius, float casterZ,
+                                                bool casterIsTranslucent, const vec4& spotColor,
+                                                const vec3& lightPosition, float lightRadius) {
+    float devSpaceSpotBlur;
+    float spotScale;
+    vec2 spotOffset;
+    GetSpotParams(casterZ, lightPosition.x, lightPosition.y, lightPosition.z, lightRadius,
+                  devSpaceSpotBlur, spotScale, spotOffset);
+    // handle scale of radius due to CTM
+    const float srcSpaceSpotBlur = devSpaceSpotBlur;
+
+    // Adjust translate for the effect of the scale.
+    spotOffset.x += spotScale;
+    spotOffset.y += spotScale;
+
+    // Compute the transformed shadow rect
+    ui::Transform shadowTransform;
+    shadowTransform.set(spotOffset.x, spotOffset.y);
+    shadowTransform.set(spotScale, 0, 0, spotScale);
+    FloatRect spotShadowRect = shadowTransform.transform(casterRect);
+    float spotShadowRadius = casterCornerRadius * spotScale;
+
+    // Compute the insetWidth
+    float blurOutset = srcSpaceSpotBlur;
+    float insetWidth = blurOutset;
+    if (casterIsTranslucent) {
+        // If transparent, just do a fill
+        insetWidth += spotShadowRect.getWidth();
+    } else {
+        // For shadows, instead of using a stroke we specify an inset from the penumbra
+        // border. We want to extend this inset area so that it meets up with the caster
+        // geometry. The inset geometry will by default already be inset by the blur width.
+        //
+        // We compare the min and max corners inset by the radius between the original
+        // rrect and the shadow rrect. The distance between the two plus the difference
+        // between the scaled radius and the original radius gives the distance from the
+        // transformed shadow shape to the original shape in that corner. The max
+        // of these gives the maximum distance we need to cover.
+        //
+        // Since we are outsetting by 1/2 the blur distance, we just add the maxOffset to
+        // that to get the full insetWidth.
+        float maxOffset;
+        if (casterCornerRadius <= 0.f) {
+            // Manhattan distance works better for rects
+            maxOffset = std::max(std::max(std::abs(spotShadowRect.left - casterRect.left),
+                                          std::abs(spotShadowRect.top - casterRect.top)),
+                                 std::max(std::abs(spotShadowRect.right - casterRect.right),
+                                          std::abs(spotShadowRect.bottom - casterRect.bottom)));
+        } else {
+            float dr = spotShadowRadius - casterCornerRadius;
+            vec2 upperLeftOffset = vec2(spotShadowRect.left - casterRect.left + dr,
+                                        spotShadowRect.top - casterRect.top + dr);
+            vec2 lowerRightOffset = vec2(spotShadowRect.right - casterRect.right - dr,
+                                         spotShadowRect.bottom - casterRect.bottom - dr);
+            maxOffset = sqrt(std::max(dot(upperLeftOffset, lowerRightOffset),
+                                      dot(lowerRightOffset, lowerRightOffset))) +
+                    dr;
+        }
+        insetWidth += std::max(blurOutset, maxOffset);
+    }
+
+    // Outset the shadow rrect to the border of the penumbra
+    spotShadowRadius += blurOutset;
+    spotShadowRect.left -= blurOutset;
+    spotShadowRect.top -= blurOutset;
+    spotShadowRect.right += blurOutset;
+    spotShadowRect.bottom += blurOutset;
+
+    return getShadowGeometry(spotColor, spotShadowRect, std::abs(spotShadowRadius),
+                             2.0f * devSpaceSpotBlur, std::abs(insetWidth));
+}
+
+} // namespace gl
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/gl/GLSkiaShadowPort.h b/libs/renderengine/gl/GLSkiaShadowPort.h
new file mode 100644
index 0000000..e7d1861
--- /dev/null
+++ b/libs/renderengine/gl/GLSkiaShadowPort.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <math/vec4.h>
+#include <ui/Rect.h>
+
+namespace android {
+namespace renderengine {
+
+class Mesh;
+
+namespace gl {
+
+/**
+ * The shadow geometry logic and vertex generation code has been ported from skia shadow
+ * fast path OpenGL implementation to draw shadows around rects and rounded rects including
+ * circles.
+ *
+ * path: skia/src/gpu/GrRenderTargetContext.cpp GrRenderTargetContext::drawFastShadow
+ *
+ * Modifications made:
+ * - Switched to using std lib math functions
+ * - Fall off function is implemented in vertex shader rather than a shadow texture
+ * - Removed transformations applied on the caster rect since the caster will be in local
+ *   coordinate space and will be transformed by the vertex shader.
+ */
+
+enum RRectType {
+    kFill_RRectType,
+    kStroke_RRectType,
+    kOverstroke_RRectType,
+};
+
+struct Geometry {
+    vec4 fColor;
+    float fOuterRadius;
+    float fUmbraInset;
+    float fInnerRadius;
+    float fBlurRadius;
+    FloatRect fDevBounds;
+    RRectType fType;
+    bool fIsCircle;
+    bool fIsStroked;
+};
+
+std::unique_ptr<Geometry> getSpotShadowGeometry(const FloatRect& casterRect,
+                                                float casterCornerRadius, float casterZ,
+                                                bool casterIsTranslucent, const vec4& spotColor,
+                                                const vec3& lightPosition, float lightRadius);
+
+std::unique_ptr<Geometry> getAmbientShadowGeometry(const FloatRect& casterRect,
+                                                   float casterCornerRadius, float casterZ,
+                                                   bool casterIsTranslucent,
+                                                   const vec4& ambientColor);
+
+int getVertexCountForGeometry(const Geometry& shadowGeometry);
+
+int getIndexCountForGeometry(const Geometry& shadowGeometry);
+
+void fillVerticesForGeometry(const Geometry& shadowGeometry, int vertexCount,
+                             Mesh::VertexArray<vec2> position, Mesh::VertexArray<vec4> shadowColor,
+                             Mesh::VertexArray<vec3> shadowParams);
+
+void fillIndicesForGeometry(const Geometry& shadowGeometry, int indexCount,
+                            int startingVertexOffset, uint16_t* indices);
+
+} // namespace gl
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/gl/Program.cpp b/libs/renderengine/gl/Program.cpp
index fe9d909..f4fbf35 100644
--- a/libs/renderengine/gl/Program.cpp
+++ b/libs/renderengine/gl/Program.cpp
@@ -37,6 +37,8 @@
     glBindAttribLocation(programId, position, "position");
     glBindAttribLocation(programId, texCoords, "texCoords");
     glBindAttribLocation(programId, cropCoords, "cropCoords");
+    glBindAttribLocation(programId, shadowColor, "shadowColor");
+    glBindAttribLocation(programId, shadowParams, "shadowParams");
     glLinkProgram(programId);
 
     GLint status;
@@ -65,6 +67,8 @@
         mSamplerLoc = glGetUniformLocation(programId, "sampler");
         mColorLoc = glGetUniformLocation(programId, "color");
         mDisplayMaxLuminanceLoc = glGetUniformLocation(programId, "displayMaxLuminance");
+        mMaxMasteringLuminanceLoc = glGetUniformLocation(programId, "maxMasteringLuminance");
+        mMaxContentLuminanceLoc = glGetUniformLocation(programId, "maxContentLuminance");
         mInputTransformMatrixLoc = glGetUniformLocation(programId, "inputTransformMatrix");
         mOutputTransformMatrixLoc = glGetUniformLocation(programId, "outputTransformMatrix");
         mCornerRadiusLoc = glGetUniformLocation(programId, "cornerRadius");
@@ -138,6 +142,12 @@
     if (mDisplayMaxLuminanceLoc >= 0) {
         glUniform1f(mDisplayMaxLuminanceLoc, desc.displayMaxLuminance);
     }
+    if (mMaxMasteringLuminanceLoc >= 0) {
+        glUniform1f(mMaxMasteringLuminanceLoc, desc.maxMasteringLuminance);
+    }
+    if (mMaxContentLuminanceLoc >= 0) {
+        glUniform1f(mMaxContentLuminanceLoc, desc.maxContentLuminance);
+    }
     if (mCornerRadiusLoc >= 0) {
         glUniform1f(mCornerRadiusLoc, desc.cornerRadius);
     }
diff --git a/libs/renderengine/gl/Program.h b/libs/renderengine/gl/Program.h
index bc9cf08..fc3755e 100644
--- a/libs/renderengine/gl/Program.h
+++ b/libs/renderengine/gl/Program.h
@@ -44,7 +44,13 @@
         texCoords = 1,
 
         /* Crop coordinates, in pixels */
-        cropCoords = 2
+        cropCoords = 2,
+
+        /* Shadow color */
+        shadowColor = 3,
+
+        /* Shadow params */
+        shadowParams = 4,
     };
 
     Program(const ProgramCache::Key& needs, const char* vertex, const char* fragment);
@@ -90,6 +96,10 @@
 
     /* location of display luminance uniform */
     GLint mDisplayMaxLuminanceLoc;
+    /* location of max mastering luminance uniform */
+    GLint mMaxMasteringLuminanceLoc;
+    /* location of max content luminance uniform */
+    GLint mMaxContentLuminanceLoc;
 
     /* location of transform matrix */
     GLint mInputTransformMatrixLoc;
diff --git a/libs/renderengine/gl/ProgramCache.cpp b/libs/renderengine/gl/ProgramCache.cpp
index 494623e..ba0e4ad 100644
--- a/libs/renderengine/gl/ProgramCache.cpp
+++ b/libs/renderengine/gl/ProgramCache.cpp
@@ -174,16 +174,15 @@
             .set(Key::OPACITY_MASK,
                  description.isOpaque ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT)
             .set(Key::Key::INPUT_TRANSFORM_MATRIX_MASK,
-                 description.hasInputTransformMatrix()
-                         ? Key::INPUT_TRANSFORM_MATRIX_ON : Key::INPUT_TRANSFORM_MATRIX_OFF)
+                 description.hasInputTransformMatrix() ? Key::INPUT_TRANSFORM_MATRIX_ON
+                                                       : Key::INPUT_TRANSFORM_MATRIX_OFF)
             .set(Key::Key::OUTPUT_TRANSFORM_MATRIX_MASK,
                  description.hasOutputTransformMatrix() || description.hasColorMatrix()
                          ? Key::OUTPUT_TRANSFORM_MATRIX_ON
                          : Key::OUTPUT_TRANSFORM_MATRIX_OFF)
             .set(Key::ROUNDED_CORNERS_MASK,
-                 description.cornerRadius > 0
-                         ? Key::ROUNDED_CORNERS_ON : Key::ROUNDED_CORNERS_OFF);
-
+                 description.cornerRadius > 0 ? Key::ROUNDED_CORNERS_ON : Key::ROUNDED_CORNERS_OFF)
+            .set(Key::SHADOW_MASK, description.drawShadows ? Key::SHADOW_ON : Key::SHADOW_OFF);
     needs.set(Key::Y410_BT2020_MASK,
               description.isY410BT2020 ? Key::Y410_BT2020_ON : Key::Y410_BT2020_OFF);
 
@@ -339,9 +338,9 @@
                 default:
                     fs << R"__SHADER__(
                         highp vec3 ToneMap(highp vec3 color) {
-                            const float maxMasteringLumi = 1000.0;
-                            const float maxContentLumi = 1000.0;
-                            const float maxInLumi = min(maxMasteringLumi, maxContentLumi);
+                            float maxMasteringLumi = maxMasteringLuminance;
+                            float maxContentLumi = maxContentLuminance;
+                            float maxInLumi = min(maxMasteringLumi, maxContentLumi);
                             float maxOutLumi = displayMaxLuminance;
 
                             float nits = color.y;
@@ -559,6 +558,12 @@
         vs << "attribute lowp vec4 cropCoords;";
         vs << "varying lowp vec2 outCropCoords;";
     }
+    if (needs.drawShadows()) {
+        vs << "attribute vec4 shadowColor;";
+        vs << "varying vec4 outShadowColor;";
+        vs << "attribute vec4 shadowParams;";
+        vs << "varying vec3 outShadowParams;";
+    }
     vs << "attribute vec4 position;"
        << "uniform mat4 projection;"
        << "uniform mat4 texture;"
@@ -569,6 +574,10 @@
     if (needs.hasRoundedCorners()) {
         vs << "outCropCoords = cropCoords.st;";
     }
+    if (needs.drawShadows()) {
+        vs << "outShadowColor = shadowColor;";
+        vs << "outShadowParams = shadowParams.xyz;";
+    }
     vs << dedent << "}";
     return vs.getString();
 }
@@ -614,6 +623,26 @@
             )__SHADER__";
     }
 
+    if (needs.drawShadows()) {
+        fs << R"__SHADER__(
+            varying vec4 outShadowColor;
+            varying vec3 outShadowParams;
+
+            /**
+             * Returns the shadow color.
+             */
+            vec4 getShadowColor()
+            {
+                // exponential falloff function provided by UX
+                float d = length(outShadowParams.xy);
+                float distance = outShadowParams.z * (1.0 - d);
+                float factor = 1.0 - clamp(distance, 0.0, 1.0);
+                factor = exp(-factor * factor * 4.0) - 0.018;
+                return outShadowColor * factor;
+            }
+            )__SHADER__";
+    }
+
     if (needs.getTextureTarget() == Key::TEXTURE_OFF || needs.hasAlpha()) {
         fs << "uniform vec4 color;";
     }
@@ -633,9 +662,10 @@
     }
 
     if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF())) {
-        // Currently, display maximum luminance is needed when doing tone mapping.
         if (needs.needsToneMapping()) {
             fs << "uniform float displayMaxLuminance;";
+            fs << "uniform float maxMasteringLuminance;";
+            fs << "uniform float maxContentLuminance;";
         }
 
         if (needs.hasInputTransformMatrix()) {
@@ -676,25 +706,29 @@
     }
 
     fs << "void main(void) {" << indent;
-    if (needs.isTexturing()) {
-        fs << "gl_FragColor = texture2D(sampler, outTexCoords);";
-        if (needs.isY410BT2020()) {
-            fs << "gl_FragColor.rgb = convertY410BT2020(gl_FragColor.rgb);";
-        }
+    if (needs.drawShadows()) {
+        fs << "gl_FragColor = getShadowColor();";
     } else {
-        fs << "gl_FragColor.rgb = color.rgb;";
-        fs << "gl_FragColor.a = 1.0;";
-    }
-    if (needs.isOpaque()) {
-        fs << "gl_FragColor.a = 1.0;";
-    }
-    if (needs.hasAlpha()) {
-        // modulate the current alpha value with alpha set
-        if (needs.isPremultiplied()) {
-            // ... and the color too if we're premultiplied
-            fs << "gl_FragColor *= color.a;";
+        if (needs.isTexturing()) {
+            fs << "gl_FragColor = texture2D(sampler, outTexCoords);";
+            if (needs.isY410BT2020()) {
+                fs << "gl_FragColor.rgb = convertY410BT2020(gl_FragColor.rgb);";
+            }
         } else {
-            fs << "gl_FragColor.a *= color.a;";
+            fs << "gl_FragColor.rgb = color.rgb;";
+            fs << "gl_FragColor.a = 1.0;";
+        }
+        if (needs.isOpaque()) {
+            fs << "gl_FragColor.a = 1.0;";
+        }
+        if (needs.hasAlpha()) {
+            // modulate the current alpha value with alpha set
+            if (needs.isPremultiplied()) {
+                // ... and the color too if we're premultiplied
+                fs << "gl_FragColor *= color.a;";
+            } else {
+                fs << "gl_FragColor.a *= color.a;";
+            }
         }
     }
 
diff --git a/libs/renderengine/gl/ProgramCache.h b/libs/renderengine/gl/ProgramCache.h
index 175c6e8..c8b6da7 100644
--- a/libs/renderengine/gl/ProgramCache.h
+++ b/libs/renderengine/gl/ProgramCache.h
@@ -112,6 +112,11 @@
             Y410_BT2020_MASK = 1 << Y410_BT2020_SHIFT,
             Y410_BT2020_OFF = 0 << Y410_BT2020_SHIFT,
             Y410_BT2020_ON = 1 << Y410_BT2020_SHIFT,
+
+            SHADOW_SHIFT = 13,
+            SHADOW_MASK = 1 << SHADOW_SHIFT,
+            SHADOW_OFF = 0 << SHADOW_SHIFT,
+            SHADOW_ON = 1 << SHADOW_SHIFT,
         };
 
         inline Key() : mKey(0) {}
@@ -130,6 +135,7 @@
         inline bool hasRoundedCorners() const {
             return (mKey & ROUNDED_CORNERS_MASK) == ROUNDED_CORNERS_ON;
         }
+        inline bool drawShadows() const { return (mKey & SHADOW_MASK) == SHADOW_ON; }
         inline bool hasInputTransformMatrix() const {
             return (mKey & INPUT_TRANSFORM_MATRIX_MASK) == INPUT_TRANSFORM_MATRIX_ON;
         }
diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index 9c9884a..c4a29a9 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <iosfwd>
+
 #include <math/mat4.h>
 #include <ui/GraphicTypes.h>
 #include <ui/Rect.h>
@@ -62,5 +64,31 @@
     uint32_t orientation = ui::Transform::ROT_0;
 };
 
+static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) {
+    return lhs.physicalDisplay == rhs.physicalDisplay && lhs.clip == rhs.clip &&
+            lhs.globalTransform == rhs.globalTransform && lhs.maxLuminance == rhs.maxLuminance &&
+            lhs.outputDataspace == rhs.outputDataspace &&
+            lhs.colorTransform == rhs.colorTransform &&
+            lhs.clearRegion.hasSameRects(rhs.clearRegion) && lhs.orientation == rhs.orientation;
+}
+
+// Defining PrintTo helps with Google Tests.
+static inline void PrintTo(const DisplaySettings& settings, ::std::ostream* os) {
+    *os << "DisplaySettings {";
+    *os << "\n    .physicalDisplay = ";
+    PrintTo(settings.physicalDisplay, os);
+    *os << "\n    .clip = ";
+    PrintTo(settings.clip, os);
+    *os << "\n    .globalTransform = " << settings.globalTransform;
+    *os << "\n    .maxLuminance = " << settings.maxLuminance;
+    *os << "\n    .outputDataspace = ";
+    PrintTo(settings.outputDataspace, os);
+    *os << "\n    .colorTransform = " << settings.colorTransform;
+    *os << "\n    .clearRegion = ";
+    PrintTo(settings.clearRegion, os);
+    *os << "\n    .orientation = " << settings.orientation;
+    *os << "\n}";
+}
+
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index b8bf801..5aa3f3b 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <iosfwd>
+
 #include <math/mat4.h>
 #include <math/vec3.h>
 #include <renderengine/Texture.h>
@@ -60,6 +62,8 @@
 
     // HDR color-space setting for Y410.
     bool isY410BT2020 = false;
+    float maxMasteringLuminance = 0.0;
+    float maxContentLuminance = 0.0;
 };
 
 // Metadata describing the layer geometry.
@@ -96,6 +100,33 @@
     half3 solidColor = half3(0.0f, 0.0f, 0.0f);
 };
 
+/*
+ * Contains the configuration for the shadows drawn by single layer. Shadow follows
+ * material design guidelines.
+ */
+struct ShadowSettings {
+    // Color to the ambient shadow. The alpha is premultiplied.
+    vec4 ambientColor = vec4();
+
+    // Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow
+    // depends on the light position.
+    vec4 spotColor = vec4();
+
+    // Position of the light source used to cast the spot shadow.
+    vec3 lightPos = vec3();
+
+    // Radius of the spot light source. Smaller radius will have sharper edges,
+    // larger radius will have softer shadows
+    float lightRadius = 0.f;
+
+    // Length of the cast shadow. If length is <= 0.f no shadows will be drawn.
+    float length = 0.f;
+
+    // If true fill in the casting layer is translucent and the shadow needs to fill the bounds.
+    // Otherwise the shadow will only be drawn around the edges of the casting layer.
+    bool casterIsTranslucent = false;
+};
+
 // The settings that RenderEngine requires for correctly rendering a Layer.
 struct LayerSettings {
     // Geometry information
@@ -116,7 +147,106 @@
 
     // True if blending will be forced to be disabled.
     bool disableBlending = false;
+
+    ShadowSettings shadow;
 };
 
+static inline bool operator==(const Buffer& lhs, const Buffer& rhs) {
+    return lhs.buffer == rhs.buffer && lhs.fence == rhs.fence &&
+            lhs.textureName == rhs.textureName &&
+            lhs.useTextureFiltering == rhs.useTextureFiltering &&
+            lhs.textureTransform == rhs.textureTransform &&
+            lhs.usePremultipliedAlpha == rhs.usePremultipliedAlpha &&
+            lhs.isOpaque == rhs.isOpaque && lhs.isY410BT2020 == rhs.isY410BT2020 &&
+            lhs.maxMasteringLuminance == rhs.maxMasteringLuminance &&
+            lhs.maxContentLuminance == rhs.maxContentLuminance;
+}
+
+static inline bool operator==(const Geometry& lhs, const Geometry& rhs) {
+    return lhs.boundaries == rhs.boundaries && lhs.positionTransform == rhs.positionTransform &&
+            lhs.roundedCornersRadius == rhs.roundedCornersRadius &&
+            lhs.roundedCornersCrop == rhs.roundedCornersCrop;
+}
+
+static inline bool operator==(const PixelSource& lhs, const PixelSource& rhs) {
+    return lhs.buffer == rhs.buffer && lhs.solidColor == rhs.solidColor;
+}
+
+static inline bool operator==(const ShadowSettings& lhs, const ShadowSettings& rhs) {
+    return lhs.ambientColor == rhs.ambientColor && lhs.spotColor == rhs.spotColor &&
+            lhs.lightPos == rhs.lightPos && lhs.lightRadius == rhs.lightRadius &&
+            lhs.length == rhs.length && lhs.casterIsTranslucent == rhs.casterIsTranslucent;
+}
+
+static inline bool operator==(const LayerSettings& lhs, const LayerSettings& rhs) {
+    return lhs.geometry == rhs.geometry && lhs.source == rhs.source && lhs.alpha == rhs.alpha &&
+            lhs.sourceDataspace == rhs.sourceDataspace &&
+            lhs.colorTransform == rhs.colorTransform &&
+            lhs.disableBlending == rhs.disableBlending && lhs.shadow == rhs.shadow;
+}
+
+// Defining PrintTo helps with Google Tests.
+
+static inline void PrintTo(const Buffer& settings, ::std::ostream* os) {
+    *os << "Buffer {";
+    *os << "\n    .buffer = " << settings.buffer.get();
+    *os << "\n    .fence = " << settings.fence.get();
+    *os << "\n    .textureName = " << settings.textureName;
+    *os << "\n    .useTextureFiltering = " << settings.useTextureFiltering;
+    *os << "\n    .textureTransform = " << settings.textureTransform;
+    *os << "\n    .usePremultipliedAlpha = " << settings.usePremultipliedAlpha;
+    *os << "\n    .isOpaque = " << settings.isOpaque;
+    *os << "\n    .isY410BT2020 = " << settings.isY410BT2020;
+    *os << "\n    .maxMasteringLuminance = " << settings.maxMasteringLuminance;
+    *os << "\n    .maxContentLuminance = " << settings.maxContentLuminance;
+    *os << "\n}";
+}
+
+static inline void PrintTo(const Geometry& settings, ::std::ostream* os) {
+    *os << "Geometry {";
+    *os << "\n    .boundaries = ";
+    PrintTo(settings.boundaries, os);
+    *os << "\n    .positionTransform = " << settings.positionTransform;
+    *os << "\n    .roundedCornersRadius = " << settings.roundedCornersRadius;
+    *os << "\n    .roundedCornersCrop = ";
+    PrintTo(settings.roundedCornersCrop, os);
+    *os << "\n}";
+}
+
+static inline void PrintTo(const PixelSource& settings, ::std::ostream* os) {
+    *os << "PixelSource {";
+    *os << "\n    .buffer = ";
+    PrintTo(settings.buffer, os);
+    *os << "\n    .solidColor = " << settings.solidColor;
+    *os << "\n}";
+}
+
+static inline void PrintTo(const ShadowSettings& settings, ::std::ostream* os) {
+    *os << "ShadowSettings {";
+    *os << "\n    .ambientColor = " << settings.ambientColor;
+    *os << "\n    .spotColor = " << settings.spotColor;
+    *os << "\n    .lightPos = " << settings.lightPos;
+    *os << "\n    .lightRadius = " << settings.lightRadius;
+    *os << "\n    .length = " << settings.length;
+    *os << "\n    .casterIsTranslucent = " << settings.casterIsTranslucent;
+    *os << "\n}";
+}
+
+static inline void PrintTo(const LayerSettings& settings, ::std::ostream* os) {
+    *os << "LayerSettings {";
+    *os << "\n    .geometry = ";
+    PrintTo(settings.geometry, os);
+    *os << "\n    .source = ";
+    PrintTo(settings.source, os);
+    *os << "\n    .alpha = " << settings.alpha;
+    *os << "\n    .sourceDataspace = ";
+    PrintTo(settings.sourceDataspace, os);
+    *os << "\n    .colorTransform = " << settings.colorTransform;
+    *os << "\n    .disableBlending = " << settings.disableBlending;
+    *os << "\n    .shadow = ";
+    PrintTo(settings.shadow, os);
+    *os << "\n}";
+}
+
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/include/renderengine/Mesh.h b/libs/renderengine/include/renderengine/Mesh.h
index 7618424..167f13f 100644
--- a/libs/renderengine/include/renderengine/Mesh.h
+++ b/libs/renderengine/include/renderengine/Mesh.h
@@ -26,13 +26,14 @@
 
 class Mesh {
 public:
+    class Builder;
+
     enum Primitive {
         TRIANGLES = 0x0004,      // GL_TRIANGLES
         TRIANGLE_STRIP = 0x0005, // GL_TRIANGLE_STRIP
         TRIANGLE_FAN = 0x0006    // GL_TRIANGLE_FAN
     };
 
-    Mesh(Primitive primitive, size_t vertexCount, size_t vertexSize, size_t texCoordsSize = 0);
     ~Mesh() = default;
 
     /*
@@ -43,12 +44,20 @@
         friend class Mesh;
         float* mData;
         size_t mStride;
+        size_t mOffset = 0;
         VertexArray(float* data, size_t stride) : mData(data), mStride(stride) {}
 
     public:
-        TYPE& operator[](size_t index) { return *reinterpret_cast<TYPE*>(&mData[index * mStride]); }
+        // Returns a vertex array at an offset so its easier to append attributes from
+        // multiple sources.
+        VertexArray(VertexArray<TYPE>& other, size_t offset)
+              : mData(other.mData), mStride(other.mStride), mOffset(offset) {}
+
+        TYPE& operator[](size_t index) {
+            return *reinterpret_cast<TYPE*>(&mData[(index + mOffset) * mStride]);
+        }
         TYPE const& operator[](size_t index) const {
-            return *reinterpret_cast<TYPE const*>(&mData[index * mStride]);
+            return *reinterpret_cast<TYPE const*>(&mData[(index + mOffset) * mStride]);
         }
     };
 
@@ -67,6 +76,18 @@
         return VertexArray<TYPE>(getCropCoords(), mStride);
     }
 
+    template <typename TYPE>
+    VertexArray<TYPE> getShadowColorArray() {
+        return VertexArray<TYPE>(getShadowColor(), mStride);
+    }
+
+    template <typename TYPE>
+    VertexArray<TYPE> getShadowParamsArray() {
+        return VertexArray<TYPE>(getShadowParams(), mStride);
+    }
+
+    uint16_t* getIndicesArray() { return getIndices(); }
+
     Primitive getPrimitive() const;
 
     // returns a pointer to the vertices positions
@@ -78,6 +99,15 @@
     // returns a pointer to the vertices crop coordinates
     float const* getCropCoords() const;
 
+    // returns a pointer to colors
+    float const* getShadowColor() const;
+
+    // returns a pointer to the shadow params
+    float const* getShadowParams() const;
+
+    // returns a pointer to indices
+    uint16_t const* getIndices() const;
+
     // number of vertices in this mesh
     size_t getVertexCount() const;
 
@@ -87,6 +117,12 @@
     // dimension of texture coordinates
     size_t getTexCoordsSize() const;
 
+    size_t getShadowParamsSize() const;
+
+    size_t getShadowColorSize() const;
+
+    size_t getIndexCount() const;
+
     // return stride in bytes
     size_t getByteStride() const;
 
@@ -94,6 +130,8 @@
     size_t getStride() const;
 
 private:
+    Mesh(Primitive primitive, size_t vertexCount, size_t vertexSize, size_t texCoordSize,
+         size_t cropCoordsSize, size_t shadowColorSize, size_t shadowParamsSize, size_t indexCount);
     Mesh(const Mesh&);
     Mesh& operator=(const Mesh&);
     Mesh const& operator=(const Mesh&) const;
@@ -101,13 +139,65 @@
     float* getPositions();
     float* getTexCoords();
     float* getCropCoords();
+    float* getShadowColor();
+    float* getShadowParams();
+    uint16_t* getIndices();
 
     std::vector<float> mVertices;
     size_t mVertexCount;
     size_t mVertexSize;
     size_t mTexCoordsSize;
+    size_t mCropCoordsSize;
+    size_t mShadowColorSize;
+    size_t mShadowParamsSize;
     size_t mStride;
     Primitive mPrimitive;
+    std::vector<uint16_t> mIndices;
+    size_t mIndexCount;
+};
+
+class Mesh::Builder {
+public:
+    Builder& setPrimitive(Primitive primitive) {
+        mPrimitive = primitive;
+        return *this;
+    };
+    Builder& setVertices(size_t vertexCount, size_t vertexSize) {
+        mVertexCount = vertexCount;
+        mVertexSize = vertexSize;
+        return *this;
+    };
+    Builder& setTexCoords(size_t texCoordsSize) {
+        mTexCoordsSize = texCoordsSize;
+        return *this;
+    };
+    Builder& setCropCoords(size_t cropCoordsSize) {
+        mCropCoordsSize = cropCoordsSize;
+        return *this;
+    };
+    Builder& setShadowAttrs() {
+        mShadowParamsSize = 3;
+        mShadowColorSize = 4;
+        return *this;
+    };
+    Builder& setIndices(size_t indexCount) {
+        mIndexCount = indexCount;
+        return *this;
+    };
+    Mesh build() const {
+        return Mesh{mPrimitive,      mVertexCount,     mVertexSize,       mTexCoordsSize,
+                    mCropCoordsSize, mShadowColorSize, mShadowParamsSize, mIndexCount};
+    }
+
+private:
+    size_t mVertexCount = 0;
+    size_t mVertexSize = 0;
+    size_t mTexCoordsSize = 0;
+    size_t mCropCoordsSize = 0;
+    size_t mShadowColorSize = 0;
+    size_t mShadowParamsSize = 0;
+    size_t mIndexCount = 0;
+    Primitive mPrimitive;
 };
 
 } // namespace renderengine
diff --git a/libs/renderengine/include/renderengine/private/Description.h b/libs/renderengine/include/renderengine/private/Description.h
index bd2055f..a62161a 100644
--- a/libs/renderengine/include/renderengine/private/Description.h
+++ b/libs/renderengine/include/renderengine/private/Description.h
@@ -71,6 +71,8 @@
     TransferFunction outputTransferFunction = TransferFunction::LINEAR;
 
     float displayMaxLuminance;
+    float maxMasteringLuminance;
+    float maxContentLuminance;
 
     // projection matrix
     mat4 projectionMatrix;
@@ -79,6 +81,9 @@
     mat4 colorMatrix;
     mat4 inputTransformMatrix;
     mat4 outputTransformMatrix;
+
+    // True if this layer will draw a shadow.
+    bool drawShadows = false;
 };
 
 } // namespace renderengine
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index d01c740..ba5a3f5 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -16,6 +16,7 @@
 
 #include <chrono>
 #include <condition_variable>
+#include <fstream>
 
 #include <gtest/gtest.h>
 #include <renderengine/RenderEngine.h>
@@ -26,6 +27,7 @@
 constexpr int DEFAULT_DISPLAY_WIDTH = 128;
 constexpr int DEFAULT_DISPLAY_HEIGHT = 256;
 constexpr int DEFAULT_DISPLAY_OFFSET = 64;
+constexpr bool WRITE_BUFFER_TO_FILE_ON_FAILURE = false;
 
 namespace android {
 
@@ -68,21 +70,80 @@
     RenderEngineTest() { mBuffer = allocateDefaultBuffer(); }
 
     ~RenderEngineTest() {
+        if (WRITE_BUFFER_TO_FILE_ON_FAILURE && ::testing::Test::HasFailure()) {
+            writeBufferToFile("/data/texture_out_");
+        }
         for (uint32_t texName : mTexNames) {
             sRE->deleteTextures(1, &texName);
         }
     }
 
-    void expectBufferColor(const Rect& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a,
-                           uint8_t tolerance = 0) {
+    void writeBufferToFile(const char* basename) {
+        std::string filename(basename);
+        filename.append(::testing::UnitTest::GetInstance()->current_test_info()->name());
+        filename.append(".ppm");
+        std::ofstream file(filename.c_str(), std::ios::binary);
+        if (!file.is_open()) {
+            ALOGE("Unable to open file: %s", filename.c_str());
+            ALOGE("You may need to do: \"adb shell setenforce 0\" to enable "
+                  "surfaceflinger to write debug images");
+            return;
+        }
+
         uint8_t* pixels;
         mBuffer->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                       reinterpret_cast<void**>(&pixels));
 
-        auto colorCompare = [tolerance](uint8_t a, uint8_t b) {
-            uint8_t tmp = a >= b ? a - b : b - a;
-            return tmp <= tolerance;
+        file << "P6\n";
+        file << mBuffer->getWidth() << "\n";
+        file << mBuffer->getHeight() << "\n";
+        file << 255 << "\n";
+
+        std::vector<uint8_t> outBuffer(mBuffer->getWidth() * mBuffer->getHeight() * 3);
+        auto outPtr = reinterpret_cast<uint8_t*>(outBuffer.data());
+
+        for (int32_t j = 0; j < mBuffer->getHeight(); j++) {
+            const uint8_t* src = pixels + (mBuffer->getStride() * j) * 4;
+            for (int32_t i = 0; i < mBuffer->getWidth(); i++) {
+                // Only copy R, G and B components
+                outPtr[0] = src[0];
+                outPtr[1] = src[1];
+                outPtr[2] = src[2];
+                outPtr += 3;
+
+                src += 4;
+            }
+        }
+        file.write(reinterpret_cast<char*>(outBuffer.data()), outBuffer.size());
+        mBuffer->unlock();
+    }
+
+    void expectBufferColor(const Region& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
+        size_t c;
+        Rect const* rect = region.getArray(&c);
+        for (size_t i = 0; i < c; i++, rect++) {
+            expectBufferColor(*rect, r, g, b, a);
+        }
+    }
+
+    void expectBufferColor(const Rect& rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a,
+                           uint8_t tolerance = 0) {
+        auto colorCompare = [tolerance](const uint8_t* colorA, const uint8_t* colorB) {
+            auto colorBitCompare = [tolerance](uint8_t a, uint8_t b) {
+                uint8_t tmp = a >= b ? a - b : b - a;
+                return tmp <= tolerance;
+            };
+            return std::equal(colorA, colorA + 4, colorB, colorBitCompare);
         };
+
+        expectBufferColor(rect, r, g, b, a, colorCompare);
+    }
+
+    void expectBufferColor(const Rect& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a,
+                           std::function<bool(const uint8_t* a, const uint8_t* b)> colorCompare) {
+        uint8_t* pixels;
+        mBuffer->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                      reinterpret_cast<void**>(&pixels));
         int32_t maxFails = 10;
         int32_t fails = 0;
         for (int32_t j = 0; j < region.getHeight(); j++) {
@@ -90,7 +151,7 @@
                     pixels + (mBuffer->getStride() * (region.top + j) + region.left) * 4;
             for (int32_t i = 0; i < region.getWidth(); i++) {
                 const uint8_t expected[4] = {r, g, b, a};
-                bool equal = std::equal(src, src + 4, expected, colorCompare);
+                bool equal = colorCompare(src, expected);
                 EXPECT_TRUE(equal)
                         << "pixel @ (" << region.left + i << ", " << region.top + j << "): "
                         << "expected (" << static_cast<uint32_t>(r) << ", "
@@ -111,6 +172,64 @@
         mBuffer->unlock();
     }
 
+    void expectAlpha(const Rect& rect, uint8_t a) {
+        auto colorCompare = [](const uint8_t* colorA, const uint8_t* colorB) {
+            return colorA[3] == colorB[3];
+        };
+        expectBufferColor(rect, 0.0f /* r */, 0.0f /*g */, 0.0f /* b */, a, colorCompare);
+    }
+
+    void expectShadowColor(const renderengine::LayerSettings& castingLayer,
+                           const renderengine::ShadowSettings& shadow, const ubyte4& casterColor,
+                           const ubyte4& backgroundColor) {
+        const Rect casterRect(castingLayer.geometry.boundaries);
+        Region casterRegion = Region(casterRect);
+        const float casterCornerRadius = castingLayer.geometry.roundedCornersRadius;
+        if (casterCornerRadius > 0.0f) {
+            // ignore the corners if a corner radius is set
+            Rect cornerRect(casterCornerRadius, casterCornerRadius);
+            casterRegion.subtractSelf(cornerRect.offsetTo(casterRect.left, casterRect.top));
+            casterRegion.subtractSelf(
+                    cornerRect.offsetTo(casterRect.right - casterCornerRadius, casterRect.top));
+            casterRegion.subtractSelf(
+                    cornerRect.offsetTo(casterRect.left, casterRect.bottom - casterCornerRadius));
+            casterRegion.subtractSelf(cornerRect.offsetTo(casterRect.right - casterCornerRadius,
+                                                          casterRect.bottom - casterCornerRadius));
+        }
+
+        const float shadowInset = shadow.length * -1.0f;
+        const Rect casterWithShadow =
+                Rect(casterRect).inset(shadowInset, shadowInset, shadowInset, shadowInset);
+        const Region shadowRegion = Region(casterWithShadow).subtractSelf(casterRect);
+        const Region backgroundRegion = Region(fullscreenRect()).subtractSelf(casterWithShadow);
+
+        // verify casting layer
+        expectBufferColor(casterRegion, casterColor.r, casterColor.g, casterColor.b, casterColor.a);
+
+        // verify shadows by testing just the alpha since its difficult to validate the shadow color
+        size_t c;
+        Rect const* r = shadowRegion.getArray(&c);
+        for (size_t i = 0; i < c; i++, r++) {
+            expectAlpha(*r, 255);
+        }
+
+        // verify background
+        expectBufferColor(backgroundRegion, backgroundColor.r, backgroundColor.g, backgroundColor.b,
+                          backgroundColor.a);
+    }
+
+    static renderengine::ShadowSettings getShadowSettings(const vec2& casterPos, float shadowLength,
+                                                          bool casterIsTranslucent) {
+        renderengine::ShadowSettings shadow;
+        shadow.ambientColor = {0.0f, 0.0f, 0.0f, 0.039f};
+        shadow.spotColor = {0.0f, 0.0f, 0.0f, 0.19f};
+        shadow.lightPos = vec3(casterPos.x, casterPos.y, 0);
+        shadow.lightRadius = 0.0f;
+        shadow.length = shadowLength;
+        shadow.casterIsTranslucent = casterIsTranslucent;
+        return shadow;
+    }
+
     static Rect fullscreenRect() { return Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT); }
 
     static Rect offsetRect() {
@@ -225,6 +344,11 @@
 
     void clearRegion();
 
+    template <typename SourceVariant>
+    void drawShadow(const renderengine::LayerSettings& castingLayer,
+                    const renderengine::ShadowSettings& shadow, const ubyte4& casterColor,
+                    const ubyte4& backgroundColor);
+
     // Keep around the same renderengine object to save on initialization time.
     // For now, exercise the GL backend directly so that some caching specifics
     // can be tested without changing the interface.
@@ -766,6 +890,40 @@
                       0, 0, 0, 0);
 }
 
+template <typename SourceVariant>
+void RenderEngineTest::drawShadow(const renderengine::LayerSettings& castingLayer,
+                                  const renderengine::ShadowSettings& shadow,
+                                  const ubyte4& casterColor, const ubyte4& backgroundColor) {
+    renderengine::DisplaySettings settings;
+    settings.physicalDisplay = fullscreenRect();
+    settings.clip = fullscreenRect();
+
+    std::vector<renderengine::LayerSettings> layers;
+
+    // add background layer
+    renderengine::LayerSettings bgLayer;
+    bgLayer.geometry.boundaries = fullscreenRect().toFloatRect();
+    ColorSourceVariant::fillColor(bgLayer, backgroundColor.r / 255.0f, backgroundColor.g / 255.0f,
+                                  backgroundColor.b / 255.0f, this);
+    bgLayer.alpha = backgroundColor.a / 255.0f;
+    layers.push_back(bgLayer);
+
+    // add shadow layer
+    renderengine::LayerSettings shadowLayer;
+    shadowLayer.geometry.boundaries = castingLayer.geometry.boundaries;
+    shadowLayer.alpha = castingLayer.alpha;
+    shadowLayer.shadow = shadow;
+    layers.push_back(shadowLayer);
+
+    // add layer casting the shadow
+    renderengine::LayerSettings layer = castingLayer;
+    SourceVariant::fillColor(layer, casterColor.r / 255.0f, casterColor.g / 255.0f,
+                             casterColor.b / 255.0f, this);
+    layers.push_back(layer);
+
+    invokeDraw(settings, layers, mBuffer);
+}
+
 TEST_F(RenderEngineTest, drawLayers_noLayersToDraw) {
     drawEmptyLayers();
 }
@@ -1083,4 +1241,101 @@
     EXPECT_FALSE(sRE->isImageCachedForTesting(bufferId));
 }
 
+TEST_F(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
+    const ubyte4 casterColor(255, 0, 0, 255);
+    const ubyte4 backgroundColor(255, 255, 255, 255);
+    const float shadowLength = 5.0f;
+    Rect casterBounds(1, 1);
+    casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
+    renderengine::LayerSettings castingLayer;
+    castingLayer.geometry.boundaries = casterBounds.toFloatRect();
+    castingLayer.alpha = 1.0f;
+    renderengine::ShadowSettings settings =
+            getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
+                              false /* casterIsTranslucent */);
+
+    drawShadow<ColorSourceVariant>(castingLayer, settings, casterColor, backgroundColor);
+    expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
+}
+
+TEST_F(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) {
+    const ubyte4 casterColor(255, 0, 0, 255);
+    const ubyte4 backgroundColor(255, 255, 255, 255);
+    const float shadowLength = 5.0f;
+    Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
+    casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
+    renderengine::LayerSettings castingLayer;
+    castingLayer.geometry.boundaries = casterBounds.toFloatRect();
+    castingLayer.alpha = 1.0f;
+    renderengine::ShadowSettings settings =
+            getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
+                              false /* casterIsTranslucent */);
+
+    drawShadow<ColorSourceVariant>(castingLayer, settings, casterColor, backgroundColor);
+    expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
+}
+
+TEST_F(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
+    const ubyte4 casterColor(255, 0, 0, 255);
+    const ubyte4 backgroundColor(255, 255, 255, 255);
+    const float shadowLength = 5.0f;
+    Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
+    casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
+    renderengine::LayerSettings castingLayer;
+    castingLayer.geometry.boundaries = casterBounds.toFloatRect();
+    castingLayer.alpha = 1.0f;
+    renderengine::ShadowSettings settings =
+            getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
+                              false /* casterIsTranslucent */);
+
+    drawShadow<BufferSourceVariant<ForceOpaqueBufferVariant>>(castingLayer, settings, casterColor,
+                                                              backgroundColor);
+    expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
+}
+
+TEST_F(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) {
+    const ubyte4 casterColor(255, 0, 0, 255);
+    const ubyte4 backgroundColor(255, 255, 255, 255);
+    const float shadowLength = 5.0f;
+    Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
+    casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
+    renderengine::LayerSettings castingLayer;
+    castingLayer.geometry.boundaries = casterBounds.toFloatRect();
+    castingLayer.geometry.roundedCornersRadius = 3.0f;
+    castingLayer.geometry.roundedCornersCrop = casterBounds.toFloatRect();
+    castingLayer.alpha = 1.0f;
+    renderengine::ShadowSettings settings =
+            getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
+                              false /* casterIsTranslucent */);
+
+    drawShadow<BufferSourceVariant<ForceOpaqueBufferVariant>>(castingLayer, settings, casterColor,
+                                                              backgroundColor);
+    expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
+}
+
+TEST_F(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) {
+    const ubyte4 casterColor(255, 0, 0, 255);
+    const ubyte4 backgroundColor(255, 255, 255, 255);
+    const float shadowLength = 5.0f;
+    Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
+    casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
+    renderengine::LayerSettings castingLayer;
+    castingLayer.geometry.boundaries = casterBounds.toFloatRect();
+    castingLayer.alpha = 0.5f;
+    renderengine::ShadowSettings settings =
+            getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
+                              true /* casterIsTranslucent */);
+
+    drawShadow<BufferSourceVariant<RelaxOpaqueBufferVariant>>(castingLayer, settings, casterColor,
+                                                              backgroundColor);
+
+    // verify only the background since the shadow will draw behind the caster
+    const float shadowInset = settings.length * -1.0f;
+    const Rect casterWithShadow =
+            Rect(casterBounds).inset(shadowInset, shadowInset, shadowInset, shadowInset);
+    const Region backgroundRegion = Region(fullscreenRect()).subtractSelf(casterWithShadow);
+    expectBufferColor(backgroundRegion, backgroundColor.r, backgroundColor.g, backgroundColor.b,
+                      backgroundColor.a);
+}
+
 } // namespace android
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index afa6a2b..124bda2 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -69,6 +69,10 @@
     include_dirs: [
         "frameworks/native/include",
     ],
+    export_include_dirs: [
+        "include",
+        "include_private",
+    ],
 
     // Uncomment the following line to enable VALIDATE_REGIONS traces
     //defaults: ["libui-validate-regions-defaults"],
@@ -85,14 +89,19 @@
         "android.hardware.graphics.mapper@4.0",
         "libbase",
         "libcutils",
+        "libgralloctypes",
         "libhidlbase",
         "libsync",
         "libutils",
         "liblog",
+        "vintf-graphics-common-ndk_platform",
     ],
 
     export_shared_lib_headers: [
         "android.hardware.graphics.common@1.2",
+        "android.hardware.graphics.mapper@4.0",
+        "libgralloctypes",
+        "vintf-graphics-common-ndk_platform",
     ],
 
     static_libs: [
diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp
index 5dc4530..040a62b 100644
--- a/libs/ui/Gralloc2.cpp
+++ b/libs/ui/Gralloc2.cpp
@@ -351,12 +351,6 @@
     return releaseFence;
 }
 
-status_t Gralloc2Mapper::isSupported(uint32_t /*width*/, uint32_t /*height*/,
-                                     android::PixelFormat /*format*/, uint32_t /*layerCount*/,
-                                     uint64_t /*usage*/, bool* /*outSupported*/) const {
-    return INVALID_OPERATION;
-}
-
 Gralloc2Allocator::Gralloc2Allocator(const Gralloc2Mapper& mapper) : mMapper(mapper) {
     mAllocator = IAllocator::getService();
     if (mAllocator == nullptr) {
@@ -369,7 +363,7 @@
     return mAllocator != nullptr;
 }
 
-std::string Gralloc2Allocator::dumpDebugInfo() const {
+std::string Gralloc2Allocator::dumpDebugInfo(bool /*less*/) const {
     std::string debugInfo;
 
     mAllocator->dumpDebugInfo([&](const auto& tmpDebugInfo) {
@@ -379,9 +373,10 @@
     return debugInfo;
 }
 
-status_t Gralloc2Allocator::allocate(uint32_t width, uint32_t height, PixelFormat format,
-                                     uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                                     uint32_t* outStride, buffer_handle_t* outBufferHandles) const {
+status_t Gralloc2Allocator::allocate(std::string /*requestorName*/, uint32_t width, uint32_t height,
+                                     PixelFormat format, uint32_t layerCount, uint64_t usage,
+                                     uint32_t bufferCount, uint32_t* outStride,
+                                     buffer_handle_t* outBufferHandles, bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo = {};
     descriptorInfo.width = width;
     descriptorInfo.height = height;
@@ -404,19 +399,33 @@
                                             return;
                                         }
 
-                                        // import buffers
-                                        for (uint32_t i = 0; i < bufferCount; i++) {
-                                            error = mMapper.importBuffer(tmpBuffers[i],
-                                                                         &outBufferHandles[i]);
-                                            if (error != NO_ERROR) {
-                                                for (uint32_t j = 0; j < i; j++) {
-                                                    mMapper.freeBuffer(outBufferHandles[j]);
-                                                    outBufferHandles[j] = nullptr;
+                                        if (importBuffers) {
+                                            for (uint32_t i = 0; i < bufferCount; i++) {
+                                                error = mMapper.importBuffer(tmpBuffers[i],
+                                                                             &outBufferHandles[i]);
+                                                if (error != NO_ERROR) {
+                                                    for (uint32_t j = 0; j < i; j++) {
+                                                        mMapper.freeBuffer(outBufferHandles[j]);
+                                                        outBufferHandles[j] = nullptr;
+                                                    }
+                                                    return;
                                                 }
-                                                return;
+                                            }
+                                        } else {
+                                            for (uint32_t i = 0; i < bufferCount; i++) {
+                                                outBufferHandles[i] = native_handle_clone(
+                                                        tmpBuffers[i].getNativeHandle());
+                                                if (!outBufferHandles[i]) {
+                                                    for (uint32_t j = 0; j < i; j++) {
+                                                        auto buffer = const_cast<native_handle_t*>(
+                                                                outBufferHandles[j]);
+                                                        native_handle_close(buffer);
+                                                        native_handle_delete(buffer);
+                                                        outBufferHandles[j] = nullptr;
+                                                    }
+                                                }
                                             }
                                         }
-
                                         *outStride = tmpStride;
                                     });
 
diff --git a/libs/ui/Gralloc3.cpp b/libs/ui/Gralloc3.cpp
index eb43765..882674f 100644
--- a/libs/ui/Gralloc3.cpp
+++ b/libs/ui/Gralloc3.cpp
@@ -352,7 +352,7 @@
     return mAllocator != nullptr;
 }
 
-std::string Gralloc3Allocator::dumpDebugInfo() const {
+std::string Gralloc3Allocator::dumpDebugInfo(bool /*less*/) const {
     std::string debugInfo;
 
     mAllocator->dumpDebugInfo([&](const auto& tmpDebugInfo) { debugInfo = tmpDebugInfo.c_str(); });
@@ -360,9 +360,10 @@
     return debugInfo;
 }
 
-status_t Gralloc3Allocator::allocate(uint32_t width, uint32_t height, android::PixelFormat format,
-                                     uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                                     uint32_t* outStride, buffer_handle_t* outBufferHandles) const {
+status_t Gralloc3Allocator::allocate(std::string /*requestorName*/, uint32_t width, uint32_t height,
+                                     android::PixelFormat format, uint32_t layerCount,
+                                     uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
+                                     buffer_handle_t* outBufferHandles, bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
     sBufferDescriptorInfo(width, height, format, layerCount, usage, &descriptorInfo);
 
@@ -381,16 +382,31 @@
                                             return;
                                         }
 
-                                        // import buffers
-                                        for (uint32_t i = 0; i < bufferCount; i++) {
-                                            error = mMapper.importBuffer(tmpBuffers[i],
-                                                                         &outBufferHandles[i]);
-                                            if (error != NO_ERROR) {
-                                                for (uint32_t j = 0; j < i; j++) {
-                                                    mMapper.freeBuffer(outBufferHandles[j]);
-                                                    outBufferHandles[j] = nullptr;
+                                        if (importBuffers) {
+                                            for (uint32_t i = 0; i < bufferCount; i++) {
+                                                error = mMapper.importBuffer(tmpBuffers[i],
+                                                                             &outBufferHandles[i]);
+                                                if (error != NO_ERROR) {
+                                                    for (uint32_t j = 0; j < i; j++) {
+                                                        mMapper.freeBuffer(outBufferHandles[j]);
+                                                        outBufferHandles[j] = nullptr;
+                                                    }
+                                                    return;
                                                 }
-                                                return;
+                                            }
+                                        } else {
+                                            for (uint32_t i = 0; i < bufferCount; i++) {
+                                                outBufferHandles[i] = native_handle_clone(
+                                                        tmpBuffers[i].getNativeHandle());
+                                                if (!outBufferHandles[i]) {
+                                                    for (uint32_t j = 0; j < i; j++) {
+                                                        auto buffer = const_cast<native_handle_t*>(
+                                                                outBufferHandles[j]);
+                                                        native_handle_close(buffer);
+                                                        native_handle_delete(buffer);
+                                                        outBufferHandles[j] = nullptr;
+                                                    }
+                                                }
                                             }
                                         }
                                         *outStride = tmpStride;
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index dc105c0..2c897cf 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -27,12 +27,20 @@
 #include <sync/sync.h>
 #pragma clang diagnostic pop
 
+using aidl::android::hardware::graphics::common::ExtendableType;
+using aidl::android::hardware::graphics::common::PlaneLayoutComponentType;
+using aidl::android::hardware::graphics::common::StandardMetadataType;
+using android::hardware::hidl_vec;
 using android::hardware::graphics::allocator::V4_0::IAllocator;
 using android::hardware::graphics::common::V1_2::BufferUsage;
 using android::hardware::graphics::mapper::V4_0::BufferDescriptor;
 using android::hardware::graphics::mapper::V4_0::Error;
 using android::hardware::graphics::mapper::V4_0::IMapper;
-using android::hardware::graphics::mapper::V4_0::YCbCrLayout;
+using BufferDump = android::hardware::graphics::mapper::V4_0::IMapper::BufferDump;
+using MetadataDump = android::hardware::graphics::mapper::V4_0::IMapper::MetadataDump;
+using MetadataType = android::hardware::graphics::mapper::V4_0::IMapper::MetadataType;
+using MetadataTypeDescription =
+        android::hardware::graphics::mapper::V4_0::IMapper::MetadataTypeDescription;
 
 namespace android {
 
@@ -60,10 +68,10 @@
     outRect.height = rect.height();
     return outRect;
 }
-static inline void sBufferDescriptorInfo(uint32_t width, uint32_t height,
-                                         android::PixelFormat format, uint32_t layerCount,
-                                         uint64_t usage,
+static inline void sBufferDescriptorInfo(std::string name, uint32_t width, uint32_t height,
+                                         PixelFormat format, uint32_t layerCount, uint64_t usage,
                                          IMapper::BufferDescriptorInfo* outDescriptorInfo) {
+    outDescriptorInfo->name = name;
     outDescriptorInfo->width = width;
     outDescriptorInfo->height = height;
     outDescriptorInfo->layerCount = layerCount;
@@ -152,11 +160,12 @@
 }
 
 status_t Gralloc4Mapper::validateBufferSize(buffer_handle_t bufferHandle, uint32_t width,
-                                            uint32_t height, android::PixelFormat format,
+                                            uint32_t height, PixelFormat format,
                                             uint32_t layerCount, uint64_t usage,
                                             uint32_t stride) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo(width, height, format, layerCount, usage, &descriptorInfo);
+    sBufferDescriptorInfo("validateBufferSize", width, height, format, layerCount, usage,
+                          &descriptorInfo);
 
     auto buffer = const_cast<native_handle_t*>(bufferHandle);
     auto ret = mMapper->validateBufferSize(buffer, descriptorInfo, stride);
@@ -190,6 +199,38 @@
 status_t Gralloc4Mapper::lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect& bounds,
                               int acquireFence, void** outData, int32_t* outBytesPerPixel,
                               int32_t* outBytesPerStride) const {
+    std::vector<ui::PlaneLayout> planeLayouts;
+    status_t err = getPlaneLayouts(bufferHandle, &planeLayouts);
+
+    if (err != NO_ERROR && !planeLayouts.empty()) {
+        if (outBytesPerPixel) {
+            int32_t bitsPerPixel = planeLayouts.front().sampleIncrementInBits;
+            for (const auto& planeLayout : planeLayouts) {
+                if (bitsPerPixel != planeLayout.sampleIncrementInBits) {
+                    bitsPerPixel = -1;
+                }
+            }
+            if (bitsPerPixel >= 0 && bitsPerPixel % 8 == 0) {
+                *outBytesPerPixel = bitsPerPixel / 8;
+            } else {
+                *outBytesPerPixel = -1;
+            }
+        }
+        if (outBytesPerStride) {
+            int32_t bytesPerStride = planeLayouts.front().strideInBytes;
+            for (const auto& planeLayout : planeLayouts) {
+                if (bytesPerStride != planeLayout.strideInBytes) {
+                    bytesPerStride = -1;
+                }
+            }
+            if (bytesPerStride >= 0) {
+                *outBytesPerStride = bytesPerStride;
+            } else {
+                *outBytesPerStride = -1;
+            }
+        }
+    }
+
     auto buffer = const_cast<native_handle_t*>(bufferHandle);
 
     IMapper::Rect accessRegion = sGralloc4Rect(bounds);
@@ -205,19 +246,12 @@
 
     Error error;
     auto ret = mMapper->lock(buffer, usage, accessRegion, acquireFenceHandle,
-                             [&](const auto& tmpError, const auto& tmpData,
-                                 const auto& tmpBytesPerPixel, const auto& tmpBytesPerStride) {
+                             [&](const auto& tmpError, const auto& tmpData) {
                                  error = tmpError;
                                  if (error != Error::NONE) {
                                      return;
                                  }
                                  *outData = tmpData;
-                                 if (outBytesPerPixel) {
-                                     *outBytesPerPixel = tmpBytesPerPixel;
-                                 }
-                                 if (outBytesPerStride) {
-                                     *outBytesPerStride = tmpBytesPerStride;
-                                 }
                              });
 
     // we own acquireFence even on errors
@@ -233,47 +267,104 @@
 }
 
 status_t Gralloc4Mapper::lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect& bounds,
-                              int acquireFence, android_ycbcr* ycbcr) const {
-    auto buffer = const_cast<native_handle_t*>(bufferHandle);
-
-    IMapper::Rect accessRegion = sGralloc4Rect(bounds);
-
-    // put acquireFence in a hidl_handle
-    hardware::hidl_handle acquireFenceHandle;
-    NATIVE_HANDLE_DECLARE_STORAGE(acquireFenceStorage, 1, 0);
-    if (acquireFence >= 0) {
-        auto h = native_handle_init(acquireFenceStorage, 1, 0);
-        h->data[0] = acquireFence;
-        acquireFenceHandle = h;
+                              int acquireFence, android_ycbcr* outYcbcr) const {
+    if (!outYcbcr) {
+        return BAD_VALUE;
     }
 
-    YCbCrLayout layout;
-    Error error;
-    auto ret = mMapper->lockYCbCr(buffer, usage, accessRegion, acquireFenceHandle,
-                                  [&](const auto& tmpError, const auto& tmpLayout) {
-                                      error = tmpError;
-                                      if (error != Error::NONE) {
-                                          return;
-                                      }
-
-                                      layout = tmpLayout;
-                                  });
-
-    if (error == Error::NONE) {
-        ycbcr->y = layout.y;
-        ycbcr->cb = layout.cb;
-        ycbcr->cr = layout.cr;
-        ycbcr->ystride = static_cast<size_t>(layout.yStride);
-        ycbcr->cstride = static_cast<size_t>(layout.cStride);
-        ycbcr->chroma_step = static_cast<size_t>(layout.chromaStep);
+    std::vector<ui::PlaneLayout> planeLayouts;
+    status_t error = getPlaneLayouts(bufferHandle, &planeLayouts);
+    if (error != NO_ERROR) {
+        return error;
     }
 
-    // we own acquireFence even on errors
-    if (acquireFence >= 0) {
-        close(acquireFence);
+    void* data = nullptr;
+    error = lock(bufferHandle, usage, bounds, acquireFence, &data, nullptr, nullptr);
+    if (error != NO_ERROR) {
+        return error;
     }
 
-    return static_cast<status_t>((ret.isOk()) ? error : kTransactionError);
+    android_ycbcr ycbcr;
+
+    ycbcr.y = nullptr;
+    ycbcr.cb = nullptr;
+    ycbcr.cr = nullptr;
+    ycbcr.ystride = 0;
+    ycbcr.cstride = 0;
+    ycbcr.chroma_step = 0;
+
+    for (const auto& planeLayout : planeLayouts) {
+        for (const auto& planeLayoutComponent : planeLayout.components) {
+            if (!gralloc4::isStandardPlaneLayoutComponentType(planeLayoutComponent.type)) {
+                continue;
+            }
+            if (0 != planeLayoutComponent.offsetInBits % 8) {
+                unlock(bufferHandle);
+                return BAD_VALUE;
+            }
+
+            uint8_t* tmpData = static_cast<uint8_t*>(data) + planeLayout.offsetInBytes +
+                    (planeLayoutComponent.offsetInBits / 8);
+            uint64_t sampleIncrementInBytes;
+
+            auto type = static_cast<PlaneLayoutComponentType>(planeLayoutComponent.type.value);
+            switch (type) {
+                case PlaneLayoutComponentType::Y:
+                    if ((ycbcr.y != nullptr) || (planeLayoutComponent.sizeInBits != 8) ||
+                        (planeLayout.sampleIncrementInBits != 8)) {
+                        unlock(bufferHandle);
+                        return BAD_VALUE;
+                    }
+                    ycbcr.y = tmpData;
+                    ycbcr.ystride = planeLayout.strideInBytes;
+                    break;
+
+                case PlaneLayoutComponentType::CB:
+                case PlaneLayoutComponentType::CR:
+                    if (planeLayout.sampleIncrementInBits % 8 != 0) {
+                        unlock(bufferHandle);
+                        return BAD_VALUE;
+                    }
+
+                    sampleIncrementInBytes = planeLayout.sampleIncrementInBits / 8;
+                    if ((sampleIncrementInBytes != 1) && (sampleIncrementInBytes != 2)) {
+                        unlock(bufferHandle);
+                        return BAD_VALUE;
+                    }
+
+                    if (ycbcr.cstride == 0 && ycbcr.chroma_step == 0) {
+                        ycbcr.cstride = planeLayout.strideInBytes;
+                        ycbcr.chroma_step = sampleIncrementInBytes;
+                    } else {
+                        if ((static_cast<int64_t>(ycbcr.cstride) != planeLayout.strideInBytes) ||
+                            (ycbcr.chroma_step != sampleIncrementInBytes)) {
+                            unlock(bufferHandle);
+                            return BAD_VALUE;
+                        }
+                    }
+
+                    if (type == PlaneLayoutComponentType::CB) {
+                        if (ycbcr.cb != nullptr) {
+                            unlock(bufferHandle);
+                            return BAD_VALUE;
+                        }
+                        ycbcr.cb = tmpData;
+                    } else {
+                        if (ycbcr.cr != nullptr) {
+                            unlock(bufferHandle);
+                            return BAD_VALUE;
+                        }
+                        ycbcr.cr = tmpData;
+                    }
+                    break;
+                default:
+                    break;
+            };
+        }
+    }
+
+    *outYcbcr = ycbcr;
+    return static_cast<status_t>(Error::UNSUPPORTED);
 }
 
 int Gralloc4Mapper::unlock(buffer_handle_t bufferHandle) const {
@@ -310,11 +401,11 @@
     return releaseFence;
 }
 
-status_t Gralloc4Mapper::isSupported(uint32_t width, uint32_t height, android::PixelFormat format,
+status_t Gralloc4Mapper::isSupported(uint32_t width, uint32_t height, PixelFormat format,
                                      uint32_t layerCount, uint64_t usage,
                                      bool* outSupported) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo(width, height, format, layerCount, usage, &descriptorInfo);
+    sBufferDescriptorInfo("isSupported", width, height, format, layerCount, usage, &descriptorInfo);
 
     Error error;
     auto ret = mMapper->isSupported(descriptorInfo,
@@ -340,6 +431,605 @@
     return static_cast<status_t>(error);
 }
 
+template <class T>
+status_t Gralloc4Mapper::get(buffer_handle_t bufferHandle, const MetadataType& metadataType,
+                             DecodeFunction<T> decodeFunction, T* outMetadata) const {
+    if (!outMetadata) {
+        return BAD_VALUE;
+    }
+
+    hidl_vec<uint8_t> vec;
+    Error error;
+    auto ret = mMapper->get(const_cast<native_handle_t*>(bufferHandle), metadataType,
+                            [&](const auto& tmpError, const hidl_vec<uint8_t>& tmpVec) {
+                                error = tmpError;
+                                vec = tmpVec;
+                            });
+
+    if (!ret.isOk()) {
+        error = kTransactionError;
+    }
+
+    if (error != Error::NONE) {
+        ALOGE("get(%s, %" PRIu64 ", ...) failed with %d", metadataType.name.c_str(),
+              metadataType.value, error);
+        return static_cast<status_t>(error);
+    }
+
+    return decodeFunction(vec, outMetadata);
+}
+
+status_t Gralloc4Mapper::getBufferId(buffer_handle_t bufferHandle, uint64_t* outBufferId) const {
+    return get(bufferHandle, gralloc4::MetadataType_BufferId, gralloc4::decodeBufferId,
+               outBufferId);
+}
+
+status_t Gralloc4Mapper::getName(buffer_handle_t bufferHandle, std::string* outName) const {
+    return get(bufferHandle, gralloc4::MetadataType_Name, gralloc4::decodeName, outName);
+}
+
+status_t Gralloc4Mapper::getWidth(buffer_handle_t bufferHandle, uint64_t* outWidth) const {
+    return get(bufferHandle, gralloc4::MetadataType_Width, gralloc4::decodeWidth, outWidth);
+}
+
+status_t Gralloc4Mapper::getHeight(buffer_handle_t bufferHandle, uint64_t* outHeight) const {
+    return get(bufferHandle, gralloc4::MetadataType_Height, gralloc4::decodeHeight, outHeight);
+}
+
+status_t Gralloc4Mapper::getLayerCount(buffer_handle_t bufferHandle,
+                                       uint64_t* outLayerCount) const {
+    return get(bufferHandle, gralloc4::MetadataType_LayerCount, gralloc4::decodeLayerCount,
+               outLayerCount);
+}
+
+status_t Gralloc4Mapper::getPixelFormatRequested(buffer_handle_t bufferHandle,
+                                                 ui::PixelFormat* outPixelFormatRequested) const {
+    return get(bufferHandle, gralloc4::MetadataType_PixelFormatRequested,
+               gralloc4::decodePixelFormatRequested, outPixelFormatRequested);
+}
+
+status_t Gralloc4Mapper::getPixelFormatFourCC(buffer_handle_t bufferHandle,
+                                              uint32_t* outPixelFormatFourCC) const {
+    return get(bufferHandle, gralloc4::MetadataType_PixelFormatFourCC,
+               gralloc4::decodePixelFormatFourCC, outPixelFormatFourCC);
+}
+
+status_t Gralloc4Mapper::getPixelFormatModifier(buffer_handle_t bufferHandle,
+                                                uint64_t* outPixelFormatModifier) const {
+    return get(bufferHandle, gralloc4::MetadataType_PixelFormatModifier,
+               gralloc4::decodePixelFormatModifier, outPixelFormatModifier);
+}
+
+status_t Gralloc4Mapper::getUsage(buffer_handle_t bufferHandle, uint64_t* outUsage) const {
+    return get(bufferHandle, gralloc4::MetadataType_Usage, gralloc4::decodeUsage, outUsage);
+}
+
+status_t Gralloc4Mapper::getAllocationSize(buffer_handle_t bufferHandle,
+                                           uint64_t* outAllocationSize) const {
+    return get(bufferHandle, gralloc4::MetadataType_AllocationSize, gralloc4::decodeAllocationSize,
+               outAllocationSize);
+}
+
+status_t Gralloc4Mapper::getProtectedContent(buffer_handle_t bufferHandle,
+                                             uint64_t* outProtectedContent) const {
+    return get(bufferHandle, gralloc4::MetadataType_ProtectedContent,
+               gralloc4::decodeProtectedContent, outProtectedContent);
+}
+
+status_t Gralloc4Mapper::getCompression(buffer_handle_t bufferHandle,
+                                        ExtendableType* outCompression) const {
+    return get(bufferHandle, gralloc4::MetadataType_Compression, gralloc4::decodeCompression,
+               outCompression);
+}
+
+status_t Gralloc4Mapper::getCompression(buffer_handle_t bufferHandle,
+                                        ui::Compression* outCompression) const {
+    if (!outCompression) {
+        return BAD_VALUE;
+    }
+    ExtendableType compression;
+    status_t error = getCompression(bufferHandle, &compression);
+    if (error) {
+        return error;
+    }
+    if (!gralloc4::isStandardCompression(compression)) {
+        return BAD_TYPE;
+    }
+    *outCompression = gralloc4::getStandardCompressionValue(compression);
+    return NO_ERROR;
+}
+
+status_t Gralloc4Mapper::getInterlaced(buffer_handle_t bufferHandle,
+                                       ExtendableType* outInterlaced) const {
+    return get(bufferHandle, gralloc4::MetadataType_Interlaced, gralloc4::decodeInterlaced,
+               outInterlaced);
+}
+
+status_t Gralloc4Mapper::getInterlaced(buffer_handle_t bufferHandle,
+                                       ui::Interlaced* outInterlaced) const {
+    if (!outInterlaced) {
+        return BAD_VALUE;
+    }
+    ExtendableType interlaced;
+    status_t error = getInterlaced(bufferHandle, &interlaced);
+    if (error) {
+        return error;
+    }
+    if (!gralloc4::isStandardInterlaced(interlaced)) {
+        return BAD_TYPE;
+    }
+    *outInterlaced = gralloc4::getStandardInterlacedValue(interlaced);
+    return NO_ERROR;
+}
+
+status_t Gralloc4Mapper::getChromaSiting(buffer_handle_t bufferHandle,
+                                         ExtendableType* outChromaSiting) const {
+    return get(bufferHandle, gralloc4::MetadataType_ChromaSiting, gralloc4::decodeChromaSiting,
+               outChromaSiting);
+}
+
+status_t Gralloc4Mapper::getChromaSiting(buffer_handle_t bufferHandle,
+                                         ui::ChromaSiting* outChromaSiting) const {
+    if (!outChromaSiting) {
+        return BAD_VALUE;
+    }
+    ExtendableType chromaSiting;
+    status_t error = getChromaSiting(bufferHandle, &chromaSiting);
+    if (error) {
+        return error;
+    }
+    if (!gralloc4::isStandardChromaSiting(chromaSiting)) {
+        return BAD_TYPE;
+    }
+    *outChromaSiting = gralloc4::getStandardChromaSitingValue(chromaSiting);
+    return NO_ERROR;
+}
+
+status_t Gralloc4Mapper::getPlaneLayouts(buffer_handle_t bufferHandle,
+                                         std::vector<ui::PlaneLayout>* outPlaneLayouts) const {
+    return get(bufferHandle, gralloc4::MetadataType_PlaneLayouts, gralloc4::decodePlaneLayouts,
+               outPlaneLayouts);
+}
+
+status_t Gralloc4Mapper::getDataspace(buffer_handle_t bufferHandle,
+                                      ui::Dataspace* outDataspace) const {
+    if (!outDataspace) {
+        return BAD_VALUE;
+    }
+    aidl::android::hardware::graphics::common::Dataspace dataspace;
+    status_t error = get(bufferHandle, gralloc4::MetadataType_Dataspace, gralloc4::decodeDataspace,
+                         &dataspace);
+    if (error) {
+        return error;
+    }
+
+    // Gralloc4 uses stable AIDL dataspace but the rest of the system still uses HIDL dataspace
+    *outDataspace = static_cast<ui::Dataspace>(dataspace);
+    return NO_ERROR;
+}
+
+status_t Gralloc4Mapper::getBlendMode(buffer_handle_t bufferHandle,
+                                      ui::BlendMode* outBlendMode) const {
+    return get(bufferHandle, gralloc4::MetadataType_BlendMode, gralloc4::decodeBlendMode,
+               outBlendMode);
+}
+
+template <class T>
+status_t Gralloc4Mapper::getDefault(uint32_t width, uint32_t height, PixelFormat format,
+                                    uint32_t layerCount, uint64_t usage,
+                                    const MetadataType& metadataType,
+                                    DecodeFunction<T> decodeFunction, T* outMetadata) const {
+    if (!outMetadata) {
+        return BAD_VALUE;
+    }
+
+    IMapper::BufferDescriptorInfo descriptorInfo;
+    sBufferDescriptorInfo("getDefault", width, height, format, layerCount, usage, &descriptorInfo);
+
+    hidl_vec<uint8_t> vec;
+    Error error;
+    auto ret = mMapper->getFromBufferDescriptorInfo(descriptorInfo, metadataType,
+                                                    [&](const auto& tmpError,
+                                                        const hidl_vec<uint8_t>& tmpVec) {
+                                                        error = tmpError;
+                                                        vec = tmpVec;
+                                                    });
+
+    if (!ret.isOk()) {
+        error = kTransactionError;
+    }
+
+    if (error != Error::NONE) {
+        ALOGE("getDefault(%s, %" PRIu64 ", ...) failed with %d", metadataType.name.c_str(),
+              metadataType.value, error);
+        return static_cast<status_t>(error);
+    }
+
+    return decodeFunction(vec, outMetadata);
+}
+
+status_t Gralloc4Mapper::getDefaultPixelFormatFourCC(uint32_t width, uint32_t height,
+                                                     PixelFormat format, uint32_t layerCount,
+                                                     uint64_t usage,
+                                                     uint32_t* outPixelFormatFourCC) const {
+    return getDefault(width, height, format, layerCount, usage,
+                      gralloc4::MetadataType_PixelFormatFourCC, gralloc4::decodePixelFormatFourCC,
+                      outPixelFormatFourCC);
+}
+
+status_t Gralloc4Mapper::getDefaultPixelFormatModifier(uint32_t width, uint32_t height,
+                                                       PixelFormat format, uint32_t layerCount,
+                                                       uint64_t usage,
+                                                       uint64_t* outPixelFormatModifier) const {
+    return getDefault(width, height, format, layerCount, usage,
+                      gralloc4::MetadataType_PixelFormatModifier,
+                      gralloc4::decodePixelFormatModifier, outPixelFormatModifier);
+}
+
+status_t Gralloc4Mapper::getDefaultAllocationSize(uint32_t width, uint32_t height,
+                                                  PixelFormat format, uint32_t layerCount,
+                                                  uint64_t usage,
+                                                  uint64_t* outAllocationSize) const {
+    return getDefault(width, height, format, layerCount, usage,
+                      gralloc4::MetadataType_AllocationSize, gralloc4::decodeAllocationSize,
+                      outAllocationSize);
+}
+
+status_t Gralloc4Mapper::getDefaultProtectedContent(uint32_t width, uint32_t height,
+                                                    PixelFormat format, uint32_t layerCount,
+                                                    uint64_t usage,
+                                                    uint64_t* outProtectedContent) const {
+    return getDefault(width, height, format, layerCount, usage,
+                      gralloc4::MetadataType_ProtectedContent, gralloc4::decodeProtectedContent,
+                      outProtectedContent);
+}
+
+status_t Gralloc4Mapper::getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format,
+                                               uint32_t layerCount, uint64_t usage,
+                                               ExtendableType* outCompression) const {
+    return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_Compression,
+                      gralloc4::decodeCompression, outCompression);
+}
+
+status_t Gralloc4Mapper::getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format,
+                                               uint32_t layerCount, uint64_t usage,
+                                               ui::Compression* outCompression) const {
+    if (!outCompression) {
+        return BAD_VALUE;
+    }
+    ExtendableType compression;
+    status_t error = getDefaultCompression(width, height, format, layerCount, usage, &compression);
+    if (error) {
+        return error;
+    }
+    if (!gralloc4::isStandardCompression(compression)) {
+        return BAD_TYPE;
+    }
+    *outCompression = gralloc4::getStandardCompressionValue(compression);
+    return NO_ERROR;
+}
+
+status_t Gralloc4Mapper::getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
+                                              uint32_t layerCount, uint64_t usage,
+                                              ExtendableType* outInterlaced) const {
+    return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_Interlaced,
+                      gralloc4::decodeInterlaced, outInterlaced);
+}
+
+status_t Gralloc4Mapper::getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
+                                              uint32_t layerCount, uint64_t usage,
+                                              ui::Interlaced* outInterlaced) const {
+    if (!outInterlaced) {
+        return BAD_VALUE;
+    }
+    ExtendableType interlaced;
+    status_t error = getDefaultInterlaced(width, height, format, layerCount, usage, &interlaced);
+    if (error) {
+        return error;
+    }
+    if (!gralloc4::isStandardInterlaced(interlaced)) {
+        return BAD_TYPE;
+    }
+    *outInterlaced = gralloc4::getStandardInterlacedValue(interlaced);
+    return NO_ERROR;
+}
+
+status_t Gralloc4Mapper::getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format,
+                                                uint32_t layerCount, uint64_t usage,
+                                                ExtendableType* outChromaSiting) const {
+    return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_ChromaSiting,
+                      gralloc4::decodeChromaSiting, outChromaSiting);
+}
+
+status_t Gralloc4Mapper::getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format,
+                                                uint32_t layerCount, uint64_t usage,
+                                                ui::ChromaSiting* outChromaSiting) const {
+    if (!outChromaSiting) {
+        return BAD_VALUE;
+    }
+    ExtendableType chromaSiting;
+    status_t error =
+            getDefaultChromaSiting(width, height, format, layerCount, usage, &chromaSiting);
+    if (error) {
+        return error;
+    }
+    if (!gralloc4::isStandardChromaSiting(chromaSiting)) {
+        return BAD_TYPE;
+    }
+    *outChromaSiting = gralloc4::getStandardChromaSitingValue(chromaSiting);
+    return NO_ERROR;
+}
+
+status_t Gralloc4Mapper::getDefaultPlaneLayouts(
+        uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage,
+        std::vector<ui::PlaneLayout>* outPlaneLayouts) const {
+    return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_PlaneLayouts,
+                      gralloc4::decodePlaneLayouts, outPlaneLayouts);
+}
+
+std::vector<MetadataTypeDescription> Gralloc4Mapper::listSupportedMetadataTypes() const {
+    hidl_vec<MetadataTypeDescription> descriptions;
+    Error error;
+    auto ret = mMapper->listSupportedMetadataTypes(
+            [&](const auto& tmpError, const auto& tmpDescriptions) {
+                error = tmpError;
+                descriptions = tmpDescriptions;
+            });
+
+    if (!ret.isOk()) {
+        error = kTransactionError;
+    }
+
+    if (error != Error::NONE) {
+        ALOGE("listSupportedMetadataType() failed with %d", error);
+        return {};
+    }
+
+    return static_cast<std::vector<MetadataTypeDescription>>(descriptions);
+}
+
+template <class T>
+status_t Gralloc4Mapper::metadataDumpHelper(const BufferDump& bufferDump,
+                                            StandardMetadataType metadataType,
+                                            DecodeFunction<T> decodeFunction, T* outT) const {
+    const auto& metadataDump = bufferDump.metadataDump;
+
+    auto itr =
+            std::find_if(metadataDump.begin(), metadataDump.end(),
+                         [&](const MetadataDump& tmpMetadataDump) {
+                             if (!gralloc4::isStandardMetadataType(tmpMetadataDump.metadataType)) {
+                                 return false;
+                             }
+                             return metadataType ==
+                                     gralloc4::getStandardMetadataTypeValue(
+                                             tmpMetadataDump.metadataType);
+                         });
+    if (itr == metadataDump.end()) {
+        return BAD_VALUE;
+    }
+
+    return decodeFunction(itr->metadata, outT);
+}
+
+status_t Gralloc4Mapper::bufferDumpHelper(const BufferDump& bufferDump, std::ostringstream* outDump,
+                                          uint64_t* outAllocationSize, bool less) const {
+    uint64_t bufferId;
+    std::string name;
+    uint64_t width;
+    uint64_t height;
+    uint64_t layerCount;
+    ui::PixelFormat pixelFormatRequested;
+    uint32_t pixelFormatFourCC;
+    uint64_t pixelFormatModifier;
+    uint64_t usage;
+    uint64_t allocationSize;
+    uint64_t protectedContent;
+    ExtendableType compression;
+    ExtendableType interlaced;
+    ExtendableType chromaSiting;
+    std::vector<ui::PlaneLayout> planeLayouts;
+
+    status_t error = metadataDumpHelper(bufferDump, StandardMetadataType::BUFFER_ID,
+                                        gralloc4::decodeBufferId, &bufferId);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::NAME, gralloc4::decodeName, &name);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::WIDTH, gralloc4::decodeWidth,
+                               &width);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::HEIGHT, gralloc4::decodeHeight,
+                               &height);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::LAYER_COUNT,
+                               gralloc4::decodeLayerCount, &layerCount);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::PIXEL_FORMAT_REQUESTED,
+                               gralloc4::decodePixelFormatRequested, &pixelFormatRequested);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::PIXEL_FORMAT_FOURCC,
+                               gralloc4::decodePixelFormatFourCC, &pixelFormatFourCC);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::PIXEL_FORMAT_MODIFIER,
+                               gralloc4::decodePixelFormatModifier, &pixelFormatModifier);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::USAGE, gralloc4::decodeUsage,
+                               &usage);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::ALLOCATION_SIZE,
+                               gralloc4::decodeAllocationSize, &allocationSize);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::PROTECTED_CONTENT,
+                               gralloc4::decodeProtectedContent, &protectedContent);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::COMPRESSION,
+                               gralloc4::decodeCompression, &compression);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::INTERLACED,
+                               gralloc4::decodeInterlaced, &interlaced);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::CHROMA_SITING,
+                               gralloc4::decodeChromaSiting, &chromaSiting);
+    if (error != NO_ERROR) {
+        return error;
+    }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::PLANE_LAYOUTS,
+                               gralloc4::decodePlaneLayouts, &planeLayouts);
+    if (error != NO_ERROR) {
+        return error;
+    }
+
+    if (outAllocationSize) {
+        *outAllocationSize = allocationSize;
+    }
+    double allocationSizeKiB = static_cast<double>(allocationSize) / 1024;
+
+    *outDump << "+ name:" << name << ", id:" << bufferId << ", size:" << allocationSizeKiB
+             << "KiB, w/h:" << width << "x" << height << ", usage: 0x" << std::hex << usage
+             << std::dec << ", req fmt:" << static_cast<int32_t>(pixelFormatRequested)
+             << ", fourcc/mod:" << pixelFormatFourCC << "/" << pixelFormatModifier
+             << ", compressed: ";
+
+    if (less) {
+        bool isCompressed = !gralloc4::isStandardCompression(compression) ||
+                (gralloc4::getStandardCompressionValue(compression) != ui::Compression::NONE);
+        *outDump << std::boolalpha << isCompressed << "\n";
+    } else {
+        *outDump << gralloc4::getCompressionName(compression) << "\n";
+    }
+
+    bool firstPlane = true;
+    for (const auto& planeLayout : planeLayouts) {
+        if (firstPlane) {
+            firstPlane = false;
+            *outDump << "\tplanes: ";
+        } else {
+            *outDump << "\t        ";
+        }
+
+        for (size_t i = 0; i < planeLayout.components.size(); i++) {
+            const auto& planeLayoutComponent = planeLayout.components[i];
+            *outDump << gralloc4::getPlaneLayoutComponentTypeName(planeLayoutComponent.type);
+            if (i < planeLayout.components.size() - 1) {
+                *outDump << "/";
+            } else {
+                *outDump << ":\t";
+            }
+        }
+        *outDump << " w/h:" << planeLayout.widthInSamples << "x" << planeLayout.heightInSamples
+                 << ", stride:" << planeLayout.strideInBytes
+                 << " bytes, size:" << planeLayout.totalSizeInBytes;
+        if (!less) {
+            *outDump << ", inc:" << planeLayout.sampleIncrementInBits
+                     << " bits, subsampling w/h:" << planeLayout.horizontalSubsampling << "x"
+                     << planeLayout.verticalSubsampling;
+        }
+        *outDump << "\n";
+    }
+
+    if (!less) {
+        *outDump << "\tlayer cnt: " << layerCount << ", protected content: " << protectedContent
+                 << ", interlaced: " << gralloc4::getInterlacedName(interlaced)
+                 << ", chroma siting:" << gralloc4::getChromaSitingName(chromaSiting) << "\n";
+    }
+
+    return NO_ERROR;
+}
+
+std::string Gralloc4Mapper::dumpBuffer(buffer_handle_t bufferHandle, bool less) const {
+    auto buffer = const_cast<native_handle_t*>(bufferHandle);
+
+    BufferDump bufferDump;
+    Error error;
+    auto ret = mMapper->dumpBuffer(buffer, [&](const auto& tmpError, const auto& tmpBufferDump) {
+        error = tmpError;
+        bufferDump = tmpBufferDump;
+    });
+
+    if (!ret.isOk()) {
+        error = kTransactionError;
+    }
+
+    if (error != Error::NONE) {
+        ALOGE("dumpBuffer() failed with %d", error);
+        return "";
+    }
+
+    std::ostringstream stream;
+    stream.precision(2);
+
+    status_t err = bufferDumpHelper(bufferDump, &stream, nullptr, less);
+    if (err != NO_ERROR) {
+        ALOGE("bufferDumpHelper() failed with %d", err);
+        return "";
+    }
+
+    return stream.str();
+}
+
+std::string Gralloc4Mapper::dumpBuffers(bool less) const {
+    hidl_vec<BufferDump> bufferDumps;
+    Error error;
+    auto ret = mMapper->dumpBuffers([&](const auto& tmpError, const auto& tmpBufferDump) {
+        error = tmpError;
+        bufferDumps = tmpBufferDump;
+    });
+
+    if (!ret.isOk()) {
+        error = kTransactionError;
+    }
+
+    if (error != Error::NONE) {
+        ALOGE("dumpBuffer() failed with %d", error);
+        return "";
+    }
+
+    uint64_t totalAllocationSize = 0;
+    std::ostringstream stream;
+    stream.precision(2);
+
+    stream << "Imported gralloc buffers:\n";
+
+    for (const auto& bufferDump : bufferDumps) {
+        uint64_t allocationSize = 0;
+        status_t err = bufferDumpHelper(bufferDump, &stream, &allocationSize, less);
+        if (err != NO_ERROR) {
+            ALOGE("bufferDumpHelper() failed with %d", err);
+            return "";
+        }
+        totalAllocationSize += allocationSize;
+    }
+
+    double totalAllocationSizeKiB = static_cast<double>(totalAllocationSize) / 1024;
+    stream << "Total imported by gralloc: " << totalAllocationSizeKiB << "KiB\n";
+    return stream.str();
+}
+
 Gralloc4Allocator::Gralloc4Allocator(const Gralloc4Mapper& mapper) : mMapper(mapper) {
     mAllocator = IAllocator::getService();
     if (mAllocator == nullptr) {
@@ -352,19 +1042,16 @@
     return mAllocator != nullptr;
 }
 
-std::string Gralloc4Allocator::dumpDebugInfo() const {
-    std::string debugInfo;
-
-    mAllocator->dumpDebugInfo([&](const auto& tmpDebugInfo) { debugInfo = tmpDebugInfo.c_str(); });
-
-    return debugInfo;
+std::string Gralloc4Allocator::dumpDebugInfo(bool less) const {
+    return mMapper.dumpBuffers(less);
 }
 
-status_t Gralloc4Allocator::allocate(uint32_t width, uint32_t height, android::PixelFormat format,
-                                     uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                                     uint32_t* outStride, buffer_handle_t* outBufferHandles) const {
+status_t Gralloc4Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height,
+                                     android::PixelFormat format, uint32_t layerCount,
+                                     uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
+                                     buffer_handle_t* outBufferHandles, bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo(width, height, format, layerCount, usage, &descriptorInfo);
+    sBufferDescriptorInfo(requestorName, width, height, format, layerCount, usage, &descriptorInfo);
 
     BufferDescriptor descriptor;
     status_t error = mMapper.createDescriptor(static_cast<void*>(&descriptorInfo),
@@ -381,16 +1068,31 @@
                                             return;
                                         }
 
-                                        // import buffers
-                                        for (uint32_t i = 0; i < bufferCount; i++) {
-                                            error = mMapper.importBuffer(tmpBuffers[i],
-                                                                         &outBufferHandles[i]);
-                                            if (error != NO_ERROR) {
-                                                for (uint32_t j = 0; j < i; j++) {
-                                                    mMapper.freeBuffer(outBufferHandles[j]);
-                                                    outBufferHandles[j] = nullptr;
+                                        if (importBuffers) {
+                                            for (uint32_t i = 0; i < bufferCount; i++) {
+                                                error = mMapper.importBuffer(tmpBuffers[i],
+                                                                             &outBufferHandles[i]);
+                                                if (error != NO_ERROR) {
+                                                    for (uint32_t j = 0; j < i; j++) {
+                                                        mMapper.freeBuffer(outBufferHandles[j]);
+                                                        outBufferHandles[j] = nullptr;
+                                                    }
+                                                    return;
                                                 }
-                                                return;
+                                            }
+                                        } else {
+                                            for (uint32_t i = 0; i < bufferCount; i++) {
+                                                outBufferHandles[i] = native_handle_clone(
+                                                        tmpBuffers[i].getNativeHandle());
+                                                if (!outBufferHandles[i]) {
+                                                    for (uint32_t j = 0; j < i; j++) {
+                                                        auto buffer = const_cast<native_handle_t*>(
+                                                                outBufferHandles[j]);
+                                                        native_handle_close(buffer);
+                                                        native_handle_delete(buffer);
+                                                        outBufferHandles[j] = nullptr;
+                                                    }
+                                                }
                                             }
                                         }
                                         *outStride = tmpStride;
diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp
index 579e68e..05fc590 100644
--- a/libs/ui/GraphicBuffer.cpp
+++ b/libs/ui/GraphicBuffer.cpp
@@ -27,7 +27,6 @@
 #include <ui/BufferHubBuffer.h>
 #endif // LIBUI_IN_VNDK
 
-#include <ui/Gralloc2.h>
 #include <ui/GraphicBufferAllocator.h>
 #include <ui/GraphicBufferMapper.h>
 #include <utils/Trace.h>
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index fcc2547..943d13e 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -69,20 +69,20 @@
 
 GraphicBufferAllocator::~GraphicBufferAllocator() {}
 
-size_t GraphicBufferAllocator::getTotalSize() const {
+uint64_t GraphicBufferAllocator::getTotalSize() const {
     Mutex::Autolock _l(sLock);
-    size_t total = 0;
+    uint64_t total = 0;
     for (size_t i = 0; i < sAllocList.size(); ++i) {
         total += sAllocList.valueAt(i).size;
     }
     return total;
 }
 
-void GraphicBufferAllocator::dump(std::string& result) const {
+void GraphicBufferAllocator::dump(std::string& result, bool less) const {
     Mutex::Autolock _l(sLock);
     KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
-    size_t total = 0;
-    result.append("Allocated buffers:\n");
+    uint64_t total = 0;
+    result.append("GraphicBufferAllocator buffers:\n");
     const size_t c = list.size();
     for (size_t i=0 ; i<c ; i++) {
         const alloc_rec_t& rec(list.valueAt(i));
@@ -99,23 +99,22 @@
         }
         total += rec.size;
     }
-    StringAppendF(&result, "Total allocated (estimate): %.2f KB\n", static_cast<double>(total) / 1024.0);
+    StringAppendF(&result, "Total allocated by GraphicBufferAllocator (estimate): %.2f KB\n",
+                  static_cast<double>(total) / 1024.0);
 
-    result.append(mAllocator->dumpDebugInfo());
+    result.append(mAllocator->dumpDebugInfo(less));
 }
 
-void GraphicBufferAllocator::dumpToSystemLog()
-{
+void GraphicBufferAllocator::dumpToSystemLog(bool less) {
     std::string s;
-    GraphicBufferAllocator::getInstance().dump(s);
+    GraphicBufferAllocator::getInstance().dump(s, less);
     ALOGD("%s", s.c_str());
 }
 
-status_t GraphicBufferAllocator::allocate(uint32_t width, uint32_t height,
-        PixelFormat format, uint32_t layerCount, uint64_t usage,
-        buffer_handle_t* handle, uint32_t* stride,
-        uint64_t /*graphicBufferId*/, std::string requestorName)
-{
+status_t GraphicBufferAllocator::allocateHelper(uint32_t width, uint32_t height, PixelFormat format,
+                                                uint32_t layerCount, uint64_t usage,
+                                                buffer_handle_t* handle, uint32_t* stride,
+                                                std::string requestorName, bool importBuffer) {
     ATRACE_CALL();
 
     // make sure to not allocate a N x 0 or 0 x N buffer, since this is
@@ -138,8 +137,18 @@
     // TODO(b/72323293, b/72703005): Remove these invalid bits from callers
     usage &= ~static_cast<uint64_t>((1 << 10) | (1 << 13));
 
-    status_t error =
-            mAllocator->allocate(width, height, format, layerCount, usage, 1, stride, handle);
+    status_t error = mAllocator->allocate(requestorName, width, height, format, layerCount, usage,
+                                          1, stride, handle, importBuffer);
+    if (error != NO_ERROR) {
+        ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
+              "usage %" PRIx64 ": %d",
+              width, height, layerCount, format, usage, error);
+        return NO_MEMORY;
+    }
+
+    if (!importBuffer) {
+        return NO_ERROR;
+    }
     size_t bufSize;
 
     // if stride has no meaning or is too large,
@@ -151,28 +160,44 @@
         bufSize = static_cast<size_t>((*stride)) * height * bpp;
     }
 
-    if (error == NO_ERROR) {
-        Mutex::Autolock _l(sLock);
-        KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
-        alloc_rec_t rec;
-        rec.width = width;
-        rec.height = height;
-        rec.stride = *stride;
-        rec.format = format;
-        rec.layerCount = layerCount;
-        rec.usage = usage;
-        rec.size = bufSize;
-        rec.requestorName = std::move(requestorName);
-        list.add(*handle, rec);
+    Mutex::Autolock _l(sLock);
+    KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
+    alloc_rec_t rec;
+    rec.width = width;
+    rec.height = height;
+    rec.stride = *stride;
+    rec.format = format;
+    rec.layerCount = layerCount;
+    rec.usage = usage;
+    rec.size = bufSize;
+    rec.requestorName = std::move(requestorName);
+    list.add(*handle, rec);
 
-        return NO_ERROR;
-    } else {
-        ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
-                "usage %" PRIx64 ": %d",
-                width, height, layerCount, format, usage,
-                error);
-        return NO_MEMORY;
-    }
+    return NO_ERROR;
+}
+status_t GraphicBufferAllocator::allocate(uint32_t width, uint32_t height, PixelFormat format,
+                                          uint32_t layerCount, uint64_t usage,
+                                          buffer_handle_t* handle, uint32_t* stride,
+                                          std::string requestorName) {
+    return allocateHelper(width, height, format, layerCount, usage, handle, stride, requestorName,
+                          true);
+}
+
+status_t GraphicBufferAllocator::allocateRawHandle(uint32_t width, uint32_t height,
+                                                   PixelFormat format, uint32_t layerCount,
+                                                   uint64_t usage, buffer_handle_t* handle,
+                                                   uint32_t* stride, std::string requestorName) {
+    return allocateHelper(width, height, format, layerCount, usage, handle, stride, requestorName,
+                          false);
+}
+
+// DEPRECATED
+status_t GraphicBufferAllocator::allocate(uint32_t width, uint32_t height, PixelFormat format,
+                                          uint32_t layerCount, uint64_t usage,
+                                          buffer_handle_t* handle, uint32_t* stride,
+                                          uint64_t /*graphicBufferId*/, std::string requestorName) {
+    return allocateHelper(width, height, format, layerCount, usage, handle, stride, requestorName,
+                          true);
 }
 
 status_t GraphicBufferAllocator::free(buffer_handle_t handle)
diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp
index 4d087d1..8540fd3 100644
--- a/libs/ui/GraphicBufferMapper.cpp
+++ b/libs/ui/GraphicBufferMapper.cpp
@@ -71,6 +71,17 @@
     LOG_ALWAYS_FATAL("gralloc-mapper is missing");
 }
 
+void GraphicBufferMapper::dumpBuffer(buffer_handle_t bufferHandle, std::string& result,
+                                     bool less) const {
+    result.append(mMapper->dumpBuffer(bufferHandle, less));
+}
+
+void GraphicBufferMapper::dumpBufferToSystemLog(buffer_handle_t bufferHandle, bool less) {
+    std::string s;
+    GraphicBufferMapper::getInstance().dumpBuffer(bufferHandle, s, less);
+    ALOGD("%s", s.c_str());
+}
+
 status_t GraphicBufferMapper::importBuffer(buffer_handle_t rawHandle,
         uint32_t width, uint32_t height, uint32_t layerCount,
         PixelFormat format, uint64_t usage, uint32_t stride,
@@ -177,5 +188,182 @@
                                           uint64_t usage, bool* outSupported) {
     return mMapper->isSupported(width, height, format, layerCount, usage, outSupported);
 }
+
+status_t GraphicBufferMapper::getBufferId(buffer_handle_t bufferHandle, uint64_t* outBufferId) {
+    return mMapper->getBufferId(bufferHandle, outBufferId);
+}
+
+status_t GraphicBufferMapper::getName(buffer_handle_t bufferHandle, std::string* outName) {
+    return mMapper->getName(bufferHandle, outName);
+}
+
+status_t GraphicBufferMapper::getWidth(buffer_handle_t bufferHandle, uint64_t* outWidth) {
+    return mMapper->getWidth(bufferHandle, outWidth);
+}
+
+status_t GraphicBufferMapper::getHeight(buffer_handle_t bufferHandle, uint64_t* outHeight) {
+    return mMapper->getHeight(bufferHandle, outHeight);
+}
+
+status_t GraphicBufferMapper::getLayerCount(buffer_handle_t bufferHandle, uint64_t* outLayerCount) {
+    return mMapper->getLayerCount(bufferHandle, outLayerCount);
+}
+
+status_t GraphicBufferMapper::getPixelFormatRequested(buffer_handle_t bufferHandle,
+                                                      ui::PixelFormat* outPixelFormatRequested) {
+    return mMapper->getPixelFormatRequested(bufferHandle, outPixelFormatRequested);
+}
+
+status_t GraphicBufferMapper::getPixelFormatFourCC(buffer_handle_t bufferHandle,
+                                                   uint32_t* outPixelFormatFourCC) {
+    return mMapper->getPixelFormatFourCC(bufferHandle, outPixelFormatFourCC);
+}
+
+status_t GraphicBufferMapper::getPixelFormatModifier(buffer_handle_t bufferHandle,
+                                                     uint64_t* outPixelFormatModifier) {
+    return mMapper->getPixelFormatModifier(bufferHandle, outPixelFormatModifier);
+}
+
+status_t GraphicBufferMapper::getUsage(buffer_handle_t bufferHandle, uint64_t* outUsage) {
+    return mMapper->getUsage(bufferHandle, outUsage);
+}
+
+status_t GraphicBufferMapper::getAllocationSize(buffer_handle_t bufferHandle,
+                                                uint64_t* outAllocationSize) {
+    return mMapper->getAllocationSize(bufferHandle, outAllocationSize);
+}
+
+status_t GraphicBufferMapper::getProtectedContent(buffer_handle_t bufferHandle,
+                                                  uint64_t* outProtectedContent) {
+    return mMapper->getProtectedContent(bufferHandle, outProtectedContent);
+}
+
+status_t GraphicBufferMapper::getCompression(
+        buffer_handle_t bufferHandle,
+        aidl::android::hardware::graphics::common::ExtendableType* outCompression) {
+    return mMapper->getCompression(bufferHandle, outCompression);
+}
+
+status_t GraphicBufferMapper::getCompression(buffer_handle_t bufferHandle,
+                                             ui::Compression* outCompression) {
+    return mMapper->getCompression(bufferHandle, outCompression);
+}
+
+status_t GraphicBufferMapper::getInterlaced(
+        buffer_handle_t bufferHandle,
+        aidl::android::hardware::graphics::common::ExtendableType* outInterlaced) {
+    return mMapper->getInterlaced(bufferHandle, outInterlaced);
+}
+
+status_t GraphicBufferMapper::getInterlaced(buffer_handle_t bufferHandle,
+                                            ui::Interlaced* outInterlaced) {
+    return mMapper->getInterlaced(bufferHandle, outInterlaced);
+}
+
+status_t GraphicBufferMapper::getChromaSiting(
+        buffer_handle_t bufferHandle,
+        aidl::android::hardware::graphics::common::ExtendableType* outChromaSiting) {
+    return mMapper->getChromaSiting(bufferHandle, outChromaSiting);
+}
+
+status_t GraphicBufferMapper::getChromaSiting(buffer_handle_t bufferHandle,
+                                              ui::ChromaSiting* outChromaSiting) {
+    return mMapper->getChromaSiting(bufferHandle, outChromaSiting);
+}
+
+status_t GraphicBufferMapper::getPlaneLayouts(buffer_handle_t bufferHandle,
+                                              std::vector<ui::PlaneLayout>* outPlaneLayouts) {
+    return mMapper->getPlaneLayouts(bufferHandle, outPlaneLayouts);
+}
+
+status_t GraphicBufferMapper::getDataspace(buffer_handle_t bufferHandle,
+                                           ui::Dataspace* outDataspace) {
+    return mMapper->getDataspace(bufferHandle, outDataspace);
+}
+
+status_t GraphicBufferMapper::getBlendMode(buffer_handle_t bufferHandle,
+                                           ui::BlendMode* outBlendMode) {
+    return mMapper->getBlendMode(bufferHandle, outBlendMode);
+}
+
+status_t GraphicBufferMapper::getDefaultPixelFormatFourCC(uint32_t width, uint32_t height,
+                                                          PixelFormat format, uint32_t layerCount,
+                                                          uint64_t usage,
+                                                          uint32_t* outPixelFormatFourCC) {
+    return mMapper->getDefaultPixelFormatFourCC(width, height, format, layerCount, usage,
+                                                outPixelFormatFourCC);
+}
+
+status_t GraphicBufferMapper::getDefaultPixelFormatModifier(uint32_t width, uint32_t height,
+                                                            PixelFormat format, uint32_t layerCount,
+                                                            uint64_t usage,
+                                                            uint64_t* outPixelFormatModifier) {
+    return mMapper->getDefaultPixelFormatModifier(width, height, format, layerCount, usage,
+                                                  outPixelFormatModifier);
+}
+
+status_t GraphicBufferMapper::getDefaultAllocationSize(uint32_t width, uint32_t height,
+                                                       PixelFormat format, uint32_t layerCount,
+                                                       uint64_t usage,
+                                                       uint64_t* outAllocationSize) {
+    return mMapper->getDefaultAllocationSize(width, height, format, layerCount, usage,
+                                             outAllocationSize);
+}
+
+status_t GraphicBufferMapper::getDefaultProtectedContent(uint32_t width, uint32_t height,
+                                                         PixelFormat format, uint32_t layerCount,
+                                                         uint64_t usage,
+                                                         uint64_t* outProtectedContent) {
+    return mMapper->getDefaultProtectedContent(width, height, format, layerCount, usage,
+                                               outProtectedContent);
+}
+
+status_t GraphicBufferMapper::getDefaultCompression(
+        uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage,
+        aidl::android::hardware::graphics::common::ExtendableType* outCompression) {
+    return mMapper->getDefaultCompression(width, height, format, layerCount, usage, outCompression);
+}
+
+status_t GraphicBufferMapper::getDefaultCompression(uint32_t width, uint32_t height,
+                                                    PixelFormat format, uint32_t layerCount,
+                                                    uint64_t usage,
+                                                    ui::Compression* outCompression) {
+    return mMapper->getDefaultCompression(width, height, format, layerCount, usage, outCompression);
+}
+
+status_t GraphicBufferMapper::getDefaultInterlaced(
+        uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage,
+        aidl::android::hardware::graphics::common::ExtendableType* outInterlaced) {
+    return mMapper->getDefaultInterlaced(width, height, format, layerCount, usage, outInterlaced);
+}
+
+status_t GraphicBufferMapper::getDefaultInterlaced(uint32_t width, uint32_t height,
+                                                   PixelFormat format, uint32_t layerCount,
+                                                   uint64_t usage, ui::Interlaced* outInterlaced) {
+    return mMapper->getDefaultInterlaced(width, height, format, layerCount, usage, outInterlaced);
+}
+
+status_t GraphicBufferMapper::getDefaultChromaSiting(
+        uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage,
+        aidl::android::hardware::graphics::common::ExtendableType* outChromaSiting) {
+    return mMapper->getDefaultChromaSiting(width, height, format, layerCount, usage,
+                                           outChromaSiting);
+}
+
+status_t GraphicBufferMapper::getDefaultChromaSiting(uint32_t width, uint32_t height,
+                                                     PixelFormat format, uint32_t layerCount,
+                                                     uint64_t usage,
+                                                     ui::ChromaSiting* outChromaSiting) {
+    return mMapper->getDefaultChromaSiting(width, height, format, layerCount, usage,
+                                           outChromaSiting);
+}
+
+status_t GraphicBufferMapper::getDefaultPlaneLayouts(
+        uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage,
+        std::vector<ui::PlaneLayout>* outPlaneLayouts) {
+    return mMapper->getDefaultPlaneLayouts(width, height, format, layerCount, usage,
+                                           outPlaneLayouts);
+}
+
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/libs/ui/Region.cpp b/libs/ui/Region.cpp
index 1222cd6..bf487c4 100644
--- a/libs/ui/Region.cpp
+++ b/libs/ui/Region.cpp
@@ -23,11 +23,10 @@
 
 #include <utils/Log.h>
 
+#include <ui/Point.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
-#include <ui/Point.h>
-
-#include <private/ui/RegionHelper.h>
+#include <ui/RegionHelper.h>
 
 // ----------------------------------------------------------------------------
 
@@ -281,6 +280,20 @@
     return begin() == region.begin();
 }
 
+bool Region::hasSameRects(const Region& other) const {
+    size_t thisRectCount = 0;
+    android::Rect const* thisRects = getArray(&thisRectCount);
+    size_t otherRectCount = 0;
+    android::Rect const* otherRects = other.getArray(&otherRectCount);
+
+    if (thisRectCount != otherRectCount) return false;
+
+    for (size_t i = 0; i < thisRectCount; i++) {
+        if (thisRects[i] != otherRects[i]) return false;
+    }
+    return true;
+}
+
 // ----------------------------------------------------------------------------
 
 void Region::addRectUnchecked(int l, int t, int r, int b)
diff --git a/libs/ui/Transform.cpp b/libs/ui/Transform.cpp
index 28c3f7b..06b6bfe 100644
--- a/libs/ui/Transform.cpp
+++ b/libs/ui/Transform.cpp
@@ -33,8 +33,8 @@
     : mMatrix(other.mMatrix), mType(other.mType) {
 }
 
-Transform::Transform(uint32_t orientation) {
-    set(orientation, 0, 0);
+Transform::Transform(uint32_t orientation, int w, int h) {
+    set(orientation, w, h);
 }
 
 Transform::~Transform() = default;
@@ -49,6 +49,15 @@
     return isZero(fabs(f) - 1.0f);
 }
 
+bool Transform::operator==(const Transform& other) const {
+    return mMatrix[0][0] == other.mMatrix[0][0] && mMatrix[0][1] == other.mMatrix[0][1] &&
+            mMatrix[0][2] == other.mMatrix[0][2] && mMatrix[1][0] == other.mMatrix[1][0] &&
+            mMatrix[1][1] == other.mMatrix[1][1] && mMatrix[1][2] == other.mMatrix[1][2] &&
+            mMatrix[2][0] == other.mMatrix[2][0] && mMatrix[2][1] == other.mMatrix[2][1] &&
+            mMatrix[2][2] == other.mMatrix[2][2];
+    ;
+}
+
 Transform Transform::operator * (const Transform& rhs) const
 {
     if (CC_LIKELY(mType == IDENTITY))
diff --git a/libs/ui/include/ui/FloatRect.h b/libs/ui/include/ui/FloatRect.h
index 4cd9a0b..bec2552 100644
--- a/libs/ui/include/ui/FloatRect.h
+++ b/libs/ui/include/ui/FloatRect.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <ostream>
+
 namespace android {
 
 class FloatRect {
@@ -52,4 +54,9 @@
     return a.left == b.left && a.top == b.top && a.right == b.right && a.bottom == b.bottom;
 }
 
+static inline void PrintTo(const FloatRect& rect, ::std::ostream* os) {
+    *os << "FloatRect(" << rect.left << ", " << rect.top << ", " << rect.right << ", "
+        << rect.bottom << ")";
+}
+
 }  // namespace android
diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index 6cc23f0..fcd959c 100644
--- a/libs/ui/include/ui/Gralloc.h
+++ b/libs/ui/include/ui/Gralloc.h
@@ -17,13 +17,15 @@
 #ifndef ANDROID_UI_GRALLOC_H
 #define ANDROID_UI_GRALLOC_H
 
-#include <string>
-
+#include <gralloctypes/Gralloc4.h>
 #include <hidl/HidlSupport.h>
+#include <ui/GraphicTypes.h>
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
 #include <utils/StrongPointer.h>
 
+#include <string>
+
 namespace android {
 
 // A wrapper to IMapper
@@ -33,6 +35,10 @@
 
     virtual bool isLoaded() const = 0;
 
+    virtual std::string dumpBuffer(buffer_handle_t /*bufferHandle*/, bool /*less*/) const {
+        return "";
+    }
+
     virtual status_t createDescriptor(void* bufferDescriptorInfo,
                                       void* outBufferDescriptor) const = 0;
 
@@ -74,8 +80,163 @@
     // allocated if resources are available.  If false, a buffer with the given specifications will
     // never successfully allocate on this device. Note that this function is not guaranteed to be
     // supported on all devices, in which case a status_t of INVALID_OPERATION will be returned.
-    virtual status_t isSupported(uint32_t width, uint32_t height, android::PixelFormat format,
-                                 uint32_t layerCount, uint64_t usage, bool* outSupported) const = 0;
+    virtual status_t isSupported(uint32_t /*width*/, uint32_t /*height*/,
+                                 android::PixelFormat /*format*/, uint32_t /*layerCount*/,
+                                 uint64_t /*usage*/, bool* /*outSupported*/) const {
+        return INVALID_OPERATION;
+    }
+
+    virtual status_t getBufferId(buffer_handle_t /*bufferHandle*/,
+                                 uint64_t* /*outBufferId*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getName(buffer_handle_t /*bufferHandle*/, std::string* /*outName*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getWidth(buffer_handle_t /*bufferHandle*/, uint64_t* /*outWidth*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getHeight(buffer_handle_t /*bufferHandle*/, uint64_t* /*outHeight*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getLayerCount(buffer_handle_t /*bufferHandle*/,
+                                   uint64_t* /*outLayerCount*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getPixelFormatRequested(buffer_handle_t /*bufferHandle*/,
+                                             ui::PixelFormat* /*outPixelFormatRequested*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getPixelFormatFourCC(buffer_handle_t /*bufferHandle*/,
+                                          uint32_t* /*outPixelFormatFourCC*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getPixelFormatModifier(buffer_handle_t /*bufferHandle*/,
+                                            uint64_t* /*outPixelFormatModifier*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getUsage(buffer_handle_t /*bufferHandle*/, uint64_t* /*outUsage*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getAllocationSize(buffer_handle_t /*bufferHandle*/,
+                                       uint64_t* /*outAllocationSize*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getProtectedContent(buffer_handle_t /*bufferHandle*/,
+                                         uint64_t* /*outProtectedContent*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getCompression(
+            buffer_handle_t /*bufferHandle*/,
+            aidl::android::hardware::graphics::common::ExtendableType* /*outCompression*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getCompression(buffer_handle_t /*bufferHandle*/,
+                                    ui::Compression* /*outCompression*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getInterlaced(
+            buffer_handle_t /*bufferHandle*/,
+            aidl::android::hardware::graphics::common::ExtendableType* /*outInterlaced*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getInterlaced(buffer_handle_t /*bufferHandle*/,
+                                   ui::Interlaced* /*outInterlaced*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getChromaSiting(
+            buffer_handle_t /*bufferHandle*/,
+            aidl::android::hardware::graphics::common::ExtendableType* /*outChromaSiting*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getChromaSiting(buffer_handle_t /*bufferHandle*/,
+                                     ui::ChromaSiting* /*outChromaSiting*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getPlaneLayouts(buffer_handle_t /*bufferHandle*/,
+                                     std::vector<ui::PlaneLayout>* /*outPlaneLayouts*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getDataspace(buffer_handle_t /*bufferHandle*/,
+                                  ui::Dataspace* /*outDataspace*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getBlendMode(buffer_handle_t /*bufferHandle*/,
+                                  ui::BlendMode* /*outBlendMode*/) const {
+        return INVALID_OPERATION;
+    }
+
+    virtual status_t getDefaultPixelFormatFourCC(uint32_t /*width*/, uint32_t /*height*/,
+                                                 PixelFormat /*format*/, uint32_t /*layerCount*/,
+                                                 uint64_t /*usage*/,
+                                                 uint32_t* /*outPixelFormatFourCC*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getDefaultPixelFormatModifier(uint32_t /*width*/, uint32_t /*height*/,
+                                                   PixelFormat /*format*/, uint32_t /*layerCount*/,
+                                                   uint64_t /*usage*/,
+                                                   uint64_t* /*outPixelFormatModifier*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getDefaultAllocationSize(uint32_t /*width*/, uint32_t /*height*/,
+                                              PixelFormat /*format*/, uint32_t /*layerCount*/,
+                                              uint64_t /*usage*/,
+                                              uint64_t* /*outAllocationSize*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getDefaultProtectedContent(uint32_t /*width*/, uint32_t /*height*/,
+                                                PixelFormat /*format*/, uint32_t /*layerCount*/,
+                                                uint64_t /*usage*/,
+                                                uint64_t* /*outProtectedContent*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getDefaultCompression(
+            uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/,
+            uint32_t /*layerCount*/, uint64_t /*usage*/,
+            aidl::android::hardware::graphics::common::ExtendableType* /*outCompression*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getDefaultCompression(uint32_t /*width*/, uint32_t /*height*/,
+                                           PixelFormat /*format*/, uint32_t /*layerCount*/,
+                                           uint64_t /*usage*/,
+                                           ui::Compression* /*outCompression*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getDefaultInterlaced(
+            uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/,
+            uint32_t /*layerCount*/, uint64_t /*usage*/,
+            aidl::android::hardware::graphics::common::ExtendableType* /*outInterlaced*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getDefaultInterlaced(uint32_t /*width*/, uint32_t /*height*/,
+                                          PixelFormat /*format*/, uint32_t /*layerCount*/,
+                                          uint64_t /*usage*/,
+                                          ui::Interlaced* /*outInterlaced*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getDefaultChromaSiting(
+            uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/,
+            uint32_t /*layerCount*/, uint64_t /*usage*/,
+            aidl::android::hardware::graphics::common::ExtendableType* /*outChromaSiting*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getDefaultChromaSiting(uint32_t /*width*/, uint32_t /*height*/,
+                                            PixelFormat /*format*/, uint32_t /*layerCount*/,
+                                            uint64_t /*usage*/,
+                                            ui::ChromaSiting* /*outChromaSiting*/) const {
+        return INVALID_OPERATION;
+    }
+    virtual status_t getDefaultPlaneLayouts(
+            uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/,
+            uint32_t /*layerCount*/, uint64_t /*usage*/,
+            std::vector<ui::PlaneLayout>* /*outPlaneLayouts*/) const {
+        return INVALID_OPERATION;
+    }
+
+    virtual std::vector<android::hardware::graphics::mapper::V4_0::IMapper::MetadataTypeDescription>
+    listSupportedMetadataTypes() const {
+        return {};
+    }
 };
 
 // A wrapper to IAllocator
@@ -85,16 +246,18 @@
 
     virtual bool isLoaded() const = 0;
 
-    virtual std::string dumpDebugInfo() const = 0;
+    virtual std::string dumpDebugInfo(bool less = true) const = 0;
 
     /*
      * The returned buffers are already imported and must not be imported
      * again.  outBufferHandles must point to a space that can contain at
      * least "bufferCount" buffer_handle_t.
      */
-    virtual status_t allocate(uint32_t width, uint32_t height, PixelFormat format,
-                              uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                              uint32_t* outStride, buffer_handle_t* outBufferHandles) const = 0;
+    virtual status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
+                              PixelFormat format, uint32_t layerCount, uint64_t usage,
+                              uint32_t bufferCount, uint32_t* outStride,
+                              buffer_handle_t* outBufferHandles,
+                              bool importBuffers = true) const = 0;
 };
 
 } // namespace android
diff --git a/libs/ui/include/ui/Gralloc2.h b/libs/ui/include/ui/Gralloc2.h
index 948f597..f570c42 100644
--- a/libs/ui/include/ui/Gralloc2.h
+++ b/libs/ui/include/ui/Gralloc2.h
@@ -61,9 +61,6 @@
 
     int unlock(buffer_handle_t bufferHandle) const override;
 
-    status_t isSupported(uint32_t width, uint32_t height, android::PixelFormat format,
-                         uint32_t layerCount, uint64_t usage, bool* outSupported) const override;
-
 private:
     // Determines whether the passed info is compatible with the mapper.
     status_t validateBufferDescriptorInfo(
@@ -81,11 +78,12 @@
 
     bool isLoaded() const override;
 
-    std::string dumpDebugInfo() const override;
+    std::string dumpDebugInfo(bool less = true) const override;
 
-    status_t allocate(uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-                      uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
-                      buffer_handle_t* outBufferHandles) const override;
+    status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
+                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
+                      uint32_t* outStride, buffer_handle_t* outBufferHandles,
+                      bool importBuffers = true) const override;
 
 private:
     const Gralloc2Mapper& mMapper;
diff --git a/libs/ui/include/ui/Gralloc3.h b/libs/ui/include/ui/Gralloc3.h
index 0965f52..93a5077 100644
--- a/libs/ui/include/ui/Gralloc3.h
+++ b/libs/ui/include/ui/Gralloc3.h
@@ -79,11 +79,12 @@
 
     bool isLoaded() const override;
 
-    std::string dumpDebugInfo() const override;
+    std::string dumpDebugInfo(bool less = true) const override;
 
-    status_t allocate(uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-                      uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
-                      buffer_handle_t* outBufferHandles) const override;
+    status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
+                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
+                      uint32_t* outStride, buffer_handle_t* outBufferHandles,
+                      bool importBuffers = true) const override;
 
 private:
     const Gralloc3Mapper& mMapper;
diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h
index 14b65bc..af7c076 100644
--- a/libs/ui/include/ui/Gralloc4.h
+++ b/libs/ui/include/ui/Gralloc4.h
@@ -17,16 +17,17 @@
 #ifndef ANDROID_UI_GRALLOC4_H
 #define ANDROID_UI_GRALLOC4_H
 
-#include <string>
-
 #include <android/hardware/graphics/allocator/4.0/IAllocator.h>
 #include <android/hardware/graphics/common/1.1/types.h>
 #include <android/hardware/graphics/mapper/4.0/IMapper.h>
+#include <gralloctypes/Gralloc4.h>
 #include <ui/Gralloc.h>
-#include <ui/PixelFormat.h>
+#include <ui/GraphicTypes.h>
 #include <ui/Rect.h>
 #include <utils/StrongPointer.h>
 
+#include <string>
+
 namespace android {
 
 class Gralloc4Mapper : public GrallocMapper {
@@ -37,6 +38,9 @@
 
     bool isLoaded() const override;
 
+    std::string dumpBuffer(buffer_handle_t bufferHandle, bool less = true) const override;
+    std::string dumpBuffers(bool less = true) const;
+
     status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override;
 
     status_t importBuffer(const hardware::hidl_handle& rawHandle,
@@ -45,7 +49,7 @@
     void freeBuffer(buffer_handle_t bufferHandle) const override;
 
     status_t validateBufferSize(buffer_handle_t bufferHandle, uint32_t width, uint32_t height,
-                                android::PixelFormat format, uint32_t layerCount, uint64_t usage,
+                                PixelFormat format, uint32_t layerCount, uint64_t usage,
                                 uint32_t stride) const override;
 
     void getTransportSize(buffer_handle_t bufferHandle, uint32_t* outNumFds,
@@ -60,14 +64,117 @@
 
     int unlock(buffer_handle_t bufferHandle) const override;
 
-    status_t isSupported(uint32_t width, uint32_t height, android::PixelFormat format,
-                         uint32_t layerCount, uint64_t usage, bool* outSupported) const override;
+    status_t isSupported(uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+                         uint64_t usage, bool* outSupported) const override;
+
+    status_t getBufferId(buffer_handle_t bufferHandle, uint64_t* outBufferId) const override;
+    status_t getName(buffer_handle_t bufferHandle, std::string* outName) const override;
+    status_t getWidth(buffer_handle_t bufferHandle, uint64_t* outWidth) const override;
+    status_t getHeight(buffer_handle_t bufferHandle, uint64_t* outHeight) const override;
+    status_t getLayerCount(buffer_handle_t bufferHandle, uint64_t* outLayerCount) const override;
+    status_t getPixelFormatRequested(buffer_handle_t bufferHandle,
+                                     ui::PixelFormat* outPixelFormatRequested) const override;
+    status_t getPixelFormatFourCC(buffer_handle_t bufferHandle,
+                                  uint32_t* outPixelFormatFourCC) const override;
+    status_t getPixelFormatModifier(buffer_handle_t bufferHandle,
+                                    uint64_t* outPixelFormatModifier) const override;
+    status_t getUsage(buffer_handle_t bufferHandle, uint64_t* outUsage) const override;
+    status_t getAllocationSize(buffer_handle_t bufferHandle,
+                               uint64_t* outAllocationSize) const override;
+    status_t getProtectedContent(buffer_handle_t bufferHandle,
+                                 uint64_t* outProtectedContent) const override;
+    status_t getCompression(buffer_handle_t bufferHandle,
+                            aidl::android::hardware::graphics::common::ExtendableType*
+                                    outCompression) const override;
+    status_t getCompression(buffer_handle_t bufferHandle,
+                            ui::Compression* outCompression) const override;
+    status_t getInterlaced(buffer_handle_t bufferHandle,
+                           aidl::android::hardware::graphics::common::ExtendableType* outInterlaced)
+            const override;
+    status_t getInterlaced(buffer_handle_t bufferHandle,
+                           ui::Interlaced* outInterlaced) const override;
+    status_t getChromaSiting(buffer_handle_t bufferHandle,
+                             aidl::android::hardware::graphics::common::ExtendableType*
+                                     outChromaSiting) const override;
+    status_t getChromaSiting(buffer_handle_t bufferHandle,
+                             ui::ChromaSiting* outChromaSiting) const override;
+    status_t getPlaneLayouts(buffer_handle_t bufferHandle,
+                             std::vector<ui::PlaneLayout>* outPlaneLayouts) const override;
+    status_t getDataspace(buffer_handle_t bufferHandle, ui::Dataspace* outDataspace) const override;
+    status_t getBlendMode(buffer_handle_t bufferHandle, ui::BlendMode* outBlendMode) const override;
+
+    status_t getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, PixelFormat format,
+                                         uint32_t layerCount, uint64_t usage,
+                                         uint32_t* outPixelFormatFourCC) const override;
+    status_t getDefaultPixelFormatModifier(uint32_t width, uint32_t height, PixelFormat format,
+                                           uint32_t layerCount, uint64_t usage,
+                                           uint64_t* outPixelFormatModifier) const override;
+    status_t getDefaultAllocationSize(uint32_t width, uint32_t height, PixelFormat format,
+                                      uint32_t layerCount, uint64_t usage,
+                                      uint64_t* outAllocationSize) const override;
+    status_t getDefaultProtectedContent(uint32_t width, uint32_t height, PixelFormat format,
+                                        uint32_t layerCount, uint64_t usage,
+                                        uint64_t* outProtectedContent) const override;
+    status_t getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format,
+                                   uint32_t layerCount, uint64_t usage,
+                                   aidl::android::hardware::graphics::common::ExtendableType*
+                                           outCompression) const override;
+    status_t getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format,
+                                   uint32_t layerCount, uint64_t usage,
+                                   ui::Compression* outCompression) const override;
+    status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
+                                  uint32_t layerCount, uint64_t usage,
+                                  aidl::android::hardware::graphics::common::ExtendableType*
+                                          outInterlaced) const override;
+    status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
+                                  uint32_t layerCount, uint64_t usage,
+                                  ui::Interlaced* outInterlaced) const override;
+    status_t getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format,
+                                    uint32_t layerCount, uint64_t usage,
+                                    aidl::android::hardware::graphics::common::ExtendableType*
+                                            outChromaSiting) const override;
+    status_t getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format,
+                                    uint32_t layerCount, uint64_t usage,
+                                    ui::ChromaSiting* outChromaSiting) const override;
+    status_t getDefaultPlaneLayouts(uint32_t width, uint32_t height, PixelFormat format,
+                                    uint32_t layerCount, uint64_t usage,
+                                    std::vector<ui::PlaneLayout>* outPlaneLayouts) const override;
+
+    std::vector<android::hardware::graphics::mapper::V4_0::IMapper::MetadataTypeDescription>
+    listSupportedMetadataTypes() const;
 
 private:
+    friend class GraphicBufferAllocator;
+
     // Determines whether the passed info is compatible with the mapper.
     status_t validateBufferDescriptorInfo(
             hardware::graphics::mapper::V4_0::IMapper::BufferDescriptorInfo* descriptorInfo) const;
 
+    template <class T>
+    using DecodeFunction = status_t (*)(const hardware::hidl_vec<uint8_t>& input, T* output);
+
+    template <class T>
+    status_t get(
+            buffer_handle_t bufferHandle,
+            const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType& metadataType,
+            DecodeFunction<T> decodeFunction, T* outMetadata) const;
+
+    template <class T>
+    status_t getDefault(
+            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+            uint64_t usage,
+            const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType& metadataType,
+            DecodeFunction<T> decodeFunction, T* outMetadata) const;
+
+    template <class T>
+    status_t metadataDumpHelper(
+            const android::hardware::graphics::mapper::V4_0::IMapper::BufferDump& bufferDump,
+            aidl::android::hardware::graphics::common::StandardMetadataType metadataType,
+            DecodeFunction<T> decodeFunction, T* outT) const;
+    status_t bufferDumpHelper(
+            const android::hardware::graphics::mapper::V4_0::IMapper::BufferDump& bufferDump,
+            std::ostringstream* outDump, uint64_t* outAllocationSize, bool less) const;
+
     sp<hardware::graphics::mapper::V4_0::IMapper> mMapper;
 };
 
@@ -79,11 +186,12 @@
 
     bool isLoaded() const override;
 
-    std::string dumpDebugInfo() const override;
+    std::string dumpDebugInfo(bool less = true) const override;
 
-    status_t allocate(uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-                      uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
-                      buffer_handle_t* outBufferHandles) const override;
+    status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
+                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
+                      uint32_t* outStride, buffer_handle_t* outBufferHandles,
+                      bool importBuffers = true) const override;
 
 private:
     const Gralloc4Mapper& mMapper;
diff --git a/libs/ui/include/ui/GraphicBufferAllocator.h b/libs/ui/include/ui/GraphicBufferAllocator.h
index 324d9e1..3ed988c 100644
--- a/libs/ui/include/ui/GraphicBufferAllocator.h
+++ b/libs/ui/include/ui/GraphicBufferAllocator.h
@@ -1,19 +1,19 @@
 /*
-**
-** Copyright 2009, 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.
-*/
+ *
+ * Copyright 2009, 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 ANDROID_BUFFER_ALLOCATOR_H
 #define ANDROID_BUFFER_ALLOCATOR_H
@@ -42,6 +42,29 @@
 public:
     static inline GraphicBufferAllocator& get() { return getInstance(); }
 
+    /**
+     * Allocates and imports a gralloc buffer.
+     *
+     * The handle must be freed with GraphicBufferAllocator::free() when no longer needed.
+     */
+    status_t allocate(uint32_t w, uint32_t h, PixelFormat format, uint32_t layerCount,
+                      uint64_t usage, buffer_handle_t* handle, uint32_t* stride,
+                      std::string requestorName);
+
+    /**
+     * Allocates and does NOT import a gralloc buffer. Buffers cannot be used until they have
+     * been imported. This function is for advanced use cases only.
+     *
+     * The raw native handle must be freed by calling native_handle_close() followed by
+     * native_handle_delete().
+     */
+    status_t allocateRawHandle(uint32_t w, uint32_t h, PixelFormat format, uint32_t layerCount,
+                               uint64_t usage, buffer_handle_t* handle, uint32_t* stride,
+                               std::string requestorName);
+
+    /**
+     * DEPRECATED: GraphicBufferAllocator does not use the graphicBufferId.
+     */
     status_t allocate(uint32_t w, uint32_t h, PixelFormat format,
             uint32_t layerCount, uint64_t usage,
             buffer_handle_t* handle, uint32_t* stride, uint64_t graphicBufferId,
@@ -49,10 +72,10 @@
 
     status_t free(buffer_handle_t handle);
 
-    size_t getTotalSize() const;
+    uint64_t getTotalSize() const;
 
-    void dump(std::string& res) const;
-    static void dumpToSystemLog();
+    void dump(std::string& res, bool less = true) const;
+    static void dumpToSystemLog(bool less = true);
 
 protected:
     struct alloc_rec_t {
@@ -66,6 +89,10 @@
         std::string requestorName;
     };
 
+    status_t allocateHelper(uint32_t w, uint32_t h, PixelFormat format, uint32_t layerCount,
+                            uint64_t usage, buffer_handle_t* handle, uint32_t* stride,
+                            std::string requestorName, bool importBuffer);
+
     static Mutex sLock;
     static KeyedVector<buffer_handle_t, alloc_rec_t> sAllocList;
 
diff --git a/libs/ui/include/ui/GraphicBufferMapper.h b/libs/ui/include/ui/GraphicBufferMapper.h
index c401a48..77c00ae 100644
--- a/libs/ui/include/ui/GraphicBufferMapper.h
+++ b/libs/ui/include/ui/GraphicBufferMapper.h
@@ -22,10 +22,11 @@
 
 #include <memory>
 
+#include <ui/GraphicTypes.h>
 #include <ui/PixelFormat.h>
+#include <ui/Rect.h>
 #include <utils/Singleton.h>
 
-
 // Needed by code that still uses the GRALLOC_USAGE_* constants.
 // when/if we get rid of gralloc, we should provide aliases or fix call sites.
 #include <hardware/gralloc.h>
@@ -36,7 +37,6 @@
 // ---------------------------------------------------------------------------
 
 class GrallocMapper;
-class Rect;
 
 class GraphicBufferMapper : public Singleton<GraphicBufferMapper>
 {
@@ -49,6 +49,9 @@
     static void preloadHal();
     static inline GraphicBufferMapper& get() { return getInstance(); }
 
+    void dumpBuffer(buffer_handle_t bufferHandle, std::string& result, bool less = true) const;
+    static void dumpBufferToSystemLog(buffer_handle_t bufferHandle, bool less = true);
+
     // The imported outHandle must be freed with freeBuffer when no longer
     // needed. rawHandle is owned by the caller.
     status_t importBuffer(buffer_handle_t rawHandle,
@@ -86,6 +89,82 @@
     status_t isSupported(uint32_t width, uint32_t height, android::PixelFormat format,
                          uint32_t layerCount, uint64_t usage, bool* outSupported);
 
+    /**
+     * Gets the gralloc metadata associated with the buffer.
+     *
+     * These functions are supported by gralloc 4.0+.
+     */
+    status_t getBufferId(buffer_handle_t bufferHandle, uint64_t* outBufferId);
+    status_t getName(buffer_handle_t bufferHandle, std::string* outName);
+    status_t getWidth(buffer_handle_t bufferHandle, uint64_t* outWidth);
+    status_t getHeight(buffer_handle_t bufferHandle, uint64_t* outHeight);
+    status_t getLayerCount(buffer_handle_t bufferHandle, uint64_t* outLayerCount);
+    status_t getPixelFormatRequested(buffer_handle_t bufferHandle,
+                                     ui::PixelFormat* outPixelFormatRequested);
+    status_t getPixelFormatFourCC(buffer_handle_t bufferHandle, uint32_t* outPixelFormatFourCC);
+    status_t getPixelFormatModifier(buffer_handle_t bufferHandle, uint64_t* outPixelFormatModifier);
+    status_t getUsage(buffer_handle_t bufferHandle, uint64_t* outUsage);
+    status_t getAllocationSize(buffer_handle_t bufferHandle, uint64_t* outAllocationSize);
+    status_t getProtectedContent(buffer_handle_t bufferHandle, uint64_t* outProtectedContent);
+    status_t getCompression(
+            buffer_handle_t bufferHandle,
+            aidl::android::hardware::graphics::common::ExtendableType* outCompression);
+    status_t getCompression(buffer_handle_t bufferHandle, ui::Compression* outCompression);
+    status_t getInterlaced(
+            buffer_handle_t bufferHandle,
+            aidl::android::hardware::graphics::common::ExtendableType* outInterlaced);
+    status_t getInterlaced(buffer_handle_t bufferHandle, ui::Interlaced* outInterlaced);
+    status_t getChromaSiting(
+            buffer_handle_t bufferHandle,
+            aidl::android::hardware::graphics::common::ExtendableType* outChromaSiting);
+    status_t getChromaSiting(buffer_handle_t bufferHandle, ui::ChromaSiting* outChromaSiting);
+    status_t getPlaneLayouts(buffer_handle_t bufferHandle,
+                             std::vector<ui::PlaneLayout>* outPlaneLayouts);
+    status_t getDataspace(buffer_handle_t bufferHandle, ui::Dataspace* outDataspace);
+    status_t getBlendMode(buffer_handle_t bufferHandle, ui::BlendMode* outBlendMode);
+
+    /**
+     * Gets the default metadata for a gralloc buffer allocated with the given parameters.
+     *
+     * These functions are supported by gralloc 4.0+.
+     */
+    status_t getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, PixelFormat format,
+                                         uint32_t layerCount, uint64_t usage,
+                                         uint32_t* outPixelFormatFourCC);
+    status_t getDefaultPixelFormatModifier(uint32_t width, uint32_t height, PixelFormat format,
+                                           uint32_t layerCount, uint64_t usage,
+                                           uint64_t* outPixelFormatModifier);
+    status_t getDefaultAllocationSize(uint32_t width, uint32_t height, PixelFormat format,
+                                      uint32_t layerCount, uint64_t usage,
+                                      uint64_t* outAllocationSize);
+    status_t getDefaultProtectedContent(uint32_t width, uint32_t height, PixelFormat format,
+                                        uint32_t layerCount, uint64_t usage,
+                                        uint64_t* outProtectedContent);
+    status_t getDefaultCompression(
+            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+            uint64_t usage,
+            aidl::android::hardware::graphics::common::ExtendableType* outCompression);
+    status_t getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format,
+                                   uint32_t layerCount, uint64_t usage,
+                                   ui::Compression* outCompression);
+    status_t getDefaultInterlaced(
+            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+            uint64_t usage,
+            aidl::android::hardware::graphics::common::ExtendableType* outInterlaced);
+    status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
+                                  uint32_t layerCount, uint64_t usage,
+                                  ui::Interlaced* outInterlaced);
+    status_t getDefaultChromaSiting(
+            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+            uint64_t usage,
+            aidl::android::hardware::graphics::common::ExtendableType* outChromaSiting);
+    status_t getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format,
+                                    uint32_t layerCount, uint64_t usage,
+                                    ui::ChromaSiting* outChromaSiting);
+    status_t getDefaultPlaneLayouts(uint32_t width, uint32_t height, PixelFormat format,
+                                    uint32_t layerCount, uint64_t usage,
+                                    std::vector<ui::PlaneLayout>* outPlaneLayouts);
+
     const GrallocMapper& getGrallocMapper() const {
         return reinterpret_cast<const GrallocMapper&>(*mMapper);
     }
diff --git a/libs/ui/include/ui/GraphicTypes.h b/libs/ui/include/ui/GraphicTypes.h
index d7411ea..ad5ee80 100644
--- a/libs/ui/include/ui/GraphicTypes.h
+++ b/libs/ui/include/ui/GraphicTypes.h
@@ -16,21 +16,47 @@
 
 #pragma once
 
+#include <aidl/android/hardware/graphics/common/BlendMode.h>
+#include <aidl/android/hardware/graphics/common/ChromaSiting.h>
+#include <aidl/android/hardware/graphics/common/Compression.h>
+#include <aidl/android/hardware/graphics/common/Interlaced.h>
+#include <aidl/android/hardware/graphics/common/PlaneLayout.h>
 #include <android/hardware/graphics/common/1.1/types.h>
 #include <android/hardware/graphics/common/1.2/types.h>
 #include <system/graphics.h>
 
 namespace android {
 
-// android::ui::* in this header file will alias different types as
-// the HIDL interface is updated.
+/**
+ * android::ui::* in this header file will alias different types as
+ * the HIDL and stable AIDL interfaces are updated.
+ */
 namespace ui {
 
+/**
+ * The following HIDL types should be moved to their stable AIDL
+ * equivalents when composer moves to stable AIDL.
+ */
 using android::hardware::graphics::common::V1_1::RenderIntent;
 using android::hardware::graphics::common::V1_2::ColorMode;
 using android::hardware::graphics::common::V1_2::Dataspace;
 using android::hardware::graphics::common::V1_2::Hdr;
 using android::hardware::graphics::common::V1_2::PixelFormat;
 
+/**
+ * Stable AIDL types
+ */
+using aidl::android::hardware::graphics::common::BlendMode;
+using aidl::android::hardware::graphics::common::PlaneLayout;
+
+/**
+ * The following stable AIDL types below have standard platform definitions
+ * that can be extended by vendors. The extensions are not defined here
+ * because they cannot be understood by the framework.
+ */
+using ChromaSiting = aidl::android::hardware::graphics::common::ChromaSiting;
+using Compression = aidl::android::hardware::graphics::common::Compression;
+using Interlaced = aidl::android::hardware::graphics::common::Interlaced;
+
 }  // namespace ui
 }  // namespace android
diff --git a/libs/ui/include/ui/Rect.h b/libs/ui/include/ui/Rect.h
index 1768805..2f2229e 100644
--- a/libs/ui/include/ui/Rect.h
+++ b/libs/ui/include/ui/Rect.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_UI_RECT
 #define ANDROID_UI_RECT
 
+#include <ostream>
+
 #include <utils/Flattenable.h>
 #include <utils/Log.h>
 #include <utils/TypeHelpers.h>
@@ -214,6 +216,12 @@
     }
 };
 
+// Defining PrintTo helps with Google Tests.
+static inline void PrintTo(const Rect& rect, ::std::ostream* os) {
+    *os << "Rect(" << rect.left << ", " << rect.top << ", " << rect.right << ", " << rect.bottom
+        << ")";
+}
+
 ANDROID_BASIC_TYPES_TRAITS(Rect)
 
 }; // namespace android
diff --git a/libs/ui/include/ui/Region.h b/libs/ui/include/ui/Region.h
index 79642ae..2db3b10 100644
--- a/libs/ui/include/ui/Region.h
+++ b/libs/ui/include/ui/Region.h
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 #include <sys/types.h>
+#include <ostream>
 
 #include <utils/Vector.h>
 
@@ -119,6 +120,8 @@
     // returns true if the regions share the same underlying storage
     bool isTriviallyEqual(const Region& region) const;
 
+    // returns true if the regions consist of the same rectangle sequence
+    bool hasSameRects(const Region& region) const;
 
     /* various ways to access the rectangle list */
 
@@ -213,6 +216,21 @@
 Region& Region::operator += (const Point& pt) {
     return translateSelf(pt.x, pt.y);
 }
+
+// Defining PrintTo helps with Google Tests.
+static inline void PrintTo(const Region& region, ::std::ostream* os) {
+    Region::const_iterator head = region.begin();
+    Region::const_iterator const tail = region.end();
+    bool first = true;
+    while (head != tail) {
+        *os << (first ? "Region(" : ", ");
+        PrintTo(*head, os);
+        head++;
+        first = false;
+    }
+    *os << ")";
+}
+
 // ---------------------------------------------------------------------------
 }; // namespace android
 
diff --git a/libs/ui/include/ui/Transform.h b/libs/ui/include/ui/Transform.h
index f29a370..de07684 100644
--- a/libs/ui/include/ui/Transform.h
+++ b/libs/ui/include/ui/Transform.h
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 #include <sys/types.h>
+#include <ostream>
 #include <string>
 
 #include <hardware/hardware.h>
@@ -38,7 +39,7 @@
 public:
     Transform();
     Transform(const Transform&  other);
-    explicit Transform(uint32_t orientation);
+    explicit Transform(uint32_t orientation, int w = 0, int h = 0);
     ~Transform();
 
     enum orientation_flags {
@@ -63,6 +64,7 @@
     bool        preserveRects() const;
     uint32_t    getType() const;
     uint32_t    getOrientation() const;
+    bool operator==(const Transform& other) const;
 
     const vec3& operator [] (size_t i) const;  // returns column i
     float   tx() const;
@@ -115,6 +117,12 @@
     mutable uint32_t    mType;
 };
 
+static inline void PrintTo(const Transform& t, ::std::ostream* os) {
+    std::string out;
+    t.dump(out, "ui::Transform");
+    *os << out;
+}
+
 }  // namespace ui
 }  // namespace android
 
diff --git a/include/private/ui/RegionHelper.h b/libs/ui/include_private/ui/RegionHelper.h
similarity index 70%
rename from include/private/ui/RegionHelper.h
rename to libs/ui/include_private/ui/RegionHelper.h
index 0ec3e94..92cfba8 100644
--- a/include/private/ui/RegionHelper.h
+++ b/libs/ui/include_private/ui/RegionHelper.h
@@ -17,18 +17,17 @@
 #ifndef ANDROID_UI_PRIVATE_REGION_HELPER_H
 #define ANDROID_UI_PRIVATE_REGION_HELPER_H
 
-#include <limits>
 #include <stdint.h>
 #include <sys/types.h>
+#include <limits>
 
 #include <limits>
 
 namespace android {
 // ----------------------------------------------------------------------------
 
-template<typename RECT>
-class region_operator
-{
+template <typename RECT>
+class region_operator {
 public:
     typedef typename RECT::value_type TYPE;
     static const TYPE max_value = std::numeric_limits<TYPE>::max();
@@ -40,39 +39,32 @@
      *    their corresponding value with the above formulae and use
      *    it when instantiating a region_operator.
      */
-    static const uint32_t LHS = 0x5;  // 0b101
-    static const uint32_t RHS = 0x6;  // 0b110
-    enum {
-        op_nand = LHS & ~RHS,
-        op_and  = LHS &  RHS,
-        op_or   = LHS |  RHS,
-        op_xor  = LHS ^  RHS
-    };
+    static const uint32_t LHS = 0x5; // 0b101
+    static const uint32_t RHS = 0x6; // 0b110
+    enum { op_nand = LHS & ~RHS, op_and = LHS & RHS, op_or = LHS | RHS, op_xor = LHS ^ RHS };
 
     struct region {
         RECT const* rects;
         size_t count;
         TYPE dx;
         TYPE dy;
-        inline region(const region& rhs) 
-            : rects(rhs.rects), count(rhs.count), dx(rhs.dx), dy(rhs.dy) { }
-        inline region(RECT const* _r, size_t _c)
-            : rects(_r), count(_c), dx(), dy() { }
+        inline region(const region& rhs)
+              : rects(rhs.rects), count(rhs.count), dx(rhs.dx), dy(rhs.dy) {}
+        inline region(RECT const* _r, size_t _c) : rects(_r), count(_c), dx(), dy() {}
         inline region(RECT const* _r, size_t _c, TYPE _dx, TYPE _dy)
-            : rects(_r), count(_c), dx(_dx), dy(_dy) { }
+              : rects(_r), count(_c), dx(_dx), dy(_dy) {}
     };
 
     class region_rasterizer {
         friend class region_operator;
         virtual void operator()(const RECT& rect) = 0;
+
     public:
-        virtual ~region_rasterizer() { }
+        virtual ~region_rasterizer() {}
     };
-    
+
     inline region_operator(uint32_t op, const region& lhs, const region& rhs)
-        : op_mask(op), spanner(lhs, rhs) 
-    {
-    }
+          : op_mask(op), spanner(lhs, rhs) {}
 
     void operator()(region_rasterizer& rasterizer) {
         RECT current(Rect::EMPTY_RECT);
@@ -83,31 +75,26 @@
             do {
                 int inner_inside = spannerInner.next(current.left, current.right);
                 if ((op_mask >> inner_inside) & 1) {
-                    if (current.left < current.right && 
-                            current.top < current.bottom) {
+                    if (current.left < current.right && current.top < current.bottom) {
                         rasterizer(current);
                     }
                 }
-            } while(!spannerInner.isDone());
-        } while(!spanner.isDone());
+            } while (!spannerInner.isDone());
+        } while (!spanner.isDone());
     }
 
-private:    
+private:
     uint32_t op_mask;
 
-    class SpannerBase
-    {
+    class SpannerBase {
     public:
         SpannerBase()
-            : lhs_head(max_value), lhs_tail(max_value),
-              rhs_head(max_value), rhs_tail(max_value) {
-        }
+              : lhs_head(max_value),
+                lhs_tail(max_value),
+                rhs_head(max_value),
+                rhs_tail(max_value) {}
 
-        enum {
-            lhs_before_rhs   = 0,
-            lhs_after_rhs    = 1,
-            lhs_coincide_rhs = 2
-        };
+        enum { lhs_before_rhs = 0, lhs_after_rhs = 1, lhs_coincide_rhs = 2 };
 
     protected:
         TYPE lhs_head;
@@ -115,9 +102,7 @@
         TYPE rhs_head;
         TYPE rhs_tail;
 
-        inline int next(TYPE& head, TYPE& tail,
-                bool& more_lhs, bool& more_rhs) 
-        {
+        inline int next(TYPE& head, TYPE& tail, bool& more_lhs, bool& more_rhs) {
             int inside;
             more_lhs = false;
             more_rhs = false;
@@ -157,32 +142,26 @@
         }
     };
 
-    class Spanner : protected SpannerBase 
-    {
+    class Spanner : protected SpannerBase {
         friend class region_operator;
         region lhs;
         region rhs;
 
     public:
-        inline Spanner(const region& _lhs, const region& _rhs)
-        : lhs(_lhs), rhs(_rhs)
-        {
+        inline Spanner(const region& _lhs, const region& _rhs) : lhs(_lhs), rhs(_rhs) {
             if (lhs.count) {
-                SpannerBase::lhs_head = lhs.rects->top      + lhs.dy;
-                SpannerBase::lhs_tail = lhs.rects->bottom   + lhs.dy;
+                SpannerBase::lhs_head = lhs.rects->top + lhs.dy;
+                SpannerBase::lhs_tail = lhs.rects->bottom + lhs.dy;
             }
             if (rhs.count) {
-                SpannerBase::rhs_head = rhs.rects->top      + rhs.dy;
-                SpannerBase::rhs_tail = rhs.rects->bottom   + rhs.dy;
+                SpannerBase::rhs_head = rhs.rects->top + rhs.dy;
+                SpannerBase::rhs_tail = rhs.rects->bottom + rhs.dy;
             }
         }
 
-        inline bool isDone() const {
-            return !rhs.count && !lhs.count;
-        }
+        inline bool isDone() const { return !rhs.count && !lhs.count; }
 
-        inline int next(TYPE& top, TYPE& bottom) 
-        {
+        inline int next(TYPE& top, TYPE& bottom) {
             bool more_lhs = false;
             bool more_rhs = false;
             int inside = SpannerBase::next(top, bottom, more_lhs, more_rhs);
@@ -196,22 +175,21 @@
         }
 
     private:
-        static inline 
-        void advance(region& reg, TYPE& aTop, TYPE& aBottom) {
+        static inline void advance(region& reg, TYPE& aTop, TYPE& aBottom) {
             // got to next span
             size_t count = reg.count;
-            RECT const * rects = reg.rects;
-            RECT const * const end = rects + count;
+            RECT const* rects = reg.rects;
+            RECT const* const end = rects + count;
             const int top = rects->top;
             while (rects != end && rects->top == top) {
                 rects++;
                 count--;
             }
             if (rects != end) {
-                aTop    = rects->top    + reg.dy;
+                aTop = rects->top + reg.dy;
                 aBottom = rects->bottom + reg.dy;
             } else {
-                aTop    = max_value;
+                aTop = max_value;
                 aBottom = max_value;
             }
             reg.rects = rects;
@@ -219,21 +197,17 @@
         }
     };
 
-    class SpannerInner : protected SpannerBase 
-    {
+    class SpannerInner : protected SpannerBase {
         region lhs;
         region rhs;
-        
+
     public:
-        inline SpannerInner(const region& _lhs, const region& _rhs)
-            : lhs(_lhs), rhs(_rhs)
-        {
-        }
+        inline SpannerInner(const region& _lhs, const region& _rhs) : lhs(_lhs), rhs(_rhs) {}
 
         inline void prepare(int inside) {
             if (inside == SpannerBase::lhs_before_rhs) {
                 if (lhs.count) {
-                    SpannerBase::lhs_head = lhs.rects->left  + lhs.dx;
+                    SpannerBase::lhs_head = lhs.rects->left + lhs.dx;
                     SpannerBase::lhs_tail = lhs.rects->right + lhs.dx;
                 }
                 SpannerBase::rhs_head = max_value;
@@ -242,28 +216,26 @@
                 SpannerBase::lhs_head = max_value;
                 SpannerBase::lhs_tail = max_value;
                 if (rhs.count) {
-                    SpannerBase::rhs_head = rhs.rects->left  + rhs.dx;
+                    SpannerBase::rhs_head = rhs.rects->left + rhs.dx;
                     SpannerBase::rhs_tail = rhs.rects->right + rhs.dx;
                 }
             } else {
                 if (lhs.count) {
-                    SpannerBase::lhs_head = lhs.rects->left  + lhs.dx;
+                    SpannerBase::lhs_head = lhs.rects->left + lhs.dx;
                     SpannerBase::lhs_tail = lhs.rects->right + lhs.dx;
                 }
                 if (rhs.count) {
-                    SpannerBase::rhs_head = rhs.rects->left  + rhs.dx;
+                    SpannerBase::rhs_head = rhs.rects->left + rhs.dx;
                     SpannerBase::rhs_tail = rhs.rects->right + rhs.dx;
                 }
             }
         }
 
         inline bool isDone() const {
-            return SpannerBase::lhs_head == max_value && 
-                   SpannerBase::rhs_head == max_value;
+            return SpannerBase::lhs_head == max_value && SpannerBase::rhs_head == max_value;
         }
 
-        inline int next(TYPE& left, TYPE& right) 
-        {
+        inline int next(TYPE& left, TYPE& right) {
             bool more_lhs = false;
             bool more_rhs = false;
             int inside = SpannerBase::next(left, right, more_lhs, more_rhs);
@@ -277,17 +249,16 @@
         }
 
     private:
-        static inline 
-        void advance(region& reg, TYPE& left, TYPE& right) {
+        static inline void advance(region& reg, TYPE& left, TYPE& right) {
             if (reg.rects && reg.count) {
                 const int cur_span_top = reg.rects->top;
                 reg.rects++;
                 reg.count--;
                 if (!reg.count || reg.rects->top != cur_span_top) {
-                    left  = max_value;
+                    left = max_value;
                     right = max_value;
                 } else {
-                    left  = reg.rects->left  + reg.dx;
+                    left = reg.rects->left + reg.dx;
                     right = reg.rects->right + reg.dx;
                 }
             }
@@ -298,6 +269,6 @@
 };
 
 // ----------------------------------------------------------------------------
-};
+}; // namespace android
 
 #endif /* ANDROID_UI_PRIVATE_REGION_HELPER_H */
diff --git a/libs/ui/include_vndk/ui/Gralloc2.h b/libs/ui/include_vndk/ui/Gralloc2.h
deleted file mode 120000
index 66098c4..0000000
--- a/libs/ui/include_vndk/ui/Gralloc2.h
+++ /dev/null
@@ -1 +0,0 @@
-../../include/ui/Gralloc2.h
\ No newline at end of file
diff --git a/libs/ui/tests/Android.bp b/libs/ui/tests/Android.bp
index d2ad242..ff55bc2 100644
--- a/libs/ui/tests/Android.bp
+++ b/libs/ui/tests/Android.bp
@@ -38,8 +38,9 @@
         "libgmock",
     ],
     shared_libs: [
+        "libhidlbase",
         "liblog",
-	"libui",
+        "libui",
     ],
     srcs: [
         "GraphicBufferAllocator_test.cpp",
diff --git a/libs/ui/tests/GraphicBufferAllocator_test.cpp b/libs/ui/tests/GraphicBufferAllocator_test.cpp
index efca083..f4c0afa 100644
--- a/libs/ui/tests/GraphicBufferAllocator_test.cpp
+++ b/libs/ui/tests/GraphicBufferAllocator_test.cpp
@@ -51,7 +51,7 @@
         std::cout << "Setting expected stride to " << stride << std::endl;
         EXPECT_CALL(*(reinterpret_cast<const mock::MockGrallocAllocator*>(mAllocator.get())),
                     allocate)
-                .WillOnce(DoAll(SetArgPointee<6>(stride), Return(err)));
+                .WillOnce(DoAll(SetArgPointee<7>(stride), Return(err)));
     }
     std::unique_ptr<const GrallocAllocator>& getAllocator() { return mAllocator; }
 };
diff --git a/libs/ui/tests/mock/MockGrallocAllocator.h b/libs/ui/tests/mock/MockGrallocAllocator.h
index 22c80a4..d62e3e2 100644
--- a/libs/ui/tests/mock/MockGrallocAllocator.h
+++ b/libs/ui/tests/mock/MockGrallocAllocator.h
@@ -32,11 +32,11 @@
     ~MockGrallocAllocator() override;
 
     MOCK_METHOD(bool, isLoaded, (), (const, override));
-    MOCK_METHOD(std::string, dumpDebugInfo, (), (const, override));
+    MOCK_METHOD(std::string, dumpDebugInfo, (bool less), (const, override));
     MOCK_METHOD(status_t, allocate,
-                (uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
-                 uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
-                 buffer_handle_t* outBufferHandles),
+                (std::string requestorName, uint32_t width, uint32_t height, PixelFormat format,
+                 uint32_t layerCount, uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
+                 buffer_handle_t* outBufferHandles, bool less),
                 (const, override));
 };
 
diff --git a/libs/vr/libvrflinger/hardware_composer.cpp b/libs/vr/libvrflinger/hardware_composer.cpp
index 67607af..188ac6b 100644
--- a/libs/vr/libvrflinger/hardware_composer.cpp
+++ b/libs/vr/libvrflinger/hardware_composer.cpp
@@ -1201,6 +1201,20 @@
   return Void();
 }
 
+Return<void> HardwareComposer::ComposerCallback::onVsync_2_4(
+    Hwc2::Display /*display*/, int64_t /*timestamp*/,
+    Hwc2::VsyncPeriodNanos /*vsyncPeriodNanos*/) {
+  LOG_ALWAYS_FATAL("Unexpected onVsync_2_4 callback");
+  return Void();
+}
+
+Return<void> HardwareComposer::ComposerCallback::onVsyncPeriodTimingChanged(
+    Hwc2::Display /*display*/,
+    const Hwc2::VsyncPeriodChangeTimeline& /*updatedTimeline*/) {
+  LOG_ALWAYS_FATAL("Unexpected onVsyncPeriodTimingChanged callback");
+  return Void();
+}
+
 void HardwareComposer::ComposerCallback::SetVsyncService(
     const sp<VsyncService>& vsync_service) {
   std::lock_guard<std::mutex> lock(mutex_);
diff --git a/libs/vr/libvrflinger/hardware_composer.h b/libs/vr/libvrflinger/hardware_composer.h
index 989ce35..8698814 100644
--- a/libs/vr/libvrflinger/hardware_composer.h
+++ b/libs/vr/libvrflinger/hardware_composer.h
@@ -375,6 +375,12 @@
     hardware::Return<void> onRefresh(Hwc2::Display display) override;
     hardware::Return<void> onVsync(Hwc2::Display display,
                                    int64_t timestamp) override;
+    hardware::Return<void> onVsync_2_4(
+        Hwc2::Display display, int64_t timestamp,
+        Hwc2::VsyncPeriodNanos vsyncPeriodNanos) override;
+    hardware::Return<void> onVsyncPeriodTimingChanged(
+        Hwc2::Display display,
+        const Hwc2::VsyncPeriodChangeTimeline& updatedTimeline) override;
 
     bool GotFirstHotplug() { return got_first_hotplug_; }
     void SetVsyncService(const sp<VsyncService>& vsync_service);
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp
index 48a68af..e255b9d 100644
--- a/opengl/libs/Android.bp
+++ b/opengl/libs/Android.bp
@@ -161,6 +161,10 @@
     ],
     ldflags: ["-Wl,--exclude-libs=ALL,--Bsymbolic-functions"],
     export_include_dirs: ["EGL/include"],
+    stubs: {
+        symbol_file: "libEGL.map.txt",
+        versions: ["29"],
+    },
 }
 
 cc_test {
diff --git a/opengl/tools/glgen/gen b/opengl/tools/glgen/gen
index da9bb49..41fcf1b 100755
--- a/opengl/tools/glgen/gen
+++ b/opengl/tools/glgen/gen
@@ -91,8 +91,8 @@
 rm src/*.class
 
 # Add UnsupportedAppUsage.java to known sources.
-mkdir -p out/android/annotation
-cp ../../../../base/core/java/android/annotation/UnsupportedAppUsage.java out/android/annotation
+mkdir -p out/android/compat/annotation
+cp ../../../../../tools/platform-compat/annotation/src/java/android/compat/annotation/UnsupportedAppUsage.java out/android/compat/annotation
 
 pushd out > /dev/null
 mkdir classes
@@ -114,7 +114,7 @@
                     android/opengl/GLES31.java \
                     android/opengl/GLES31Ext.java \
                     android/opengl/GLES32.java \
-                    android/annotation/UnsupportedAppUsage.java
+                    android/compat/annotation/UnsupportedAppUsage.java
 popd > /dev/null
 JAVA_RESULT=$?
 if [ $JAVA_RESULT -ne 0 ]; then
diff --git a/opengl/tools/glgen/stubs/egl/EGL14Header.java-if b/opengl/tools/glgen/stubs/egl/EGL14Header.java-if
index 12728f5..9932556 100644
--- a/opengl/tools/glgen/stubs/egl/EGL14Header.java-if
+++ b/opengl/tools/glgen/stubs/egl/EGL14Header.java-if
@@ -18,7 +18,7 @@
 
 package android.opengl;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.SurfaceTexture;
 import android.view.Surface;
 import android.view.SurfaceView;
diff --git a/opengl/tools/glgen/stubs/gles11/GLES20Header.java-if b/opengl/tools/glgen/stubs/gles11/GLES20Header.java-if
index c2711aa..7db1101 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES20Header.java-if
+++ b/opengl/tools/glgen/stubs/gles11/GLES20Header.java-if
@@ -19,7 +19,7 @@
 
 package android.opengl;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /** OpenGL ES 2.0
  */
diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp
index 7b8e0f8..baba64f 100644
--- a/services/gpuservice/Android.bp
+++ b/services/gpuservice/Android.bp
@@ -1,20 +1,6 @@
-filegroup {
-    name: "gpuservice_sources",
-    srcs: [
-        "GpuService.cpp",
-        "gpustats/GpuStats.cpp"
-    ],
-}
-
-filegroup {
-    name: "gpuservice_binary_sources",
-    srcs: ["main_gpuservice.cpp"],
-}
-
 cc_defaults {
     name: "gpuservice_defaults",
     cflags: [
-        "-DLOG_TAG=\"GpuService\"",
         "-Wall",
         "-Werror",
         "-Wformat",
@@ -22,12 +8,13 @@
         "-Wunused",
         "-Wunreachable-code",
     ],
-    srcs: [
-        ":gpuservice_sources",
-    ],
-    include_dirs: [
-        "frameworks/native/vulkan/vkjson",
-        "frameworks/native/vulkan/include",
+}
+
+cc_defaults {
+    name: "libgpuservice_defaults",
+    defaults: ["gpuservice_defaults"],
+    cflags: [
+        "-DLOG_TAG=\"GpuService\"",
     ],
     shared_libs: [
         "libbase",
@@ -36,17 +23,22 @@
         "libgraphicsenv",
         "liblog",
         "libutils",
-        "libvulkan",
+        "libvkjson",
     ],
     static_libs: [
         "libserviceutils",
-        "libvkjson",
+    ],
+    export_static_lib_headers: [
+        "libserviceutils",
+    ],
+    export_shared_lib_headers: [
+        "libgraphicsenv",
     ],
 }
 
 cc_defaults {
-    name: "gpuservice_production_defaults",
-    defaults: ["gpuservice_defaults"],
+    name: "libgpuservice_production_defaults",
+    defaults: ["libgpuservice_defaults"],
     cflags: [
         "-fvisibility=hidden",
         "-fwhole-program-vtables", // requires ThinLTO
@@ -56,8 +48,24 @@
     },
 }
 
+filegroup {
+    name: "libgpuservice_sources",
+    srcs: [
+        "GpuService.cpp",
+        "gpustats/GpuStats.cpp"
+    ],
+}
+
+cc_library_shared {
+    name: "libgpuservice",
+    defaults: ["libgpuservice_production_defaults"],
+    srcs: [
+        ":libgpuservice_sources",
+    ],
+}
+
 cc_defaults {
-    name: "gpuservice_binary",
+    name: "libgpuservice_binary",
     defaults: ["gpuservice_defaults"],
     shared_libs: [
         "libbinder",
@@ -68,9 +76,17 @@
     ldflags: ["-Wl,--export-dynamic"],
 }
 
+filegroup {
+    name: "gpuservice_binary_sources",
+    srcs: ["main_gpuservice.cpp"],
+}
+
 cc_binary {
     name: "gpuservice",
-    defaults: ["gpuservice_binary"],
+    defaults: ["libgpuservice_binary"],
     init_rc: ["gpuservice.rc"],
     srcs: [":gpuservice_binary_sources"],
+    shared_libs: [
+        "libgpuservice",
+    ],
 }
diff --git a/services/inputflinger/InputClassifier.cpp b/services/inputflinger/InputClassifier.cpp
index 7c061c5..ae9a348 100644
--- a/services/inputflinger/InputClassifier.cpp
+++ b/services/inputflinger/InputClassifier.cpp
@@ -82,7 +82,7 @@
 // Check if the "deep touch" feature is on.
 static bool deepPressEnabled() {
     std::string flag_value = server_configurable_flags::GetServerConfigurableFlag(
-            INPUT_NATIVE_BOOT, DEEP_PRESS_ENABLED, "false");
+            INPUT_NATIVE_BOOT, DEEP_PRESS_ENABLED, "true");
     std::transform(flag_value.begin(), flag_value.end(), flag_value.begin(), ::tolower);
     if (flag_value == "1" || flag_value == "true") {
         ALOGI("Deep press feature enabled.");
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index e7640dd..c7c61cf 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -20,7 +20,6 @@
 
 #include "InputManager.h"
 #include "InputDispatcherFactory.h"
-#include "InputDispatcherThread.h"
 #include "InputReaderFactory.h"
 
 #include <binder/IPCThreadState.h>
@@ -38,30 +37,24 @@
     mDispatcher = createInputDispatcher(dispatcherPolicy);
     mClassifier = new InputClassifier(mDispatcher);
     mReader = createInputReader(readerPolicy, mClassifier);
-    initialize();
 }
 
 InputManager::~InputManager() {
     stop();
 }
 
-void InputManager::initialize() {
-    mReaderThread = new InputReaderThread(mReader);
-    mDispatcherThread = new InputDispatcherThread(mDispatcher);
-}
-
 status_t InputManager::start() {
-    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
+    status_t result = mDispatcher->start();
     if (result) {
         ALOGE("Could not start InputDispatcher thread due to error %d.", result);
         return result;
     }
 
-    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
+    result = mReader->start();
     if (result) {
-        ALOGE("Could not start InputReader thread due to error %d.", result);
+        ALOGE("Could not start InputReader due to error %d.", result);
 
-        mDispatcherThread->requestExit();
+        mDispatcher->stop();
         return result;
     }
 
@@ -69,17 +62,21 @@
 }
 
 status_t InputManager::stop() {
-    status_t result = mReaderThread->requestExitAndWait();
+    status_t status = OK;
+
+    status_t result = mReader->stop();
     if (result) {
-        ALOGW("Could not stop InputReader thread due to error %d.", result);
+        ALOGW("Could not stop InputReader due to error %d.", result);
+        status = result;
     }
 
-    result = mDispatcherThread->requestExitAndWait();
+    result = mDispatcher->stop();
     if (result) {
         ALOGW("Could not stop InputDispatcher thread due to error %d.", result);
+        status = result;
     }
 
-    return OK;
+    return status;
 }
 
 sp<InputReaderInterface> InputManager::getReader() {
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index 40f66d8..586097f 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -43,15 +43,15 @@
 /*
  * The input manager is the core of the system event processing.
  *
- * The input manager uses two threads.
+ * The input manager has two components.
  *
- * 1. The InputReaderThread (called "InputReader") reads and preprocesses raw input events,
- *    applies policy, and posts messages to a queue managed by the DispatcherThread.
- * 2. The InputDispatcherThread (called "InputDispatcher") thread waits for new events on the
+ * 1. The InputReader class starts a thread that reads and preprocesses raw input events, applies
+ *    policy, and posts messages to a queue managed by the InputDispatcherThread.
+ * 2. The InputDispatcher class starts a thread that waits for new events on the
  *    queue and asynchronously dispatches them to applications.
  *
- * By design, the InputReaderThread class and InputDispatcherThread class do not share any
- * internal state.  Moreover, all communication is done one way from the InputReaderThread
+ * By design, the InputReader class and InputDispatcher class do not share any
+ * internal state.  Moreover, all communication is done one way from the InputReader
  * into the InputDispatcherThread and never the reverse.  Both classes may interact with the
  * InputDispatchPolicy, however.
  *
@@ -65,10 +65,10 @@
     virtual ~InputManagerInterface() { }
 
 public:
-    /* Starts the input manager threads. */
+    /* Starts the input threads. */
     virtual status_t start() = 0;
 
-    /* Stops the input manager threads and waits for them to exit. */
+    /* Stops the input threads and waits for them to exit. */
     virtual status_t stop() = 0;
 
     /* Gets the input reader. */
@@ -102,14 +102,10 @@
 
 private:
     sp<InputReaderInterface> mReader;
-    sp<InputReaderThread> mReaderThread;
 
     sp<InputClassifierInterface> mClassifier;
 
     sp<InputDispatcherInterface> mDispatcher;
-    sp<InputDispatcherThread> mDispatcherThread;
-
-    void initialize();
 };
 
 } // namespace android
diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp
index 0422d83..2d6f2c1 100644
--- a/services/inputflinger/InputReaderBase.cpp
+++ b/services/inputflinger/InputReaderBase.cpp
@@ -33,20 +33,6 @@
 
 namespace android {
 
-// --- InputReaderThread ---
-
-InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
-        Thread(/*canCallJava*/ true), mReader(reader) {
-}
-
-InputReaderThread::~InputReaderThread() {
-}
-
-bool InputReaderThread::threadLoop() {
-    mReader->loopOnce();
-    return true;
-}
-
 // --- InputReaderConfiguration ---
 
 std::string InputReaderConfiguration::changesToString(uint32_t changes) {
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index 9185e00..a556aad 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -21,7 +21,6 @@
         "InjectionState.cpp",
         "InputDispatcher.cpp",
         "InputDispatcherFactory.cpp",
-        "InputDispatcherThread.cpp",
         "InputState.cpp",
         "InputTarget.cpp",
         "Monitor.cpp",
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 930c7c7..e925f5b 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -60,7 +60,7 @@
 
 // --- EventEntry ---
 
-EventEntry::EventEntry(uint32_t sequenceNum, int32_t type, nsecs_t eventTime, uint32_t policyFlags)
+EventEntry::EventEntry(uint32_t sequenceNum, Type type, nsecs_t eventTime, uint32_t policyFlags)
       : sequenceNum(sequenceNum),
         refCount(1),
         type(type),
@@ -92,7 +92,7 @@
 // --- ConfigurationChangedEntry ---
 
 ConfigurationChangedEntry::ConfigurationChangedEntry(uint32_t sequenceNum, nsecs_t eventTime)
-      : EventEntry(sequenceNum, TYPE_CONFIGURATION_CHANGED, eventTime, 0) {}
+      : EventEntry(sequenceNum, Type::CONFIGURATION_CHANGED, eventTime, 0) {}
 
 ConfigurationChangedEntry::~ConfigurationChangedEntry() {}
 
@@ -103,7 +103,7 @@
 // --- DeviceResetEntry ---
 
 DeviceResetEntry::DeviceResetEntry(uint32_t sequenceNum, nsecs_t eventTime, int32_t deviceId)
-      : EventEntry(sequenceNum, TYPE_DEVICE_RESET, eventTime, 0), deviceId(deviceId) {}
+      : EventEntry(sequenceNum, Type::DEVICE_RESET, eventTime, 0), deviceId(deviceId) {}
 
 DeviceResetEntry::~DeviceResetEntry() {}
 
@@ -117,7 +117,7 @@
                    int32_t displayId, uint32_t policyFlags, int32_t action, int32_t flags,
                    int32_t keyCode, int32_t scanCode, int32_t metaState, int32_t repeatCount,
                    nsecs_t downTime)
-      : EventEntry(sequenceNum, TYPE_KEY, eventTime, policyFlags),
+      : EventEntry(sequenceNum, Type::KEY, eventTime, policyFlags),
         deviceId(deviceId),
         source(source),
         displayId(displayId),
@@ -165,7 +165,7 @@
                          float xCursorPosition, float yCursorPosition, nsecs_t downTime,
                          uint32_t pointerCount, const PointerProperties* pointerProperties,
                          const PointerCoords* pointerCoords, float xOffset, float yOffset)
-      : EventEntry(sequenceNum, TYPE_MOTION, eventTime, policyFlags),
+      : EventEntry(sequenceNum, Type::MOTION, eventTime, policyFlags),
         eventTime(eventTime),
         deviceId(deviceId),
         source(source),
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 28c2799..9dcaadc 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -33,11 +33,24 @@
 constexpr uint32_t SYNTHESIZED_EVENT_SEQUENCE_NUM = 0;
 
 struct EventEntry {
-    enum { TYPE_CONFIGURATION_CHANGED, TYPE_DEVICE_RESET, TYPE_KEY, TYPE_MOTION };
+    enum class Type { CONFIGURATION_CHANGED, DEVICE_RESET, KEY, MOTION };
+
+    static const char* typeToString(Type type) {
+        switch (type) {
+            case Type::CONFIGURATION_CHANGED:
+                return "CONFIGURATION_CHANGED";
+            case Type::DEVICE_RESET:
+                return "DEVICE_RESET";
+            case Type::KEY:
+                return "KEY";
+            case Type::MOTION:
+                return "MOTION";
+        }
+    }
 
     uint32_t sequenceNum;
     mutable int32_t refCount;
-    int32_t type;
+    Type type;
     nsecs_t eventTime;
     uint32_t policyFlags;
     InjectionState* injectionState;
@@ -66,7 +79,7 @@
     virtual void appendDescription(std::string& msg) const = 0;
 
 protected:
-    EventEntry(uint32_t sequenceNum, int32_t type, nsecs_t eventTime, uint32_t policyFlags);
+    EventEntry(uint32_t sequenceNum, Type type, nsecs_t eventTime, uint32_t policyFlags);
     virtual ~EventEntry();
     void releaseInjectionState();
 };
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 58a5b3c..5a49b5e 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -240,6 +240,24 @@
     return removed;
 }
 
+// --- InputDispatcherThread ---
+
+class InputDispatcher::InputDispatcherThread : public Thread {
+public:
+    explicit InputDispatcherThread(InputDispatcher* dispatcher)
+          : Thread(/* canCallJava */ true), mDispatcher(dispatcher) {}
+
+    ~InputDispatcherThread() {}
+
+private:
+    InputDispatcher* mDispatcher;
+
+    virtual bool threadLoop() override {
+        mDispatcher->dispatchOnce();
+        return true;
+    }
+};
+
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy)
@@ -252,6 +270,10 @@
         mDispatchEnabled(false),
         mDispatchFrozen(false),
         mInputFilterEnabled(false),
+        // mInTouchMode will be initialized by the WindowManager to the default device config.
+        // To avoid leaking stack in case that call never comes, and for tests,
+        // initialize it here anyways.
+        mInTouchMode(true),
         mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
         mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
     mLooper = new Looper(false);
@@ -260,6 +282,8 @@
     mKeyRepeatState.lastKeyEntry = nullptr;
 
     policy->getDispatcherConfiguration(&mConfig);
+
+    mThread = new InputDispatcherThread(this);
 }
 
 InputDispatcher::~InputDispatcher() {
@@ -277,6 +301,28 @@
     }
 }
 
+status_t InputDispatcher::start() {
+    if (mThread->isRunning()) {
+        return ALREADY_EXISTS;
+    }
+    return mThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
+}
+
+status_t InputDispatcher::stop() {
+    if (!mThread->isRunning()) {
+        return OK;
+    }
+    if (gettid() == mThread->getTid()) {
+        ALOGE("InputDispatcher can only be stopped from outside of the InputDispatcherThread!");
+        return INVALID_OPERATION;
+    }
+    // Directly calling requestExitAndWait() causes the thread to not exit
+    // if mLooper is waiting for a long timeout.
+    mThread->requestExit();
+    mLooper->wake();
+    return mThread->requestExitAndWait();
+}
+
 void InputDispatcher::dispatchOnce() {
     nsecs_t nextWakeupTime = LONG_LONG_MAX;
     { // acquire lock
@@ -294,6 +340,12 @@
         if (runCommandsLockedInterruptible()) {
             nextWakeupTime = LONG_LONG_MIN;
         }
+
+        // We are about to enter an infinitely long sleep, because we have no commands or
+        // pending or queued events
+        if (nextWakeupTime == LONG_LONG_MAX) {
+            mDispatcherEnteredIdle.notify_all();
+        }
     } // release lock
 
     // Wait for callback or timeout or wake.  (make sure we round up, not down)
@@ -386,7 +438,7 @@
     }
 
     switch (mPendingEvent->type) {
-        case EventEntry::TYPE_CONFIGURATION_CHANGED: {
+        case EventEntry::Type::CONFIGURATION_CHANGED: {
             ConfigurationChangedEntry* typedEntry =
                     static_cast<ConfigurationChangedEntry*>(mPendingEvent);
             done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
@@ -394,14 +446,14 @@
             break;
         }
 
-        case EventEntry::TYPE_DEVICE_RESET: {
+        case EventEntry::Type::DEVICE_RESET: {
             DeviceResetEntry* typedEntry = static_cast<DeviceResetEntry*>(mPendingEvent);
             done = dispatchDeviceResetLocked(currentTime, typedEntry);
             dropReason = DropReason::NOT_DROPPED; // device resets are never dropped
             break;
         }
 
-        case EventEntry::TYPE_KEY: {
+        case EventEntry::Type::KEY: {
             KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
             if (isAppSwitchDue) {
                 if (isAppSwitchKeyEvent(*typedEntry)) {
@@ -421,7 +473,7 @@
             break;
         }
 
-        case EventEntry::TYPE_MOTION: {
+        case EventEntry::Type::MOTION: {
             MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
             if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
                 dropReason = DropReason::APP_SWITCH;
@@ -435,10 +487,6 @@
             done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
             break;
         }
-
-        default:
-            ALOG_ASSERT(false);
-            break;
     }
 
     if (done) {
@@ -458,7 +506,7 @@
     traceInboundQueueLengthLocked();
 
     switch (entry->type) {
-        case EventEntry::TYPE_KEY: {
+        case EventEntry::Type::KEY: {
             // Optimize app switch latency.
             // If the application takes too long to catch up then we drop all events preceding
             // the app switch key.
@@ -480,7 +528,7 @@
             break;
         }
 
-        case EventEntry::TYPE_MOTION: {
+        case EventEntry::Type::MOTION: {
             // Optimize case where the current application is unresponsive and the user
             // decides to touch a window in a different application.
             // If the application takes too long to catch up then we drop all events preceding
@@ -508,6 +556,11 @@
             }
             break;
         }
+        case EventEntry::Type::CONFIGURATION_CHANGED:
+        case EventEntry::Type::DEVICE_RESET: {
+            // nothing to do
+            break;
+        }
     }
 
     return needWake;
@@ -627,12 +680,12 @@
     }
 
     switch (entry.type) {
-        case EventEntry::TYPE_KEY: {
+        case EventEntry::Type::KEY: {
             CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
             synthesizeCancelationEventsForAllConnectionsLocked(options);
             break;
         }
-        case EventEntry::TYPE_MOTION: {
+        case EventEntry::Type::MOTION: {
             const MotionEntry& motionEntry = static_cast<const MotionEntry&>(entry);
             if (motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) {
                 CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, reason);
@@ -643,6 +696,11 @@
             }
             break;
         }
+        case EventEntry::Type::CONFIGURATION_CHANGED:
+        case EventEntry::Type::DEVICE_RESET: {
+            LOG_ALWAYS_FATAL("Should not drop %s events", EventEntry::typeToString(entry.type));
+            break;
+        }
     }
 }
 
@@ -1174,18 +1232,19 @@
 int32_t InputDispatcher::getTargetDisplayId(const EventEntry& entry) {
     int32_t displayId;
     switch (entry.type) {
-        case EventEntry::TYPE_KEY: {
+        case EventEntry::Type::KEY: {
             const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
             displayId = keyEntry.displayId;
             break;
         }
-        case EventEntry::TYPE_MOTION: {
+        case EventEntry::Type::MOTION: {
             const MotionEntry& motionEntry = static_cast<const MotionEntry&>(entry);
             displayId = motionEntry.displayId;
             break;
         }
-        default: {
-            ALOGE("Unsupported event type '%" PRId32 "' for target display.", entry.type);
+        case EventEntry::Type::CONFIGURATION_CHANGED:
+        case EventEntry::Type::DEVICE_RESET: {
+            ALOGE("%s events do not have a target display", EventEntry::typeToString(entry.type));
             return ADISPLAY_ID_NONE;
         }
     }
@@ -1849,7 +1908,7 @@
     }
 
     // Ensure that the dispatch queues aren't too far backed up for this event.
-    if (eventEntry.type == EventEntry::TYPE_KEY) {
+    if (eventEntry.type == EventEntry::Type::KEY) {
         // If the event is a key event, then we must wait for all previous events to
         // complete before delivering it because previous events may have the
         // side-effect of transferring focus to a different window and we want to
@@ -1937,7 +1996,7 @@
 
     int32_t eventType = USER_ACTIVITY_EVENT_OTHER;
     switch (eventEntry.type) {
-        case EventEntry::TYPE_MOTION: {
+        case EventEntry::Type::MOTION: {
             const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
             if (motionEntry.action == AMOTION_EVENT_ACTION_CANCEL) {
                 return;
@@ -1948,7 +2007,7 @@
             }
             break;
         }
-        case EventEntry::TYPE_KEY: {
+        case EventEntry::Type::KEY: {
             const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
             if (keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) {
                 return;
@@ -1956,6 +2015,12 @@
             eventType = USER_ACTIVITY_EVENT_BUTTON;
             break;
         }
+        case EventEntry::Type::CONFIGURATION_CHANGED:
+        case EventEntry::Type::DEVICE_RESET: {
+            LOG_ALWAYS_FATAL("%s events are not user activity",
+                             EventEntry::typeToString(eventEntry.type));
+            break;
+        }
     }
 
     std::unique_ptr<CommandEntry> commandEntry =
@@ -1996,7 +2061,7 @@
 
     // Split a motion event if needed.
     if (inputTarget->flags & InputTarget::FLAG_SPLIT) {
-        ALOG_ASSERT(eventEntry->type == EventEntry::TYPE_MOTION);
+        ALOG_ASSERT(eventEntry->type == EventEntry::Type::MOTION);
 
         const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry);
         if (inputTarget->pointerIds.count() != originalMotionEntry.pointerCount) {
@@ -2080,7 +2145,7 @@
 
     // Apply target flags and update the connection's input state.
     switch (eventEntry->type) {
-        case EventEntry::TYPE_KEY: {
+        case EventEntry::Type::KEY: {
             const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*eventEntry);
             dispatchEntry->resolvedAction = keyEntry.action;
             dispatchEntry->resolvedFlags = keyEntry.flags;
@@ -2097,7 +2162,7 @@
             break;
         }
 
-        case EventEntry::TYPE_MOTION: {
+        case EventEntry::Type::MOTION: {
             const MotionEntry& motionEntry = static_cast<const MotionEntry&>(*eventEntry);
             if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
                 dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
@@ -2147,6 +2212,12 @@
 
             break;
         }
+        case EventEntry::Type::CONFIGURATION_CHANGED:
+        case EventEntry::Type::DEVICE_RESET: {
+            LOG_ALWAYS_FATAL("%s events should not go to apps",
+                             EventEntry::typeToString(eventEntry->type));
+            break;
+        }
     }
 
     // Remember that we are waiting for this dispatch to complete.
@@ -2206,7 +2277,7 @@
         status_t status;
         EventEntry* eventEntry = dispatchEntry->eventEntry;
         switch (eventEntry->type) {
-            case EventEntry::TYPE_KEY: {
+            case EventEntry::Type::KEY: {
                 KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
 
                 // Publish the key event.
@@ -2221,7 +2292,7 @@
                 break;
             }
 
-            case EventEntry::TYPE_MOTION: {
+            case EventEntry::Type::MOTION: {
                 MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
 
                 PointerCoords scaledCoords[MAX_POINTERS];
@@ -2276,10 +2347,12 @@
                 reportTouchEventForStatistics(*motionEntry);
                 break;
             }
-
-            default:
-                ALOG_ASSERT(false);
+            case EventEntry::Type::CONFIGURATION_CHANGED:
+            case EventEntry::Type::DEVICE_RESET: {
+                LOG_ALWAYS_FATAL("Should never start dispatch cycles for %s events",
+                                 EventEntry::typeToString(eventEntry->type));
                 return;
+            }
         }
 
         // Check the result.
@@ -2502,15 +2575,23 @@
         for (size_t i = 0; i < cancelationEvents.size(); i++) {
             EventEntry* cancelationEventEntry = cancelationEvents[i];
             switch (cancelationEventEntry->type) {
-                case EventEntry::TYPE_KEY:
+                case EventEntry::Type::KEY: {
                     logOutboundKeyDetails("cancel - ",
                                           static_cast<const KeyEntry&>(*cancelationEventEntry));
                     break;
-                case EventEntry::TYPE_MOTION:
+                }
+                case EventEntry::Type::MOTION: {
                     logOutboundMotionDetails("cancel - ",
                                              static_cast<const MotionEntry&>(
                                                      *cancelationEventEntry));
                     break;
+                }
+                case EventEntry::Type::CONFIGURATION_CHANGED:
+                case EventEntry::Type::DEVICE_RESET: {
+                    LOG_ALWAYS_FATAL("%s event should not be found inside Connections's queue",
+                                     EventEntry::typeToString(cancelationEventEntry->type));
+                    break;
+                }
             }
 
             InputTarget target;
@@ -3003,7 +3084,7 @@
         }
 
         default:
-            ALOGW("Cannot inject event of type %d", event->getType());
+            ALOGW("Cannot inject %s events", inputEventTypeToString(event->getType()));
             return INPUT_EVENT_INJECTION_FAILED;
     }
 
@@ -3509,6 +3590,11 @@
     mLooper->wake();
 }
 
+void InputDispatcher::setInTouchMode(bool inTouchMode) {
+    std::scoped_lock lock(mLock);
+    mInTouchMode = inTouchMode;
+}
+
 bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp<IBinder>& toToken) {
     if (fromToken == toToken) {
         if (DEBUG_FOCUS) {
@@ -4029,7 +4115,9 @@
         options.displayId = displayId;
         for (const TouchedWindow& window : state.windows) {
             sp<InputChannel> channel = getInputChannelLocked(window.windowHandle->getToken());
-            synthesizeCancelationEventsForInputChannelLocked(channel, options);
+            if (channel != nullptr) {
+                synthesizeCancelationEventsForInputChannelLocked(channel, options);
+            }
         }
         // Then clear the current touch state so we stop dispatching to them as well.
         state.filterNonMonitors();
@@ -4237,11 +4325,11 @@
     }
 
     bool restartEvent;
-    if (dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) {
+    if (dispatchEntry->eventEntry->type == EventEntry::Type::KEY) {
         KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
         restartEvent =
                 afterKeyEventLockedInterruptible(connection, dispatchEntry, keyEntry, handled);
-    } else if (dispatchEntry->eventEntry->type == EventEntry::TYPE_MOTION) {
+    } else if (dispatchEntry->eventEntry->type == EventEntry::Type::MOTION) {
         MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry);
         restartEvent = afterMotionEventLockedInterruptible(connection, dispatchEntry, motionEntry,
                                                            handled);
@@ -4544,4 +4632,22 @@
     mDispatcherIsAlive.wait(_l);
 }
 
+/**
+ * Wake up the dispatcher and wait until it processes all events and commands.
+ * The notification of mDispatcherEnteredIdle is guaranteed to happen after wake(), so
+ * this method can be safely called from any thread, as long as you've ensured that
+ * the work you are interested in completing has already been queued.
+ */
+bool InputDispatcher::waitForIdle() {
+    /**
+     * Timeout should represent the longest possible time that a device might spend processing
+     * events and commands.
+     */
+    constexpr std::chrono::duration TIMEOUT = 100ms;
+    std::unique_lock lock(mLock);
+    mLooper->wake();
+    std::cv_status result = mDispatcherEnteredIdle.wait_for(lock, TIMEOUT);
+    return result == std::cv_status::no_timeout;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index d21b0a1..96a09e3 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -81,8 +81,9 @@
 
     virtual void dump(std::string& dump) override;
     virtual void monitor() override;
-
-    virtual void dispatchOnce() override;
+    virtual bool waitForIdle() override;
+    virtual status_t start() override;
+    virtual status_t stop() override;
 
     virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override;
     virtual void notifyKey(const NotifyKeyArgs* args) override;
@@ -102,6 +103,7 @@
     virtual void setFocusedDisplay(int32_t displayId) override;
     virtual void setInputDispatchMode(bool enabled, bool frozen) override;
     virtual void setInputFilterEnabled(bool enabled) override;
+    virtual void setInTouchMode(bool inTouchMode) override;
 
     virtual bool transferTouchFocus(const sp<IBinder>& fromToken,
                                     const sp<IBinder>& toToken) override;
@@ -122,12 +124,16 @@
         STALE,
     };
 
+    class InputDispatcherThread;
+    sp<InputDispatcherThread> mThread;
+
     sp<InputDispatcherPolicyInterface> mPolicy;
     android::InputDispatcherConfiguration mConfig;
 
     std::mutex mLock;
 
     std::condition_variable mDispatcherIsAlive;
+    std::condition_variable mDispatcherEnteredIdle;
 
     sp<Looper> mLooper;
 
@@ -138,6 +144,11 @@
 
     DropReason mLastDropReason GUARDED_BY(mLock);
 
+    // With each iteration, InputDispatcher nominally processes one queued event,
+    // a timeout, or a response from an input consumer.
+    // This method should only be called on the input dispatcher's own thread.
+    void dispatchOnce();
+
     void dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) REQUIRES(mLock);
 
     // Enqueues an inbound event.  Returns true if mLooper->wake() should be called.
@@ -247,6 +258,7 @@
     bool mDispatchEnabled GUARDED_BY(mLock);
     bool mDispatchFrozen GUARDED_BY(mLock);
     bool mInputFilterEnabled GUARDED_BY(mLock);
+    bool mInTouchMode GUARDED_BY(mLock);
 
     std::unordered_map<int32_t, std::vector<sp<InputWindowHandle>>> mWindowHandlesByDisplay
             GUARDED_BY(mLock);
diff --git a/services/inputflinger/dispatcher/InputDispatcherThread.cpp b/services/inputflinger/dispatcher/InputDispatcherThread.cpp
deleted file mode 100644
index 18b1b8c..0000000
--- a/services/inputflinger/dispatcher/InputDispatcherThread.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "InputDispatcherThread.h"
-
-#include "InputDispatcherInterface.h"
-
-namespace android {
-
-InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher)
-      : Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {}
-
-InputDispatcherThread::~InputDispatcherThread() {}
-
-bool InputDispatcherThread::threadLoop() {
-    mDispatcher->dispatchOnce();
-    return true;
-}
-
-} // namespace android
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 5acf92b..2e9bca2 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -93,21 +93,22 @@
     sp<InputChannel> inputChannel;
 
     // Flags for the input target.
-    int32_t flags;
+    int32_t flags = 0;
 
     // The x and y offset to add to a MotionEvent as it is delivered.
     // (ignored for KeyEvents)
-    float xOffset, yOffset;
+    float xOffset = 0.0f;
+    float yOffset = 0.0f;
 
     // Scaling factor to apply to MotionEvent as it is delivered.
     // (ignored for KeyEvents)
-    float globalScaleFactor;
+    float globalScaleFactor = 1.0f;
     float windowXScale = 1.0f;
     float windowYScale = 1.0f;
 
     // The subset of pointer ids to include in motion events dispatched to this input target
     // if FLAG_SPLIT is set.
-    BitSet32 pointerIds;
+    BitSet32 pointerIds{};
 };
 
 std::string dispatchModeToString(int32_t dispatchMode);
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index ce7366f..3424f4c 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -63,12 +63,23 @@
     /* Called by the heatbeat to ensures that the dispatcher has not deadlocked. */
     virtual void monitor() = 0;
 
-    /* Runs a single iteration of the dispatch loop.
-     * Nominally processes one queued event, a timeout, or a response from an input consumer.
-     *
-     * This method should only be called on the input dispatcher thread.
+    /**
+     * Wait until dispatcher is idle. That means, there are no further events to be processed,
+     * and all of the policy callbacks have been completed.
+     * Return true if the dispatcher is idle.
+     * Return false if the timeout waiting for the dispatcher to become idle has expired.
      */
-    virtual void dispatchOnce() = 0;
+    virtual bool waitForIdle() = 0;
+
+    /* Make the dispatcher start processing events.
+     *
+     * The dispatcher will start consuming events from the InputListenerInterface
+     * in the order that they were received.
+     */
+    virtual status_t start() = 0;
+
+    /* Makes the dispatcher stop processing events. */
+    virtual status_t stop() = 0;
 
     /* Injects an input event and optionally waits for sync.
      * The synchronization mode determines whether the method blocks while waiting for
@@ -116,6 +127,14 @@
      */
     virtual void setInputFilterEnabled(bool enabled) = 0;
 
+    /**
+     * Set the touch mode state.
+     * Touch mode is a global state that apps may enter / exit based on specific
+     * user interactions with input devices.
+     * If true, the device is in touch mode.
+     */
+    virtual void setInTouchMode(bool inTouchMode) = 0;
+
     /* Transfers touch focus from one window to another window.
      *
      * Returns true on success.  False if the window did not actually have touch focus.
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherThread.h b/services/inputflinger/dispatcher/include/InputDispatcherThread.h
deleted file mode 100644
index 2604959..0000000
--- a/services/inputflinger/dispatcher/include/InputDispatcherThread.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERTHREAD_H
-#define _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERTHREAD_H
-
-#include <utils/RefBase.h>
-#include <utils/threads.h>
-
-namespace android {
-
-class InputDispatcherInterface;
-
-/* Enqueues and dispatches input events, endlessly. */
-class InputDispatcherThread : public Thread {
-public:
-    explicit InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher);
-    ~InputDispatcherThread();
-
-private:
-    virtual bool threadLoop();
-
-    sp<InputDispatcherInterface> mDispatcher;
-};
-
-} // namespace android
-
-#endif // _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERTHREAD_H
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 5d576b9..56c0a73 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -19,12 +19,12 @@
 
 #include "PointerControllerInterface.h"
 
+#include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <input/InputDevice.h>
-#include <input/DisplayViewport.h>
 #include <input/VelocityControl.h>
 #include <input/VelocityTracker.h>
-#include <utils/Thread.h>
+#include <utils/Errors.h>
 #include <utils/RefBase.h>
 
 #include <stddef.h>
@@ -44,7 +44,16 @@
 
 namespace android {
 
-/* Processes raw input events and sends cooked event data to an input listener. */
+// --- InputReaderInterface ---
+
+/* The interface for the InputReader shared library.
+ *
+ * Manages one or more threads that process raw input events and sends cooked event data to an
+ * input listener.
+ *
+ * The implementation must guarantee thread safety for this interface. However, since the input
+ * listener is NOT thread safe, all calls to the listener must happen from the same thread.
+ */
 class InputReaderInterface : public virtual RefBase {
 protected:
     InputReaderInterface() { }
@@ -56,18 +65,17 @@
      * This method may be called on any thread (usually by the input manager). */
     virtual void dump(std::string& dump) = 0;
 
-    /* Called by the heatbeat to ensures that the reader has not deadlocked. */
+    /* Called by the heartbeat to ensures that the reader has not deadlocked. */
     virtual void monitor() = 0;
 
     /* Returns true if the input device is enabled. */
     virtual bool isInputDeviceEnabled(int32_t deviceId) = 0;
 
-    /* Runs a single iteration of the processing loop.
-     * Nominally reads and processes one incoming message from the EventHub.
-     *
-     * This method should be called on the input reader thread.
-     */
-    virtual void loopOnce() = 0;
+    /* Makes the reader start processing events from the kernel. */
+    virtual status_t start() = 0;
+
+    /* Makes the reader stop processing any more events. */
+    virtual status_t stop() = 0;
 
     /* Gets information about all input devices.
      *
@@ -104,17 +112,7 @@
     virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0;
 };
 
-/* Reads raw events from the event hub and processes them, endlessly. */
-class InputReaderThread : public Thread {
-public:
-    explicit InputReaderThread(const sp<InputReaderInterface>& reader);
-    virtual ~InputReaderThread();
-
-private:
-    sp<InputReaderInterface> mReader;
-
-    virtual bool threadLoop();
-};
+// --- InputReaderConfiguration ---
 
 /*
  * Input reader configuration.
@@ -285,6 +283,8 @@
     std::vector<DisplayViewport> mDisplays;
 };
 
+// --- TouchAffineTransformation ---
+
 struct TouchAffineTransformation {
     float x_scale;
     float x_ymix;
@@ -307,6 +307,8 @@
     void applyTo(float& x, float& y) const;
 };
 
+// --- InputReaderPolicyInterface ---
+
 /*
  * Input reader policy interface.
  *
@@ -316,8 +318,8 @@
  * The actual implementation is partially supported by callbacks into the DVM
  * via JNI.  This interface is also mocked in the unit tests.
  *
- * These methods must NOT re-enter the input reader since they may be called while
- * holding the input reader lock.
+ * These methods will NOT re-enter the input reader interface, so they may be called from
+ * any method in the input reader interface.
  */
 class InputReaderPolicyInterface : public virtual RefBase {
 protected:
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index 3c16070..23c08b2 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -58,7 +58,6 @@
         "liblog",
         "libui",
         "libutils",
-        "libhardware_legacy",
     ],
 
     header_libs: [
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index c8da0ab..264d287 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -37,8 +37,6 @@
 
 #include "EventHub.h"
 
-#include <hardware_legacy/power.h>
-
 #include <android-base/stringprintf.h>
 #include <cutils/properties.h>
 #include <openssl/sha.h>
@@ -71,7 +69,6 @@
 
 static constexpr bool DEBUG = false;
 
-static const char* WAKE_LOCK_ID = "KeyEvents";
 static const char* DEVICE_PATH = "/dev/input";
 // v4l2 devices go directly into /dev
 static const char* VIDEO_DEVICE_PATH = "/dev";
@@ -296,7 +293,6 @@
         mPendingEventIndex(0),
         mPendingINotify(false) {
     ensureProcessCanBlockSuspend();
-    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
 
     mEpollFd = epoll_create1(EPOLL_CLOEXEC);
     LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
@@ -354,8 +350,6 @@
     ::close(mINotifyFd);
     ::close(mWakeReadPipeFd);
     ::close(mWakeWritePipeFd);
-
-    release_wake_lock(WAKE_LOCK_ID);
 }
 
 InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const {
@@ -1046,26 +1040,24 @@
             break;
         }
 
-        // Poll for events.  Mind the wake lock dance!
-        // We hold a wake lock at all times except during epoll_wait().  This works due to some
-        // subtle choreography.  When a device driver has pending (unread) events, it acquires
-        // a kernel wake lock.  However, once the last pending event has been read, the device
-        // driver will release the kernel wake lock.  To prevent the system from going to sleep
-        // when this happens, the EventHub holds onto its own user wake lock while the client
-        // is processing events.  Thus the system can only sleep if there are no events
-        // pending or currently being processed.
+        // Poll for events.
+        // When a device driver has pending (unread) events, it acquires
+        // a kernel wake lock.  Once the last pending event has been read, the device
+        // driver will release the kernel wake lock, but the epoll will hold the wakelock,
+        // since we are using EPOLLWAKEUP. The wakelock is released by the epoll when epoll_wait
+        // is called again for the same fd that produced the event.
+        // Thus the system can only sleep if there are no events pending or
+        // currently being processed.
         //
         // The timeout is advisory only.  If the device is asleep, it will not wake just to
         // service the timeout.
         mPendingEventIndex = 0;
 
-        mLock.unlock(); // release lock before poll, must be before release_wake_lock
-        release_wake_lock(WAKE_LOCK_ID);
+        mLock.unlock(); // release lock before poll
 
         int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
 
-        acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
-        mLock.lock(); // reacquire lock after poll, must be after acquire_wake_lock
+        mLock.lock(); // reacquire lock after poll
 
         if (pollResult == 0) {
             // Timed out.
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 1c5adc3..05f0db1 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -38,16 +38,38 @@
 #include <unistd.h>
 
 #include <log/log.h>
+#include <utils/Errors.h>
 
 #include <android-base/stringprintf.h>
 #include <input/Keyboard.h>
 #include <input/VirtualKeyMap.h>
-
+#include <utils/Thread.h>
 
 using android::base::StringPrintf;
 
 namespace android {
 
+// --- InputReader::InputReaderThread ---
+
+/* Thread that reads raw events from the event hub and processes them, endlessly. */
+class InputReader::InputReaderThread : public Thread {
+public:
+    explicit InputReaderThread(InputReader* reader)
+          : Thread(/* canCallJava */ true), mReader(reader) {}
+
+    ~InputReaderThread() {}
+
+private:
+    InputReader* mReader;
+
+    bool threadLoop() override {
+        mReader->loopOnce();
+        return true;
+    }
+};
+
+// --- InputReader ---
+
 InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
                          const sp<InputReaderPolicyInterface>& policy,
                          const sp<InputListenerInterface>& listener)
@@ -61,6 +83,7 @@
         mNextTimeout(LLONG_MAX),
         mConfigurationChangesToRefresh(0) {
     mQueuedListener = new QueuedInputListener(listener);
+    mThread = new InputReaderThread(this);
 
     { // acquire lock
         AutoMutex _l(mLock);
@@ -76,6 +99,28 @@
     }
 }
 
+status_t InputReader::start() {
+    if (mThread->isRunning()) {
+        return ALREADY_EXISTS;
+    }
+    return mThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
+}
+
+status_t InputReader::stop() {
+    if (!mThread->isRunning()) {
+        return OK;
+    }
+    if (gettid() == mThread->getTid()) {
+        ALOGE("InputReader can only be stopped from outside of the InputReaderThread!");
+        return INVALID_OPERATION;
+    }
+    // Directly calling requestExitAndWait() causes the thread to not exit
+    // if mEventHub is waiting for a long timeout.
+    mThread->requestExit();
+    mEventHub->wake();
+    return mThread->requestExitAndWait();
+}
+
 void InputReader::loopOnce() {
     int32_t oldGeneration;
     int32_t timeoutMillis;
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 557eb3b..5024906 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -38,12 +38,12 @@
  * that it sends to the input listener.  Some functions of the input reader, such as early
  * event filtering in low power states, are controlled by a separate policy object.
  *
- * The InputReader owns a collection of InputMappers.  Most of the work it does happens
- * on the input reader thread but the InputReader can receive queries from other system
+ * The InputReader owns a collection of InputMappers. InputReader starts its own thread, where
+ * most of the work happens, but the InputReader can receive queries from other system
  * components running on arbitrary threads.  To keep things manageable, the InputReader
  * uses a single Mutex to guard its state.  The Mutex may be held while calling into the
  * EventHub or the InputReaderPolicy but it is never held while calling into the
- * InputListener.
+ * InputListener. All calls to InputListener must happen from InputReader's thread.
  */
 class InputReader : public InputReaderInterface {
 public:
@@ -55,7 +55,8 @@
     virtual void dump(std::string& dump) override;
     virtual void monitor() override;
 
-    virtual void loopOnce() override;
+    virtual status_t start() override;
+    virtual status_t stop() override;
 
     virtual void getInputDevices(std::vector<InputDeviceInfo>& outInputDevices) override;
 
@@ -86,6 +87,10 @@
                                             const InputDeviceIdentifier& identifier,
                                             uint32_t classes);
 
+    // With each iteration of the loop, InputReader reads and processes one incoming message from
+    // the EventHub.
+    void loopOnce();
+
     class ContextImpl : public InputReaderContext {
         InputReader* mReader;
 
@@ -111,6 +116,9 @@
     friend class ContextImpl;
 
 private:
+    class InputReaderThread;
+    sp<InputReaderThread> mThread;
+
     Mutex mLock;
 
     Condition mReaderIsAliveCondition;
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index f51d4a0..348a7ad 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -183,6 +183,9 @@
 
     mParameters.handlesKeyRepeat = false;
     config.tryGetProperty(String8("keyboard.handlesKeyRepeat"), mParameters.handlesKeyRepeat);
+
+    mParameters.doNotWakeByDefault = false;
+    config.tryGetProperty(String8("keyboard.doNotWakeByDefault"), mParameters.doNotWakeByDefault);
 }
 
 void KeyboardInputMapper::dumpParameters(std::string& dump) {
@@ -331,10 +334,12 @@
 
     // Key down on external an keyboard should wake the device.
     // We don't do this for internal keyboards to prevent them from waking up in your pocket.
-    // For internal keyboards, the key layout file should specify the policy flags for
-    // each wake key individually.
+    // For internal keyboards and devices for which the default wake behavior is explicitly
+    // prevented (e.g. TV remotes), the key layout file should specify the policy flags for each
+    // wake key individually.
     // TODO: Use the input device configuration to control this behavior more finely.
-    if (down && getDevice()->isExternal() && !isMediaKey(keyCode)) {
+    if (down && getDevice()->isExternal() && !mParameters.doNotWakeByDefault &&
+        !isMediaKey(keyCode)) {
         policyFlags |= POLICY_FLAG_WAKE;
     }
 
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index de2a377..badbcb2 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -73,6 +73,7 @@
     struct Parameters {
         bool orientationAware;
         bool handlesKeyRepeat;
+        bool doNotWakeByDefault;
     } mParameters;
 
     void configureParameters();
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
index 4ff941f..16095b9 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
@@ -56,8 +56,8 @@
 void SwitchInputMapper::sync(nsecs_t when) {
     if (mUpdatedSwitchMask) {
         uint32_t updatedSwitchValues = mSwitchValues & mUpdatedSwitchMask;
-        NotifySwitchArgs args(mContext->getNextSequenceNum(), when, 0, updatedSwitchValues,
-                              mUpdatedSwitchMask);
+        NotifySwitchArgs args(mContext->getNextSequenceNum(), when, 0 /*policyFlags*/,
+                              updatedSwitchValues, mUpdatedSwitchMask);
         getListener()->notifySwitch(&args);
 
         mUpdatedSwitchMask = 0;
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 2a29e0d..0701f3b 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -16,9 +16,8 @@
 
 #include "../dispatcher/InputDispatcher.h"
 
-#include <InputDispatcherThread.h>
-
 #include <binder/Binder.h>
+#include <input/Input.h>
 
 #include <gtest/gtest.h>
 #include <linux/input.h>
@@ -50,49 +49,55 @@
 
 public:
     FakeInputDispatcherPolicy() {
-        mOnPointerDownToken.clear();
     }
 
     void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) {
-        ASSERT_NE(nullptr, mFilteredEvent) << "Expected filterInputEvent() to have been called.";
-        ASSERT_EQ(mFilteredEvent->getType(), AINPUT_EVENT_TYPE_KEY);
-
-        const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*mFilteredEvent);
-        ASSERT_EQ(keyEvent.getEventTime(), args.eventTime);
-        ASSERT_EQ(keyEvent.getAction(), args.action);
-        ASSERT_EQ(keyEvent.getDisplayId(), args.displayId);
-
-        reset();
+        assertFilterInputEventWasCalled(AINPUT_EVENT_TYPE_KEY, args.eventTime, args.action,
+                                        args.displayId);
     }
 
     void assertFilterInputEventWasCalled(const NotifyMotionArgs& args) {
-        ASSERT_NE(nullptr, mFilteredEvent) << "Expected filterInputEvent() to have been called.";
-        ASSERT_EQ(mFilteredEvent->getType(), AINPUT_EVENT_TYPE_MOTION);
-
-        const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*mFilteredEvent);
-        ASSERT_EQ(motionEvent.getEventTime(), args.eventTime);
-        ASSERT_EQ(motionEvent.getAction(), args.action);
-        ASSERT_EQ(motionEvent.getDisplayId(), args.displayId);
-
-        reset();
+        assertFilterInputEventWasCalled(AINPUT_EVENT_TYPE_MOTION, args.eventTime, args.action,
+                                        args.displayId);
     }
 
-    void assertFilterInputEventWasNotCalled() {
-        ASSERT_EQ(nullptr, mFilteredEvent)
-                << "Expected filterInputEvent() to not have been called.";
+    void assertFilterInputEventWasNotCalled() { ASSERT_EQ(nullptr, mFilteredEvent); }
+
+    void assertNotifyConfigurationChangedWasCalled(nsecs_t when) {
+        ASSERT_TRUE(mConfigurationChangedTime)
+                << "Timed out waiting for configuration changed call";
+        ASSERT_EQ(*mConfigurationChangedTime, when);
+        mConfigurationChangedTime = std::nullopt;
+    }
+
+    void assertNotifySwitchWasCalled(const NotifySwitchArgs& args) {
+        ASSERT_TRUE(mLastNotifySwitch);
+        // We do not check sequenceNum because it is not exposed to the policy
+        EXPECT_EQ(args.eventTime, mLastNotifySwitch->eventTime);
+        EXPECT_EQ(args.policyFlags, mLastNotifySwitch->policyFlags);
+        EXPECT_EQ(args.switchValues, mLastNotifySwitch->switchValues);
+        EXPECT_EQ(args.switchMask, mLastNotifySwitch->switchMask);
+        mLastNotifySwitch = std::nullopt;
     }
 
     void assertOnPointerDownEquals(const sp<IBinder>& touchedToken) {
-        ASSERT_EQ(mOnPointerDownToken, touchedToken)
-                << "Expected token from onPointerDownOutsideFocus was not matched";
-        reset();
+        ASSERT_EQ(touchedToken, mOnPointerDownToken);
+        mOnPointerDownToken.clear();
+    }
+
+    void assertOnPointerDownWasNotCalled() {
+        ASSERT_TRUE(mOnPointerDownToken == nullptr)
+                << "Expected onPointerDownOutsideFocus to not have been called";
     }
 
 private:
     std::unique_ptr<InputEvent> mFilteredEvent;
+    std::optional<nsecs_t> mConfigurationChangedTime;
     sp<IBinder> mOnPointerDownToken;
+    std::optional<NotifySwitchArgs> mLastNotifySwitch;
 
-    virtual void notifyConfigurationChanged(nsecs_t) {
+    virtual void notifyConfigurationChanged(nsecs_t when) override {
+        mConfigurationChangedTime = when;
     }
 
     virtual nsecs_t notifyANR(const sp<InputApplicationHandle>&,
@@ -144,7 +149,13 @@
         return false;
     }
 
-    virtual void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) {
+    virtual void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
+                              uint32_t policyFlags) override {
+        /** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is
+         * essentially a passthrough for notifySwitch.
+         */
+        mLastNotifySwitch =
+                NotifySwitchArgs(1 /*sequenceNum*/, when, policyFlags, switchValues, switchMask);
     }
 
     virtual void pokeUserActivity(nsecs_t, int32_t) {
@@ -158,9 +169,26 @@
         mOnPointerDownToken = newToken;
     }
 
-    void reset() {
+    void assertFilterInputEventWasCalled(int type, nsecs_t eventTime, int32_t action,
+                                         int32_t displayId) {
+        ASSERT_NE(nullptr, mFilteredEvent) << "Expected filterInputEvent() to have been called.";
+        ASSERT_EQ(mFilteredEvent->getType(), type);
+
+        if (type == AINPUT_EVENT_TYPE_KEY) {
+            const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*mFilteredEvent);
+            EXPECT_EQ(keyEvent.getEventTime(), eventTime);
+            EXPECT_EQ(keyEvent.getAction(), action);
+            EXPECT_EQ(keyEvent.getDisplayId(), displayId);
+        } else if (type == AINPUT_EVENT_TYPE_MOTION) {
+            const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*mFilteredEvent);
+            EXPECT_EQ(motionEvent.getEventTime(), eventTime);
+            EXPECT_EQ(motionEvent.getAction(), action);
+            EXPECT_EQ(motionEvent.getDisplayId(), displayId);
+        } else {
+            FAIL() << "Unknown type: " << type;
+        }
+
         mFilteredEvent = nullptr;
-        mOnPointerDownToken.clear();
     }
 };
 
@@ -171,20 +199,17 @@
 protected:
     sp<FakeInputDispatcherPolicy> mFakePolicy;
     sp<InputDispatcher> mDispatcher;
-    sp<InputDispatcherThread> mDispatcherThread;
 
-    virtual void SetUp() {
+    virtual void SetUp() override {
         mFakePolicy = new FakeInputDispatcherPolicy();
         mDispatcher = new InputDispatcher(mFakePolicy);
         mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
         //Start InputDispatcher thread
-        mDispatcherThread = new InputDispatcherThread(mDispatcher);
-        mDispatcherThread->run("InputDispatcherTest", PRIORITY_URGENT_DISPLAY);
+        ASSERT_EQ(OK, mDispatcher->start());
     }
 
-    virtual void TearDown() {
-        mDispatcherThread->requestExit();
-        mDispatcherThread.clear();
+    virtual void TearDown() override {
+        ASSERT_EQ(OK, mDispatcher->stop());
         mFakePolicy.clear();
         mDispatcher.clear();
     }
@@ -348,9 +373,30 @@
             << "Should reject motion events with duplicate pointer ids.";
 }
 
+/* Test InputDispatcher for notifyConfigurationChanged and notifySwitch events */
+
+TEST_F(InputDispatcherTest, NotifyConfigurationChanged_CallsPolicy) {
+    constexpr nsecs_t eventTime = 20;
+    NotifyConfigurationChangedArgs args(10 /*sequenceNum*/, eventTime);
+    mDispatcher->notifyConfigurationChanged(&args);
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+
+    mFakePolicy->assertNotifyConfigurationChangedWasCalled(eventTime);
+}
+
+TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) {
+    NotifySwitchArgs args(10 /*sequenceNum*/, 20 /*eventTime*/, 0 /*policyFlags*/,
+                          1 /*switchValues*/, 2 /*switchMask*/);
+    mDispatcher->notifySwitch(&args);
+
+    // InputDispatcher adds POLICY_FLAG_TRUSTED because the event went through InputListener
+    args.policyFlags |= POLICY_FLAG_TRUSTED;
+    mFakePolicy->assertNotifySwitchWasCalled(args);
+}
+
 // --- InputDispatcherTest SetInputWindowTest ---
-static const int32_t INJECT_EVENT_TIMEOUT = 500;
-static const int32_t DISPATCHING_TIMEOUT = 100;
+static constexpr int32_t INJECT_EVENT_TIMEOUT = 500;
+static constexpr int32_t DISPATCHING_TIMEOUT = 100;
 
 class FakeApplicationHandle : public InputApplicationHandle {
 public:
@@ -365,19 +411,51 @@
 
 class FakeInputReceiver {
 public:
-    void consumeEvent(int32_t expectedEventType, int32_t expectedAction, int32_t expectedDisplayId,
-                      int32_t expectedFlags) {
+    InputEvent* consume() {
         uint32_t consumeSeq;
         InputEvent* event;
-        status_t status = mConsumer->consume(&mEventFactory, false /*consumeBatches*/, -1,
-            &consumeSeq, &event);
 
-        ASSERT_EQ(OK, status)
-                << mName.c_str() << ": consumer consume should return OK.";
-        ASSERT_TRUE(event != nullptr)
-                << mName.c_str() << ": consumer should have returned non-NULL event.";
+        std::chrono::time_point start = std::chrono::steady_clock::now();
+        status_t status = WOULD_BLOCK;
+        while (status == WOULD_BLOCK) {
+            status = mConsumer->consume(&mEventFactory, false /*consumeBatches*/, -1, &consumeSeq,
+                                        &event);
+            std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
+            if (elapsed > 100ms) {
+                break;
+            }
+        }
+
+        if (status == WOULD_BLOCK) {
+            // Just means there's no event available.
+            return nullptr;
+        }
+
+        if (status != OK) {
+            ADD_FAILURE() << mName.c_str() << ": consumer consume should return OK.";
+            return nullptr;
+        }
+        if (event == nullptr) {
+            ADD_FAILURE() << "Consumed correctly, but received NULL event from consumer";
+            return nullptr;
+        }
+
+        status = mConsumer->sendFinishedSignal(consumeSeq, handled());
+        if (status != OK) {
+            ADD_FAILURE() << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
+        }
+        return event;
+    }
+
+    void consumeEvent(int32_t expectedEventType, int32_t expectedAction, int32_t expectedDisplayId,
+                      int32_t expectedFlags) {
+        InputEvent* event = consume();
+
+        ASSERT_NE(nullptr, event) << mName.c_str()
+                                  << ": consumer should have returned non-NULL event.";
         ASSERT_EQ(expectedEventType, event->getType())
-                << mName.c_str() << ": event type should match.";
+                << mName.c_str() << "expected " << inputEventTypeToString(expectedEventType)
+                << " event, got " << inputEventTypeToString(event->getType()) << " event";
 
         EXPECT_EQ(expectedDisplayId, event->getDisplayId());
 
@@ -398,10 +476,6 @@
                 FAIL() << mName.c_str() << ": invalid event type: " << expectedEventType;
             }
         }
-
-        status = mConsumer->sendFinishedSignal(consumeSeq, handled());
-        ASSERT_EQ(OK, status)
-                << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
     }
 
     void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
@@ -414,14 +488,16 @@
                      expectedFlags);
     }
 
+    void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+        consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId,
+                     expectedFlags);
+    }
+
     void assertNoEvents() {
-        uint32_t consumeSeq;
-        InputEvent* event;
-        status_t status = mConsumer->consume(&mEventFactory, false /*consumeBatches*/, -1,
-            &consumeSeq, &event);
-        ASSERT_NE(OK, status)
+        InputEvent* event = consume();
+        ASSERT_EQ(nullptr, event)
                 << mName.c_str()
-                << ": should not have received any events, so consume(..) should not return OK.";
+                << ": should not have received any events, so consume() should return NULL";
     }
 
 protected:
@@ -508,9 +584,7 @@
         InputWindowHandle::releaseChannel();
     }
 protected:
-    virtual bool handled() {
-        return true;
-    }
+    virtual bool handled() override { return true; }
 
     bool mFocused;
     Rect mFrame;
@@ -570,6 +644,11 @@
     return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_DOWN, source, displayId, x, y);
 }
 
+static int32_t injectMotionUp(const sp<InputDispatcher>& dispatcher, int32_t source,
+                              int32_t displayId, int32_t x = 100, int32_t y = 200) {
+    return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_UP, source, displayId, x, y);
+}
+
 static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
@@ -610,10 +689,7 @@
     sp<FakeWindowHandle> window = new FakeWindowHandle(application, mDispatcher, "Fake Window",
             ADISPLAY_ID_DEFAULT);
 
-    std::vector<sp<InputWindowHandle>> inputWindowHandles;
-    inputWindowHandles.push_back(window);
-
-    mDispatcher->setInputWindows(inputWindowHandles, ADISPLAY_ID_DEFAULT);
+    mDispatcher->setInputWindows({window}, ADISPLAY_ID_DEFAULT);
     ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectMotionDown(mDispatcher,
             AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return INPUT_EVENT_INJECTION_SUCCEEDED";
@@ -630,11 +706,7 @@
     sp<FakeWindowHandle> windowSecond = new FakeWindowHandle(application, mDispatcher, "Second",
             ADISPLAY_ID_DEFAULT);
 
-    std::vector<sp<InputWindowHandle>> inputWindowHandles;
-    inputWindowHandles.push_back(windowTop);
-    inputWindowHandles.push_back(windowSecond);
-
-    mDispatcher->setInputWindows(inputWindowHandles, ADISPLAY_ID_DEFAULT);
+    mDispatcher->setInputWindows({windowTop, windowSecond}, ADISPLAY_ID_DEFAULT);
     ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectMotionDown(mDispatcher,
             AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return INPUT_EVENT_INJECTION_SUCCEEDED";
@@ -656,11 +728,8 @@
 
     // Expect one focus window exist in display.
     windowSecond->setFocus();
-    std::vector<sp<InputWindowHandle>> inputWindowHandles;
-    inputWindowHandles.push_back(windowTop);
-    inputWindowHandles.push_back(windowSecond);
 
-    mDispatcher->setInputWindows(inputWindowHandles, ADISPLAY_ID_DEFAULT);
+    mDispatcher->setInputWindows({windowTop, windowSecond}, ADISPLAY_ID_DEFAULT);
     ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectKeyDown(mDispatcher))
             << "Inject key event should return INPUT_EVENT_INJECTION_SUCCEEDED";
 
@@ -682,11 +751,8 @@
     // Display has two focused windows. Add them to inputWindowsHandles in z-order (top most first)
     windowTop->setFocus();
     windowSecond->setFocus();
-    std::vector<sp<InputWindowHandle>> inputWindowHandles;
-    inputWindowHandles.push_back(windowTop);
-    inputWindowHandles.push_back(windowSecond);
 
-    mDispatcher->setInputWindows(inputWindowHandles, ADISPLAY_ID_DEFAULT);
+    mDispatcher->setInputWindows({windowTop, windowSecond}, ADISPLAY_ID_DEFAULT);
     ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectKeyDown(mDispatcher))
             << "Inject key event should return INPUT_EVENT_INJECTION_SUCCEEDED";
 
@@ -708,12 +774,9 @@
 
     windowTop->setFocus();
     windowSecond->setFocus();
-    std::vector<sp<InputWindowHandle>> inputWindowHandles;
-    inputWindowHandles.push_back(windowTop);
-    inputWindowHandles.push_back(windowSecond);
     // Release channel for window is no longer valid.
     windowTop->releaseChannel();
-    mDispatcher->setInputWindows(inputWindowHandles, ADISPLAY_ID_DEFAULT);
+    mDispatcher->setInputWindows({windowTop, windowSecond}, ADISPLAY_ID_DEFAULT);
 
     // Test inject a key down, should dispatch to a valid window.
     ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectKeyDown(mDispatcher))
@@ -738,8 +801,7 @@
 
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
 
-    std::vector<sp<InputWindowHandle>> inputWindowHandles{windowLeft, windowRight};
-    mDispatcher->setInputWindows(inputWindowHandles, ADISPLAY_ID_DEFAULT);
+    mDispatcher->setInputWindows({windowLeft, windowRight}, ADISPLAY_ID_DEFAULT);
 
     // Inject an event with coordinate in the area of right window, with mouse cursor in the area of
     // left window. This event should be dispatched to the left window.
@@ -750,38 +812,155 @@
     windowRight->assertNoEvents();
 }
 
+TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) {
+    sp<FakeApplicationHandle> application = new FakeApplicationHandle();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+    window->setFocus();
+
+    mDispatcher->setInputWindows({window}, ADISPLAY_ID_DEFAULT);
+
+    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(&keyArgs);
+
+    // Window should receive key down event.
+    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+
+    // When device reset happens, that key stream should be terminated with FLAG_CANCELED
+    // on the app side.
+    NotifyDeviceResetArgs args(10 /*sequenceNum*/, 20 /*eventTime*/, DEVICE_ID);
+    mDispatcher->notifyDeviceReset(&args);
+    window->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT,
+                         AKEY_EVENT_FLAG_CANCELED);
+}
+
+TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) {
+    sp<FakeApplicationHandle> application = new FakeApplicationHandle();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+
+    mDispatcher->setInputWindows({window}, ADISPLAY_ID_DEFAULT);
+
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&motionArgs);
+
+    // Window should receive motion down event.
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    // When device reset happens, that motion stream should be terminated with ACTION_CANCEL
+    // on the app side.
+    NotifyDeviceResetArgs args(10 /*sequenceNum*/, 20 /*eventTime*/, DEVICE_ID);
+    mDispatcher->notifyDeviceReset(&args);
+    window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT,
+                         0 /*expectedFlags*/);
+}
+
+class FakeMonitorReceiver : public FakeInputReceiver, public RefBase {
+public:
+    FakeMonitorReceiver(const sp<InputDispatcher>& dispatcher, const std::string name,
+                        int32_t displayId, bool isGestureMonitor = false)
+          : FakeInputReceiver(dispatcher, name, displayId) {
+        mDispatcher->registerInputMonitor(mServerChannel, displayId, isGestureMonitor);
+    }
+
+    sp<IBinder> getToken() { return mServerChannel->getConnectionToken(); }
+};
+
+// Tests for gesture monitors
+TEST_F(InputDispatcherTest, GestureMonitor_ReceivesMotionEvents) {
+    sp<FakeApplicationHandle> application = new FakeApplicationHandle();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+    mDispatcher->setInputWindows({window}, ADISPLAY_ID_DEFAULT);
+
+    sp<FakeMonitorReceiver> monitor =
+            new FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
+                                    true /*isGestureMonitor*/);
+
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return INPUT_EVENT_INJECTION_SUCCEEDED";
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    monitor->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+}
+
+TEST_F(InputDispatcherTest, GestureMonitor_DoesNotReceiveKeyEvents) {
+    sp<FakeApplicationHandle> application = new FakeApplicationHandle();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    window->setFocus();
+
+    mDispatcher->setInputWindows({window}, ADISPLAY_ID_DEFAULT);
+
+    sp<FakeMonitorReceiver> monitor =
+            new FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
+                                    true /*isGestureMonitor*/);
+
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT))
+            << "Inject key event should return INPUT_EVENT_INJECTION_SUCCEEDED";
+    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    monitor->assertNoEvents();
+}
+
+TEST_F(InputDispatcherTest, GestureMonitor_CanPilferAfterWindowIsRemovedMidStream) {
+    sp<FakeApplicationHandle> application = new FakeApplicationHandle();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+    mDispatcher->setInputWindows({window}, ADISPLAY_ID_DEFAULT);
+
+    sp<FakeMonitorReceiver> monitor =
+            new FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
+                                    true /*isGestureMonitor*/);
+
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return INPUT_EVENT_INJECTION_SUCCEEDED";
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    monitor->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    window->releaseChannel();
+
+    mDispatcher->pilferPointers(monitor->getToken());
+
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return INPUT_EVENT_INJECTION_SUCCEEDED";
+    monitor->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+}
+
 /* Test InputDispatcher for MultiDisplay */
 class InputDispatcherFocusOnTwoDisplaysTest : public InputDispatcherTest {
 public:
     static constexpr int32_t SECOND_DISPLAY_ID = 1;
-    virtual void SetUp() {
+    virtual void SetUp() override {
         InputDispatcherTest::SetUp();
 
         application1 = new FakeApplicationHandle();
         windowInPrimary = new FakeWindowHandle(application1, mDispatcher, "D_1",
                 ADISPLAY_ID_DEFAULT);
-        std::vector<sp<InputWindowHandle>> inputWindowHandles;
-        inputWindowHandles.push_back(windowInPrimary);
+
         // Set focus window for primary display, but focused display would be second one.
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application1);
         windowInPrimary->setFocus();
-        mDispatcher->setInputWindows(inputWindowHandles, ADISPLAY_ID_DEFAULT);
+        mDispatcher->setInputWindows({windowInPrimary}, ADISPLAY_ID_DEFAULT);
 
         application2 = new FakeApplicationHandle();
         windowInSecondary = new FakeWindowHandle(application2, mDispatcher, "D_2",
                 SECOND_DISPLAY_ID);
         // Set focus to second display window.
-        std::vector<sp<InputWindowHandle>> inputWindowHandles_Second;
-        inputWindowHandles_Second.push_back(windowInSecondary);
         // Set focus display to second one.
         mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID);
         // Set focus window for second display.
         mDispatcher->setFocusedApplication(SECOND_DISPLAY_ID, application2);
         windowInSecondary->setFocus();
-        mDispatcher->setInputWindows(inputWindowHandles_Second, SECOND_DISPLAY_ID);
+        mDispatcher->setInputWindows({windowInSecondary}, SECOND_DISPLAY_ID);
     }
 
-    virtual void TearDown() {
+    virtual void TearDown() override {
         InputDispatcherTest::TearDown();
 
         application1.clear();
@@ -826,9 +1005,8 @@
     windowInPrimary->assertNoEvents();
     windowInSecondary->consumeKeyDown(ADISPLAY_ID_NONE);
 
-    // Remove secondary display.
-    std::vector<sp<InputWindowHandle>> noWindows;
-    mDispatcher->setInputWindows(noWindows, SECOND_DISPLAY_ID);
+    // Remove all windows in secondary display.
+    mDispatcher->setInputWindows({}, SECOND_DISPLAY_ID);
 
     // Expect old focus should receive a cancel event.
     windowInSecondary->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_NONE,
@@ -841,15 +1019,6 @@
     windowInSecondary->assertNoEvents();
 }
 
-class FakeMonitorReceiver : public FakeInputReceiver, public RefBase {
-public:
-    FakeMonitorReceiver(const sp<InputDispatcher>& dispatcher, const std::string name,
-            int32_t displayId, bool isGestureMonitor = false)
-            : FakeInputReceiver(dispatcher, name, displayId) {
-        mDispatcher->registerInputMonitor(mServerChannel, displayId, isGestureMonitor);
-    }
-};
-
 // Test per-display input monitors for motion event.
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) {
     sp<FakeMonitorReceiver> monitorInPrimary =
@@ -917,7 +1086,7 @@
         motionArgs = generateMotionArgs(
                 AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, displayId);
         mDispatcher->notifyMotion(&motionArgs);
-
+        ASSERT_TRUE(mDispatcher->waitForIdle());
         if (expectToBeFiltered) {
             mFakePolicy->assertFilterInputEventWasCalled(motionArgs);
         } else {
@@ -932,6 +1101,7 @@
         mDispatcher->notifyKey(&keyArgs);
         keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP);
         mDispatcher->notifyKey(&keyArgs);
+        ASSERT_TRUE(mDispatcher->waitForIdle());
 
         if (expectToBeFiltered) {
             mFakePolicy->assertFilterInputEventWasCalled(keyArgs);
@@ -977,7 +1147,7 @@
 }
 
 class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest {
-    virtual void SetUp() {
+    virtual void SetUp() override {
         InputDispatcherTest::SetUp();
 
         sp<FakeApplicationHandle> application = new FakeApplicationHandle();
@@ -988,34 +1158,31 @@
         // window.
         mUnfocusedWindow->setLayoutParamFlags(InputWindowInfo::FLAG_NOT_TOUCH_MODAL);
 
-        mWindowFocused = new FakeWindowHandle(application, mDispatcher, "Second",
-                ADISPLAY_ID_DEFAULT);
-        mWindowFocused->setFrame(Rect(50, 50, 100, 100));
-        mWindowFocused->setLayoutParamFlags(InputWindowInfo::FLAG_NOT_TOUCH_MODAL);
-        mWindowFocusedTouchPoint = 60;
+        mFocusedWindow =
+                new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+        mFocusedWindow->setFrame(Rect(50, 50, 100, 100));
+        mFocusedWindow->setLayoutParamFlags(InputWindowInfo::FLAG_NOT_TOUCH_MODAL);
+        mFocusedWindowTouchPoint = 60;
 
         // Set focused application.
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
-        mWindowFocused->setFocus();
+        mFocusedWindow->setFocus();
 
         // Expect one focus window exist in display.
-        std::vector<sp<InputWindowHandle>> inputWindowHandles;
-        inputWindowHandles.push_back(mUnfocusedWindow);
-        inputWindowHandles.push_back(mWindowFocused);
-        mDispatcher->setInputWindows(inputWindowHandles, ADISPLAY_ID_DEFAULT);
+        mDispatcher->setInputWindows({mUnfocusedWindow, mFocusedWindow}, ADISPLAY_ID_DEFAULT);
     }
 
-    virtual void TearDown() {
+    virtual void TearDown() override {
         InputDispatcherTest::TearDown();
 
         mUnfocusedWindow.clear();
-        mWindowFocused.clear();
+        mFocusedWindow.clear();
     }
 
 protected:
     sp<FakeWindowHandle> mUnfocusedWindow;
-    sp<FakeWindowHandle> mWindowFocused;
-    int32_t mWindowFocusedTouchPoint;
+    sp<FakeWindowHandle> mFocusedWindow;
+    int32_t mFocusedWindowTouchPoint;
 };
 
 // Have two windows, one with focus. Inject MotionEvent with source TOUCHSCREEN and action
@@ -1025,9 +1192,8 @@
     ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectMotionDown(mDispatcher,
             AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, 20, 20))
             << "Inject motion event should return INPUT_EVENT_INJECTION_SUCCEEDED";
-    // Call monitor to wait for the command queue to get flushed.
-    mDispatcher->monitor();
 
+    ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertOnPointerDownEquals(mUnfocusedWindow->getToken());
 }
 
@@ -1038,10 +1204,9 @@
     ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectMotionDown(mDispatcher,
             AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_DEFAULT, 20, 20))
             << "Inject motion event should return INPUT_EVENT_INJECTION_SUCCEEDED";
-    // Call monitor to wait for the command queue to get flushed.
-    mDispatcher->monitor();
 
-    mFakePolicy->assertOnPointerDownEquals(nullptr);
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertOnPointerDownWasNotCalled();
 }
 
 // Have two windows, one with focus. Inject KeyEvent with action DOWN on the window that doesn't
@@ -1049,10 +1214,9 @@
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonMotionFailure) {
     ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT))
             << "Inject key event should return INPUT_EVENT_INJECTION_SUCCEEDED";
-    // Call monitor to wait for the command queue to get flushed.
-    mDispatcher->monitor();
 
-    mFakePolicy->assertOnPointerDownEquals(nullptr);
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertOnPointerDownWasNotCalled();
 }
 
 // Have two windows, one with focus. Inject MotionEvent with source TOUCHSCREEN and action
@@ -1060,14 +1224,13 @@
 // onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus,
         OnPointerDownOutsideFocus_OnAlreadyFocusedWindow) {
-    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectMotionDown(mDispatcher,
-            AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, mWindowFocusedTouchPoint,
-            mWindowFocusedTouchPoint))
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               mFocusedWindowTouchPoint, mFocusedWindowTouchPoint))
             << "Inject motion event should return INPUT_EVENT_INJECTION_SUCCEEDED";
-    // Call monitor to wait for the command queue to get flushed.
-    mDispatcher->monitor();
 
-    mFakePolicy->assertOnPointerDownEquals(nullptr);
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertOnPointerDownWasNotCalled();
 }
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index c1c9122..1fc8217 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -1113,6 +1113,9 @@
                                classes);
     }
 
+    // Make the protected loopOnce method accessible to tests.
+    using InputReader::loopOnce;
+
 protected:
     virtual InputDevice* createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
                                             const InputDeviceIdentifier& identifier,
@@ -1133,12 +1136,8 @@
 protected:
     sp<FakeInputReaderPolicy> mFakePolicy;
 
-    virtual void SetUp() {
-        mFakePolicy = new FakeInputReaderPolicy();
-    }
-    virtual void TearDown() {
-        mFakePolicy.clear();
-    }
+    virtual void SetUp() override { mFakePolicy = new FakeInputReaderPolicy(); }
+    virtual void TearDown() override { mFakePolicy.clear(); }
 };
 
 /**
@@ -1321,19 +1320,18 @@
     sp<TestInputListener> mFakeListener;
     sp<FakeInputReaderPolicy> mFakePolicy;
     std::shared_ptr<FakeEventHub> mFakeEventHub;
-    sp<InstrumentedInputReader> mReader;
+    std::unique_ptr<InstrumentedInputReader> mReader;
 
-    virtual void SetUp() {
+    virtual void SetUp() override {
         mFakeEventHub = std::make_unique<FakeEventHub>();
         mFakePolicy = new FakeInputReaderPolicy();
         mFakeListener = new TestInputListener();
 
-        mReader = new InstrumentedInputReader(mFakeEventHub, mFakePolicy, mFakeListener);
+        mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
+                                                            mFakeListener);
     }
 
-    virtual void TearDown() {
-        mReader.clear();
-
+    virtual void TearDown() override {
         mFakeListener.clear();
         mFakePolicy.clear();
     }
@@ -1354,16 +1352,12 @@
 
     void disableDevice(int32_t deviceId, InputDevice* device) {
         mFakePolicy->addDisabledDevice(deviceId);
-        configureDevice(InputReaderConfiguration::CHANGE_ENABLED_STATE, device);
+        mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_ENABLED_STATE);
     }
 
     void enableDevice(int32_t deviceId, InputDevice* device) {
         mFakePolicy->removeDisabledDevice(deviceId);
-        configureDevice(InputReaderConfiguration::CHANGE_ENABLED_STATE, device);
-    }
-
-    void configureDevice(uint32_t changes, InputDevice* device) {
-        device->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes);
+        mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_ENABLED_STATE);
     }
 
     FakeInputMapper* addDeviceWithFakeInputMapper(int32_t deviceId, int32_t controllerNumber,
@@ -1417,7 +1411,6 @@
 
     NotifyDeviceResetArgs resetArgs;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
     ASSERT_EQ(deviceId, resetArgs.deviceId);
 
     ASSERT_EQ(device->isEnabled(), true);
@@ -1425,7 +1418,6 @@
     mReader->loopOnce();
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
     ASSERT_EQ(deviceId, resetArgs.deviceId);
     ASSERT_EQ(device->isEnabled(), false);
 
@@ -1438,7 +1430,6 @@
     enableDevice(deviceId, device);
     mReader->loopOnce();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
     ASSERT_EQ(deviceId, resetArgs.deviceId);
     ASSERT_EQ(device->isEnabled(), true);
 }
@@ -1629,7 +1620,6 @@
     FakeInputMapper* mapper = new FakeInputMapper(device, AINPUT_SOURCE_TOUCHSCREEN);
     device->addMapper(mapper);
     mReader->setNextDevice(device);
-    addDevice(deviceId, "fake", deviceClass, nullptr);
 
     const uint8_t hdmi1 = 1;
 
@@ -1637,13 +1627,21 @@
     mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1);
 
     // Add default and second display.
+    mFakePolicy->clearViewports();
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
             DISPLAY_ORIENTATION_0, "local:0", NO_PORT, ViewportType::VIEWPORT_INTERNAL);
     mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
             DISPLAY_ORIENTATION_0, "local:1", hdmi1, ViewportType::VIEWPORT_EXTERNAL);
     mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
     mReader->loopOnce();
+
+    // Add the device, and make sure all of the callbacks are triggered.
+    // The device is added after the input port associations are processed since
+    // we do not yet support dynamic device-to-display associations.
+    ASSERT_NO_FATAL_FAILURE(addDevice(deviceId, "fake", deviceClass, nullptr));
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+    ASSERT_NO_FATAL_FAILURE(mapper->assertConfigureWasCalled());
 
     // Device should only dispatch to the specified display.
     ASSERT_EQ(deviceId, device->getId());
@@ -1652,6 +1650,7 @@
 
     // Can't dispatch event from a disabled device.
     disableDevice(deviceId, device);
+    mReader->loopOnce();
     ASSERT_FALSE(mReader->canDispatchToDisplay(deviceId, SECONDARY_DISPLAY_ID));
 }
 
@@ -1674,7 +1673,7 @@
 
     InputDevice* mDevice;
 
-    virtual void SetUp() {
+    virtual void SetUp() override {
         mFakeEventHub = std::make_unique<FakeEventHub>();
         mFakePolicy = new FakeInputReaderPolicy();
         mFakeListener = new TestInputListener();
@@ -1688,7 +1687,7 @@
                 DEVICE_CONTROLLER_NUMBER, identifier, DEVICE_CLASSES);
     }
 
-    virtual void TearDown() {
+    virtual void TearDown() override {
         delete mDevice;
 
         delete mFakeContext;
@@ -1912,7 +1911,7 @@
     FakeInputReaderContext* mFakeContext;
     InputDevice* mDevice;
 
-    virtual void SetUp() {
+    virtual void SetUp() override {
         mFakeEventHub = std::make_unique<FakeEventHub>();
         mFakePolicy = new FakeInputReaderPolicy();
         mFakeListener = new TestInputListener();
@@ -1926,7 +1925,7 @@
         mFakeEventHub->addDevice(mDevice->getId(), DEVICE_NAME, 0);
     }
 
-    virtual void TearDown() {
+    virtual void TearDown() override {
         delete mDevice;
         delete mFakeContext;
         mFakeListener.clear();
@@ -2581,6 +2580,84 @@
                                                 AKEYCODE_DPAD_LEFT, newDisplayId));
 }
 
+TEST_F(KeyboardInputMapperTest, ExternalDevice_WakeBehavior) {
+    // For external devices, non-media keys will trigger wake on key down. Media keys need to be
+    // marked as WAKE in the keylayout file to trigger wake.
+    mDevice->setExternal(true);
+
+    mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, 0, AKEYCODE_HOME, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_PLAYPAUSE, 0, AKEYCODE_MEDIA_PLAY_PAUSE, POLICY_FLAG_WAKE);
+
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, AINPUT_SOURCE_KEYBOARD,
+                                                          AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addMapperAndConfigure(mapper);
+
+    process(mapper, ARBITRARY_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);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+
+    process(mapper, ARBITRARY_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);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+
+    process(mapper, ARBITRARY_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);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+}
+
+TEST_F(KeyboardInputMapperTest, ExternalDevice_DoNotWakeByDefaultBehavior) {
+    // Tv Remote key's wake behavior is prescribed by the keylayout file.
+    mDevice->setExternal(true);
+
+    mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_PLAY, 0, AKEYCODE_MEDIA_PLAY, POLICY_FLAG_WAKE);
+
+    KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, AINPUT_SOURCE_KEYBOARD,
+                                                          AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    addConfigurationProperty("keyboard.doNotWakeByDefault", "1");
+    addMapperAndConfigure(mapper);
+
+    process(mapper, ARBITRARY_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);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+
+    process(mapper, ARBITRARY_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);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+
+    process(mapper, ARBITRARY_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);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+}
+
 // --- CursorInputMapperTest ---
 
 class CursorInputMapperTest : public InputMapperTest {
@@ -2589,7 +2666,7 @@
 
     sp<FakePointerController> mFakePointerController;
 
-    virtual void SetUp() {
+    virtual void SetUp() override {
         InputMapperTest::SetUp();
 
         mFakePointerController = new FakePointerController();
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 14ed73d..c2e1204 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -1173,6 +1173,11 @@
                 return nullptr;
             }
             int fd = resource->data[0];
+            if (!ashmem_valid(fd)) {
+                ALOGE("Supplied Ashmem memory region is invalid");
+                return nullptr;
+            }
+
             int size2 = ashmem_get_size_region(fd);
             // check size consistency
             if (size2 < static_cast<int64_t>(size)) {
diff --git a/services/sensorservice/tests/sensorservicetest.cpp b/services/sensorservice/tests/sensorservicetest.cpp
index 1cb0489..caf7f03 100644
--- a/services/sensorservice/tests/sensorservicetest.cpp
+++ b/services/sensorservice/tests/sensorservicetest.cpp
@@ -15,19 +15,20 @@
  */
 
 #include <inttypes.h>
+#include <android/hardware_buffer.h>
 #include <android/sensor.h>
 #include <sensor/Sensor.h>
 #include <sensor/SensorManager.h>
 #include <sensor/SensorEventQueue.h>
 #include <utils/Looper.h>
+#include <vndk/hardware_buffer.h>
 
 using namespace android;
 
 static nsecs_t sStartTime = 0;
 
 
-int receiver(__unused int fd, __unused int events, void* data)
-{
+int receiver(__unused int fd, __unused int events, void* data) {
     sp<SensorEventQueue> q((SensorEventQueue*)data);
     ssize_t n;
     ASensorEvent buffer[8];
@@ -59,11 +60,42 @@
     return 1;
 }
 
+void testInvalidSharedMem_NoCrash(SensorManager &mgr) {
+    AHardwareBuffer *hardwareBuffer;
+    char* buffer;
 
-int main()
-{
+    constexpr size_t kEventSize = sizeof(ASensorEvent);
+    constexpr size_t kNEvent = 4096; // enough to contain 1.5 * 800 * 2.2 events
+    constexpr size_t kMemSize = kEventSize * kNEvent;
+    AHardwareBuffer_Desc desc = {
+            .width = static_cast<uint32_t>(kMemSize),
+            .height = 1,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_BLOB,
+            .usage = AHARDWAREBUFFER_USAGE_SENSOR_DIRECT_DATA
+                        | AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+    };
+
+    AHardwareBuffer_allocate(&desc, &hardwareBuffer);
+    AHardwareBuffer_lock(hardwareBuffer, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY,
+                         -1, nullptr, reinterpret_cast<void **>(&buffer));
+
+    const native_handle_t *resourceHandle = AHardwareBuffer_getNativeHandle(hardwareBuffer);
+
+    // Pass in AHardwareBuffer, but with the wrong DIRECT_CHANNEL_TYPE to see
+    // if anything in the Sensor framework crashes
+    int ret = mgr.createDirectChannel(
+            kMemSize, ASENSOR_DIRECT_CHANNEL_TYPE_SHARED_MEMORY, resourceHandle);
+
+    // Should print -22 (BAD_VALUE) and the device runtime shouldn't restart
+    printf("createInvalidDirectChannel=%d\n", ret);
+}
+
+int main() {
     SensorManager& mgr = SensorManager::getInstanceForPackage(String16("Sensor Service Test"));
 
+    testInvalidSharedMem_NoCrash(mgr);
+
     Sensor const* const* list;
     ssize_t count = mgr.getSensorList(&list);
     printf("numSensors=%d\n", int(count));
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 4b71bd8..d476f7b4 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -97,6 +97,10 @@
         "android.hardware.power@1.3",
         "libhidlbase",
     ],
+    // TODO (marissaw): this library is not used by surfaceflinger. This is here so
+    // the library compiled in a way that is accessible to system partition when running
+    // IMapper's VTS.
+    required: ["libgralloctypes"]
 }
 
 cc_defaults {
@@ -163,12 +167,14 @@
         "Scheduler/LayerInfo.cpp",
         "Scheduler/MessageQueue.cpp",
         "Scheduler/PhaseOffsets.cpp",
+        "Scheduler/RefreshRateConfigs.cpp",
         "Scheduler/Scheduler.cpp",
         "Scheduler/SchedulerUtils.cpp",
         "Scheduler/Timer.cpp",
         "Scheduler/VSyncDispatchTimerQueue.cpp",
         "Scheduler/VSyncPredictor.cpp",
         "Scheduler/VSyncModulator.cpp",
+        "Scheduler/VSyncReactor.cpp",
         "StartPropertySetThread.cpp",
         "SurfaceFlinger.cpp",
         "SurfaceFlingerDefaultFactory.cpp",
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 94c4a81..054acc5 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -56,6 +56,9 @@
 
 namespace android {
 
+static constexpr float defaultMaxMasteringLuminance = 1000.0;
+static constexpr float defaultMaxContentLuminance = 1000.0;
+
 BufferLayer::BufferLayer(const LayerCreationArgs& args)
       : Layer(args),
         mTextureName(args.textureName),
@@ -184,6 +187,14 @@
         layer.source.buffer.textureName = mTextureName;
         layer.source.buffer.usePremultipliedAlpha = getPremultipledAlpha();
         layer.source.buffer.isY410BT2020 = isHdrY410();
+        bool hasSmpte2086 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::SMPTE2086;
+        bool hasCta861_3 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::CTA861_3;
+        layer.source.buffer.maxMasteringLuminance = hasSmpte2086
+                ? mBufferInfo.mHdrMetadata.smpte2086.maxLuminance
+                : defaultMaxMasteringLuminance;
+        layer.source.buffer.maxContentLuminance = hasCta861_3
+                ? mBufferInfo.mHdrMetadata.cta8613.maxContentLightLevel
+                : defaultMaxContentLuminance;
         // TODO: we could be more subtle with isFixedSize()
         const bool useFiltering = targetSettings.needsFiltering || mNeedsFiltering || isFixedSize();
 
diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h
index 16855d2..5f5532e 100644
--- a/services/surfaceflinger/BufferLayer.h
+++ b/services/surfaceflinger/BufferLayer.h
@@ -190,7 +190,7 @@
 
     static bool getOpacityForFormat(uint32_t format);
 
-    // from GLES
+    // from graphics API
     const uint32_t mTextureName;
 
     bool mRefreshPending{false};
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index d51d34b..945abd7 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -35,6 +35,7 @@
 BufferQueueLayer::BufferQueueLayer(const LayerCreationArgs& args) : BufferLayer(args) {}
 
 BufferQueueLayer::~BufferQueueLayer() {
+    mContentsChangedListener->abandon();
     mConsumer->abandon();
 }
 
@@ -399,8 +400,7 @@
     // Add this buffer from our internal queue tracker
     { // Autolock scope
         const nsecs_t presentTime = item.mIsAutoTimestamp ? 0 : item.mTimestamp;
-        const bool isHDR = item.mHdrMetadata.validTypes != 0;
-        mFlinger->mScheduler->recordLayerHistory(this, presentTime, isHDR);
+        mFlinger->mScheduler->recordLayerHistory(this, presentTime);
 
         Mutex::Autolock lock(mQueueItemLock);
         // Reset the frame number tracker when we receive the first buffer after
@@ -480,7 +480,9 @@
             mFlinger->getFactory().createBufferLayerConsumer(consumer, mFlinger->getRenderEngine(),
                                                              mTextureName, this);
     mConsumer->setConsumerUsageBits(getEffectiveUsage(0));
-    mConsumer->setContentsChangedListener(this);
+
+    mContentsChangedListener = new ContentsChangedListener(this);
+    mConsumer->setContentsChangedListener(mContentsChangedListener);
     mConsumer->setName(String8(mName.data(), mName.size()));
 
     // BufferQueueCore::mMaxDequeuedBufferCount is default to 1
@@ -552,4 +554,57 @@
     return layer;
 }
 
+// -----------------------------------------------------------------------
+// Interface implementation for BufferLayerConsumer::ContentsChangedListener
+// -----------------------------------------------------------------------
+
+void BufferQueueLayer::ContentsChangedListener::onFrameAvailable(const BufferItem& item) {
+    Mutex::Autolock lock(mMutex);
+    if (mBufferQueueLayer != nullptr) {
+        mBufferQueueLayer->onFrameAvailable(item);
+    }
+}
+
+void BufferQueueLayer::ContentsChangedListener::onFrameReplaced(const BufferItem& item) {
+    Mutex::Autolock lock(mMutex);
+    if (mBufferQueueLayer != nullptr) {
+        mBufferQueueLayer->onFrameReplaced(item);
+    }
+}
+
+void BufferQueueLayer::ContentsChangedListener::onSidebandStreamChanged() {
+    Mutex::Autolock lock(mMutex);
+    if (mBufferQueueLayer != nullptr) {
+        mBufferQueueLayer->onSidebandStreamChanged();
+    }
+}
+
+void BufferQueueLayer::ContentsChangedListener::onFrameDequeued(const uint64_t bufferId) {
+    Mutex::Autolock lock(mMutex);
+    if (mBufferQueueLayer != nullptr) {
+        mBufferQueueLayer->onFrameDequeued(bufferId);
+    }
+}
+
+void BufferQueueLayer::ContentsChangedListener::onFrameDetached(const uint64_t bufferId) {
+    Mutex::Autolock lock(mMutex);
+    if (mBufferQueueLayer != nullptr) {
+        mBufferQueueLayer->onFrameDetached(bufferId);
+    }
+}
+
+void BufferQueueLayer::ContentsChangedListener::onFrameCancelled(const uint64_t bufferId) {
+    Mutex::Autolock lock(mMutex);
+    if (mBufferQueueLayer != nullptr) {
+        mBufferQueueLayer->onFrameCancelled(bufferId);
+    }
+}
+
+void BufferQueueLayer::ContentsChangedListener::abandon() {
+    Mutex::Autolock lock(mMutex);
+    mBufferQueueLayer = nullptr;
+}
+
+// -----------------------------------------------------------------------
+
 } // namespace android
diff --git a/services/surfaceflinger/BufferQueueLayer.h b/services/surfaceflinger/BufferQueueLayer.h
index 1b1fccd..b040556 100644
--- a/services/surfaceflinger/BufferQueueLayer.h
+++ b/services/surfaceflinger/BufferQueueLayer.h
@@ -29,7 +29,7 @@
  * This also implements onFrameAvailable(), which notifies SurfaceFlinger
  * that new data has arrived.
  */
-class BufferQueueLayer : public BufferLayer, public BufferLayerConsumer::ContentsChangedListener {
+class BufferQueueLayer : public BufferLayer {
 public:
     // Only call while mStateLock is held
     explicit BufferQueueLayer(const LayerCreationArgs&);
@@ -84,18 +84,37 @@
     void latchPerFrameState(compositionengine::LayerFECompositionState&) const override;
     sp<Layer> createClone() override;
 
-    // -----------------------------------------------------------------------
-    // Interface implementation for BufferLayerConsumer::ContentsChangedListener
-    // -----------------------------------------------------------------------
+    void onFrameAvailable(const BufferItem& item);
+    void onFrameReplaced(const BufferItem& item);
+    void onSidebandStreamChanged();
+    void onFrameDequeued(const uint64_t bufferId);
+    void onFrameDetached(const uint64_t bufferId);
+    void onFrameCancelled(const uint64_t bufferId);
+
 protected:
     void gatherBufferInfo() override;
 
-    void onFrameAvailable(const BufferItem& item) override;
-    void onFrameReplaced(const BufferItem& item) override;
-    void onSidebandStreamChanged() override;
-    void onFrameDequeued(const uint64_t bufferId) override;
-    void onFrameDetached(const uint64_t bufferId) override;
-    void onFrameCancelled(const uint64_t bufferId) override;
+    // -----------------------------------------------------------------------
+    // Interface implementation for BufferLayerConsumer::ContentsChangedListener
+    // -----------------------------------------------------------------------
+    class ContentsChangedListener : public BufferLayerConsumer::ContentsChangedListener {
+    public:
+        ContentsChangedListener(BufferQueueLayer* bufferQueueLayer)
+              : mBufferQueueLayer(bufferQueueLayer) {}
+        void abandon();
+
+    protected:
+        void onFrameAvailable(const BufferItem& item) override;
+        void onFrameReplaced(const BufferItem& item) override;
+        void onSidebandStreamChanged() override;
+        void onFrameDequeued(const uint64_t bufferId) override;
+        void onFrameDetached(const uint64_t bufferId) override;
+        void onFrameCancelled(const uint64_t bufferId) override;
+
+    private:
+        BufferQueueLayer* mBufferQueueLayer = nullptr;
+        Mutex mMutex;
+    };
     // -----------------------------------------------------------------------
 
 public:
@@ -130,6 +149,8 @@
     // thread-safe
     std::atomic<int32_t> mQueuedFrames{0};
     std::atomic<bool> mSidebandStreamChanged{false};
+
+    sp<ContentsChangedListener> mContentsChangedListener;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index d68fe8e..1e471e5 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -247,8 +247,7 @@
                                            FrameTracer::FrameEvent::POST);
     mCurrentState.desiredPresentTime = desiredPresentTime;
 
-    const bool isHDR = mCurrentState.hdrMetadata.validTypes != 0;
-    mFlinger->mScheduler->recordLayerHistory(this, desiredPresentTime, isHDR);
+    mFlinger->mScheduler->recordLayerHistory(this, desiredPresentTime);
 
     return true;
 }
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 738a2a4..78f8104 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -20,6 +20,7 @@
         "liblayers_proto",
         "liblog",
         "libnativewindow",
+        "libprotobuf-cpp-lite",
         "libsync",
         "libtimestats_proto",
         "libui",
@@ -28,6 +29,7 @@
     static_libs: [
         "libmath",
         "librenderengine",
+        "libtimestats",
         "libtrace_proto",
     ],
     header_libs: [
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
index 8687d0c..e3650f3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
@@ -16,10 +16,11 @@
 
 #pragma once
 
-#include <memory>
-
+#include <TimeStats/TimeStats.h>
 #include <utils/Timers.h>
 
+#include <memory>
+
 namespace android {
 
 class HWComposer;
@@ -55,6 +56,9 @@
     virtual renderengine::RenderEngine& getRenderEngine() const = 0;
     virtual void setRenderEngine(std::unique_ptr<renderengine::RenderEngine>) = 0;
 
+    virtual TimeStats& getTimeStats() const = 0;
+    virtual void setTimeStats(const std::shared_ptr<TimeStats>&) = 0;
+
     virtual bool needsAnotherUpdate() const = 0;
     virtual nsecs_t getLastFrameRefreshTimestamp() const = 0;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
index 0e67acf..6559ed8 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
@@ -45,19 +45,19 @@
     // prepareFrame is called after the composition configuration is known but
     // before composition takes place. The DisplaySurface can use the
     // composition type to decide how to manage the flow of buffers between
-    // GLES and HWC for this frame.
+    // GPU and HWC for this frame.
     enum CompositionType {
         COMPOSITION_UNKNOWN = 0,
-        COMPOSITION_GLES = 1,
+        COMPOSITION_GPU = 1,
         COMPOSITION_HWC = 2,
-        COMPOSITION_MIXED = COMPOSITION_GLES | COMPOSITION_HWC
+        COMPOSITION_MIXED = COMPOSITION_GPU | COMPOSITION_HWC
     };
     virtual status_t prepareFrame(CompositionType compositionType) = 0;
 
-    // Inform the surface that GLES composition is complete for this frame, and
+    // Inform the surface that GPU composition is complete for this frame, and
     // the surface should make sure that HWComposer has the correct buffer for
     // this frame. Some implementations may only push a new buffer to
-    // HWComposer if GLES composition took place, others need to push a new
+    // HWComposer if GPU composition took place, others need to push a new
     // buffer on every frame.
     //
     // advanceFrame must be followed by a call to  onFrameCommitted before
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index e585769..e432b1c 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <optional>
+#include <ostream>
 #include <unordered_set>
 
 #include <renderengine/LayerSettings.h>
@@ -93,6 +94,13 @@
     virtual std::optional<renderengine::LayerSettings> prepareClientComposition(
             ClientCompositionTargetSettings&) = 0;
 
+    // Returns the LayerSettings used to draw shadows around a layer. It is passed
+    // to RenderEngine::drawLayers. Returns nullopt_t if the layer does not render
+    // shadows.
+    virtual std::optional<renderengine::LayerSettings> prepareShadowClientComposition(
+            const renderengine::LayerSettings& layerSettings, const Rect& displayViewport,
+            ui::Dataspace outputDataspace) = 0;
+
     // Called after the layer is displayed to update the presentation fence
     virtual void onLayerDisplayed(const sp<Fence>&) = 0;
 
@@ -108,5 +116,29 @@
 
 using LayerFESet = std::unordered_set<sp<LayerFE>, LayerFESpHash>;
 
+static inline bool operator==(const LayerFE::ClientCompositionTargetSettings& lhs,
+                              const LayerFE::ClientCompositionTargetSettings& rhs) {
+    return lhs.clip.hasSameRects(rhs.clip) &&
+            lhs.useIdentityTransform == rhs.useIdentityTransform &&
+            lhs.needsFiltering == rhs.needsFiltering && lhs.isSecure == rhs.isSecure &&
+            lhs.supportsProtectedContent == rhs.supportsProtectedContent &&
+            lhs.clearRegion.hasSameRects(rhs.clearRegion);
+}
+
+// Defining PrintTo helps with Google Tests.
+static inline void PrintTo(const LayerFE::ClientCompositionTargetSettings& settings,
+                           ::std::ostream* os) {
+    *os << "ClientCompositionTargetSettings{";
+    *os << "\n    .clip = \n";
+    PrintTo(settings.clip, os);
+    *os << "\n    .useIdentityTransform = " << settings.useIdentityTransform;
+    *os << "\n    .needsFiltering = " << settings.needsFiltering;
+    *os << "\n    .isSecure = " << settings.isSecure;
+    *os << "\n    .supportsProtectedContent = " << settings.supportsProtectedContent;
+    *os << "\n    .clearRegion = ";
+    PrintTo(settings.clearRegion, os);
+    *os << "\n}";
+}
+
 } // namespace compositionengine
 } // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index 5f42ea1..076fdad 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -272,7 +272,7 @@
     virtual bool getSkipColorTransform() const = 0;
     virtual FrameFences presentAndGetFrameFences() = 0;
     virtual std::vector<renderengine::LayerSettings> generateClientCompositionRequests(
-            bool supportsProtectedContent, Region& clearRegion) = 0;
+            bool supportsProtectedContent, Region& clearRegion, ui::Dataspace outputDataspace) = 0;
     virtual void appendRegionFlashRequests(
             const Region& flashRegion,
             std::vector<renderengine::LayerSettings>& clientCompositionLayers) = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
index f416c9c..450b9ca 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
@@ -36,6 +36,9 @@
     renderengine::RenderEngine& getRenderEngine() const override;
     void setRenderEngine(std::unique_ptr<renderengine::RenderEngine>) override;
 
+    TimeStats& getTimeStats() const override;
+    void setTimeStats(const std::shared_ptr<TimeStats>&) override;
+
     bool needsAnotherUpdate() const override;
     nsecs_t getLastFrameRefreshTimestamp() const override;
 
@@ -56,6 +59,7 @@
 private:
     std::unique_ptr<HWComposer> mHwComposer;
     std::unique_ptr<renderengine::RenderEngine> mRenderEngine;
+    std::shared_ptr<TimeStats> mTimeStats;
     bool mNeedsAnotherUpdate = false;
     nsecs_t mRefreshStartTime = 0;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index a2342ae..159e928 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -97,7 +97,8 @@
     bool getSkipColorTransform() const override;
     compositionengine::Output::FrameFences presentAndGetFrameFences() override;
     std::vector<renderengine::LayerSettings> generateClientCompositionRequests(
-            bool supportsProtectedContent, Region& clearRegion) override;
+            bool supportsProtectedContent, Region& clearRegion,
+            ui::Dataspace outputDataspace) override;
     void appendRegionFlashRequests(const Region&,
                                    std::vector<renderengine::LayerSettings>&) override;
     void setExpensiveRenderingExpected(bool enabled) override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
index 8e6f2e2..104e20d 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
@@ -41,6 +41,9 @@
     MOCK_CONST_METHOD0(getRenderEngine, renderengine::RenderEngine&());
     MOCK_METHOD1(setRenderEngine, void(std::unique_ptr<renderengine::RenderEngine>));
 
+    MOCK_CONST_METHOD0(getTimeStats, TimeStats&());
+    MOCK_METHOD1(setTimeStats, void(const std::shared_ptr<TimeStats>&));
+
     MOCK_CONST_METHOD0(needsAnotherUpdate, bool());
     MOCK_CONST_METHOD0(getLastFrameRefreshTimestamp, nsecs_t());
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 3eada3c..739490f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -38,6 +38,10 @@
     MOCK_METHOD1(prepareClientComposition,
                  std::optional<renderengine::LayerSettings>(
                          compositionengine::LayerFE::ClientCompositionTargetSettings&));
+    MOCK_METHOD3(prepareShadowClientComposition,
+                 std::optional<renderengine::LayerSettings>(const renderengine::LayerSettings&,
+                                                            const Rect&, ui::Dataspace));
+
     MOCK_METHOD1(onLayerDisplayed, void(const sp<Fence>&));
 
     MOCK_CONST_METHOD0(getDebugName, const char*());
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 02e68fc..7f5bd06 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -106,8 +106,8 @@
     MOCK_METHOD0(postFramebuffer, void());
     MOCK_METHOD0(presentAndGetFrameFences, compositionengine::Output::FrameFences());
 
-    MOCK_METHOD2(generateClientCompositionRequests,
-                 std::vector<renderengine::LayerSettings>(bool, Region&));
+    MOCK_METHOD3(generateClientCompositionRequests,
+                 std::vector<renderengine::LayerSettings>(bool, Region&, ui::Dataspace));
     MOCK_METHOD2(appendRegionFlashRequests,
                  void(const Region&, std::vector<renderengine::LayerSettings>&));
     MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index be8646c..5eabecd 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -64,6 +64,14 @@
     mRenderEngine = std::move(renderEngine);
 }
 
+TimeStats& CompositionEngine::getTimeStats() const {
+    return *mTimeStats.get();
+}
+
+void CompositionEngine::setTimeStats(const std::shared_ptr<TimeStats>& timeStats) {
+    mTimeStats = timeStats;
+}
+
 bool CompositionEngine::needsAnotherUpdate() const {
     return mNeedsAnotherUpdate;
 }
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 6877f8b..7e5a720 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -799,7 +799,8 @@
     // Generate the client composition requests for the layers on this output.
     std::vector<renderengine::LayerSettings> clientCompositionLayers =
             generateClientCompositionRequests(supportsProtectedContent,
-                                              clientCompositionDisplay.clearRegion);
+                                              clientCompositionDisplay.clearRegion,
+                                              clientCompositionDisplay.outputDataspace);
     appendRegionFlashRequests(debugRegion, clientCompositionLayers);
 
     // If we the display is secure, protected content support is enabled, and at
@@ -838,9 +839,18 @@
         setExpensiveRenderingExpected(true);
     }
 
+    const nsecs_t renderEngineStart = systemTime();
     renderEngine.drawLayers(clientCompositionDisplay, clientCompositionLayers,
                             buf->getNativeBuffer(), /*useFramebufferCache=*/true, std::move(fd),
                             &readyFence);
+    auto& timeStats = getCompositionEngine().getTimeStats();
+    if (readyFence.get() < 0) {
+        timeStats.recordRenderEngineDuration(renderEngineStart, systemTime());
+    } else {
+        timeStats.recordRenderEngineDuration(renderEngineStart,
+                                             std::make_shared<FenceTime>(
+                                                     new Fence(dup(readyFence.get()))));
+    }
 
     if (expensiveRenderingExpected) {
         setExpensiveRenderingExpected(false);
@@ -850,7 +860,7 @@
 }
 
 std::vector<renderengine::LayerSettings> Output::generateClientCompositionRequests(
-        bool supportsProtectedContent, Region& clearRegion) {
+        bool supportsProtectedContent, Region& clearRegion, ui::Dataspace outputDataspace) {
     std::vector<renderengine::LayerSettings> clientCompositionLayers;
     ALOGV("Rendering client layers");
 
@@ -902,6 +912,13 @@
                     layerSettings.source.solidColor = half3(0.0, 0.0, 0.0);
                     layerSettings.alpha = half(0.0);
                     layerSettings.disableBlending = true;
+                } else {
+                    std::optional<renderengine::LayerSettings> shadowLayer =
+                            layerFE.prepareShadowClientComposition(*result, outputState.viewport,
+                                                                   outputDataspace);
+                    if (shadowLayer) {
+                        clientCompositionLayers.push_back(*shadowLayer);
+                    }
                 }
 
                 layer->editState().clientCompositionTimestamp = systemTime();
diff --git a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
index 5ed21fc..949feb4 100644
--- a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
@@ -117,12 +117,12 @@
     if (usesClientComposition && usesDeviceComposition) {
         compositionType = DisplaySurface::COMPOSITION_MIXED;
     } else if (usesClientComposition) {
-        compositionType = DisplaySurface::COMPOSITION_GLES;
+        compositionType = DisplaySurface::COMPOSITION_GPU;
     } else if (usesDeviceComposition) {
         compositionType = DisplaySurface::COMPOSITION_HWC;
     } else {
         // Nothing to do -- when turning the screen off we get a frame like
-        // this. Call it a HWC frame since we won't be doing any GLES work but
+        // this. Call it a HWC frame since we won't be doing any GPU work but
         // will do a prepare/set cycle.
         compositionType = DisplaySurface::COMPOSITION_HWC;
     }
diff --git a/services/surfaceflinger/CompositionEngine/tests/CallOrderStateMachineHelper.h b/services/surfaceflinger/CompositionEngine/tests/CallOrderStateMachineHelper.h
new file mode 100644
index 0000000..2675dcf
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/tests/CallOrderStateMachineHelper.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/**
+ * CallOrderStateMachineHelper is a helper class for setting up a compile-time
+ * checked state machine that a sequence of calls is correct for completely
+ * setting up the state for some other type.
+ *
+ * Two examples where this could be used are with setting up a "Builder" flow
+ * for initializing an instance of some type, and writing tests where the state
+ * machine sets up expectations and preconditions, calls the function under
+ * test, and then evaluations postconditions.
+ *
+ * The purpose of this helper is to offload some of the boilerplate code to
+ * simplify the actual state classes, and is also a place to document how to
+ * go about setting up the state classes.
+ *
+ * To work at compile time, the idea is that each state is a unique C++ type,
+ * and the valid transitions between states are given by member functions on
+ * those types, with those functions returning a simple value type expressing
+ * the new state to use. Illegal state transitions become a compile error because
+ * a named member function does not exist.
+ *
+ * Example usage in a test:
+ *
+ *    A two step (+ terminator step) setup process can defined using:
+ *
+ *        class Step1 : public CallOrderStateMachineHelper<TestFixtureType, Step1> {
+ *           [[nodiscard]] auto firstMockCalledWith(int value1) {
+ *              // Set up an expectation or initial state using the fixture
+ *              EXPECT_CALL(getInstance->firstMock, FirstCall(value1));
+ *              return nextState<Step2>();
+ *           }
+ *        };
+ *
+ *        class Step2 : public CallOrderStateMachineHelper<TestFixtureType, Step2> {
+ *           [[nodiscard]] auto secondMockCalledWith(int value2) {
+ *              // Set up an expectation or initial state using the fixture
+ *              EXPECT_CALL(getInstance()->secondMock, SecondCall(value2));
+ *              return nextState<StepExecute>();
+ *           }
+ *        };
+ *
+ *        class StepExecute : public CallOrderStateMachineHelper<TestFixtureType, Step3> {
+ *           void execute() {
+ *              invokeFunctionUnderTest();
+ *           }
+ *        };
+ *
+ *    Note how the non-terminator steps return by value and use [[nodiscard]] to
+ *    enforce the setup flow. Only the terminator step returns void.
+ *
+ *    This can then be used in the tests with:
+ *
+ *        Step1::make(this).firstMockCalledWith(value1)
+ *                .secondMockCalledWith(value2)
+ *                .execute);
+ *
+ *    If the test fixture defines a `verify()` helper function which returns
+ *    `Step1::make(this)`, this can be simplified to:
+ *
+ *        verify().firstMockCalledWith(value1)
+ *                .secondMockCalledWith(value2)
+ *                .execute();
+ *
+ *    This is equivalent to the following calls made by the text function:
+ *
+ *        EXPECT_CALL(firstMock, FirstCall(value1));
+ *        EXPECT_CALL(secondMock, SecondCall(value2));
+ *        invokeFunctionUnderTest();
+ */
+template <typename InstanceType, typename CurrentStateType>
+class CallOrderStateMachineHelper {
+public:
+    CallOrderStateMachineHelper() = default;
+
+    // Disallow copying
+    CallOrderStateMachineHelper(const CallOrderStateMachineHelper&) = delete;
+    CallOrderStateMachineHelper& operator=(const CallOrderStateMachineHelper&) = delete;
+
+    // Moving is intended use case.
+    CallOrderStateMachineHelper(CallOrderStateMachineHelper&&) = default;
+    CallOrderStateMachineHelper& operator=(CallOrderStateMachineHelper&&) = default;
+
+    // Using a static "Make" function means the CurrentStateType classes do not
+    // need anything other than a default no-argument constructor.
+    static CurrentStateType make(InstanceType* instance) {
+        auto helper = CurrentStateType();
+        helper.mInstance = instance;
+        return helper;
+    }
+
+    // Each non-terminal state function
+    template <typename NextStateType>
+    auto nextState() {
+        // Note: Further operations on the current state become undefined
+        // operations as the instance pointer is moved to the next state type.
+        // But that doesn't stop someone from storing an intermediate state
+        // instance as a local and possibly calling one than one member function
+        // on it. By swapping with nullptr, we at least can try to catch this
+        // this at runtime.
+        InstanceType* instance = nullptr;
+        std::swap(instance, mInstance);
+        return NextStateType::make(instance);
+    }
+
+    InstanceType* getInstance() const { return mInstance; }
+
+private:
+    InstanceType* mInstance;
+};
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index 49e7c70..989494d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -25,6 +25,7 @@
 #include <renderengine/mock/RenderEngine.h>
 
 #include "MockHWComposer.h"
+#include "TimeStats/TimeStats.h"
 
 namespace android::compositionengine {
 namespace {
@@ -41,6 +42,8 @@
     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;
     CompositionRefreshArgs mRefreshArgs;
 
@@ -71,6 +74,12 @@
     EXPECT_EQ(mRenderEngine, &mEngine.getRenderEngine());
 }
 
+TEST_F(CompositionEngineTest, canSetTimeStats) {
+    mEngine.setTimeStats(mTimeStats);
+
+    EXPECT_EQ(mTimeStats.get(), &mEngine.getTimeStats());
+}
+
 /*
  * CompositionEngine::present
  */
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp
index c07dfbb..21b9aa9 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp
@@ -19,35 +19,6 @@
 #include <compositionengine/mock/CompositionEngine.h>
 #include <gtest/gtest.h>
 
-namespace android::hardware::graphics::common::V1_1 {
-
-// Note: These operator overloads need to be defined in the same namespace as
-// the values they print.
-
-std::ostream& operator<<(std::ostream& os, const RenderIntent& value) {
-    return os << toString(value) << " (" << static_cast<std::underlying_type_t<Dataspace>>(value)
-              << ")";
-}
-
-} // namespace android::hardware::graphics::common::V1_1
-
-namespace android::hardware::graphics::common::V1_2 {
-
-// Note: These operator overloads need to be defined in the same namespace as
-// the values they print.
-
-std::ostream& operator<<(std::ostream& os, const Dataspace& value) {
-    return os << toString(value) << " (" << static_cast<std::underlying_type_t<Dataspace>>(value)
-              << ")";
-}
-
-std::ostream& operator<<(std::ostream& os, const ColorMode& value) {
-    return os << toString(value) << " (" << static_cast<std::underlying_type_t<Dataspace>>(value)
-              << ")";
-}
-
-} // namespace android::hardware::graphics::common::V1_2
-
 namespace android::compositionengine {
 namespace {
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 4d71d43..3a4df74 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -21,8 +21,10 @@
 #include <compositionengine/DisplaySurface.h>
 #include <compositionengine/RenderSurfaceCreationArgs.h>
 #include <compositionengine/impl/Display.h>
+#include <compositionengine/impl/RenderSurface.h>
 #include <compositionengine/mock/CompositionEngine.h>
 #include <compositionengine/mock/DisplayColorProfile.h>
+#include <compositionengine/mock/DisplaySurface.h>
 #include <compositionengine/mock/Layer.h>
 #include <compositionengine/mock/LayerFE.h>
 #include <compositionengine/mock/NativeWindow.h>
@@ -39,6 +41,8 @@
 
 using testing::_;
 using testing::DoAll;
+using testing::InSequence;
+using testing::NiceMock;
 using testing::Return;
 using testing::ReturnRef;
 using testing::Sequence;
@@ -46,6 +50,8 @@
 using testing::StrictMock;
 
 constexpr DisplayId DEFAULT_DISPLAY_ID = DisplayId{42};
+constexpr int32_t DEFAULT_DISPLAY_WIDTH = 1920;
+constexpr int32_t DEFAULT_DISPLAY_HEIGHT = 1080;
 
 struct DisplayTest : public testing::Test {
     class Display : public impl::Display {
@@ -768,5 +774,59 @@
     nonHwcDisplay->finishFrame(refreshArgs);
 }
 
+/*
+ * Display functional tests
+ */
+
+struct DisplayFunctionalTest : public testing::Test {
+    class Display : public impl::Display {
+    public:
+        explicit Display(const compositionengine::DisplayCreationArgs& args)
+              : impl::Display(args) {}
+
+        using impl::Display::injectOutputLayerForTest;
+        virtual void injectOutputLayerForTest(std::unique_ptr<compositionengine::OutputLayer>) = 0;
+    };
+
+    static std::shared_ptr<Display> createDisplay(
+            const compositionengine::CompositionEngine& compositionEngine,
+            compositionengine::DisplayCreationArgs&& args) {
+        return impl::createDisplayTemplated<Display>(compositionEngine, args);
+    }
+
+    DisplayFunctionalTest() {
+        EXPECT_CALL(mCompositionEngine, getHwComposer()).WillRepeatedly(ReturnRef(mHwComposer));
+
+        mDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+    }
+
+    NiceMock<android::mock::HWComposer> mHwComposer;
+    NiceMock<Hwc2::mock::PowerAdvisor> mPowerAdvisor;
+    NiceMock<mock::CompositionEngine> mCompositionEngine;
+    sp<mock::NativeWindow> mNativeWindow = new NiceMock<mock::NativeWindow>();
+    sp<mock::DisplaySurface> mDisplaySurface = new NiceMock<mock::DisplaySurface>();
+    std::shared_ptr<Display> mDisplay = createDisplay(mCompositionEngine,
+                                                      DisplayCreationArgsBuilder()
+                                                              .setDisplayId(DEFAULT_DISPLAY_ID)
+                                                              .setPowerAdvisor(&mPowerAdvisor)
+                                                              .build());
+    impl::RenderSurface* mRenderSurface =
+            new impl::RenderSurface{mCompositionEngine, *mDisplay,
+                                    RenderSurfaceCreationArgs{DEFAULT_DISPLAY_WIDTH,
+                                                              DEFAULT_DISPLAY_HEIGHT, mNativeWindow,
+                                                              mDisplaySurface}};
+};
+
+TEST_F(DisplayFunctionalTest, postFramebufferCriticalCallsAreOrdered) {
+    InSequence seq;
+
+    mDisplay->editState().isEnabled = true;
+
+    EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_));
+    EXPECT_CALL(*mDisplaySurface, onFrameCommitted());
+
+    mDisplay->postFramebuffer();
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/FloatRectMatcher.h b/services/surfaceflinger/CompositionEngine/tests/FloatRectMatcher.h
deleted file mode 100644
index 6741cc9..0000000
--- a/services/surfaceflinger/CompositionEngine/tests/FloatRectMatcher.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <string>
-
-#include <android-base/stringprintf.h>
-#include <gmock/gmock.h>
-
-namespace {
-
-using android::base::StringAppendF;
-using FloatRect = android::FloatRect;
-
-void dumpFloatRect(const FloatRect& rect, std::string& result, const char* name) {
-    StringAppendF(&result, "%s (%f %f %f %f) ", name, rect.left, rect.top, rect.right, rect.bottom);
-}
-
-// Checks for a region match
-MATCHER_P(FloatRectEq, expected, "") {
-    std::string buf;
-    buf.append("FloatRects are not equal\n");
-    dumpFloatRect(expected, buf, "expected rect");
-    dumpFloatRect(arg, buf, "actual rect");
-    *result_listener << buf;
-
-    const float TOLERANCE = 1e-3f;
-    return (std::fabs(expected.left - arg.left) < TOLERANCE) &&
-            (std::fabs(expected.top - arg.top) < TOLERANCE) &&
-            (std::fabs(expected.right - arg.right) < TOLERANCE) &&
-            (std::fabs(expected.bottom - arg.bottom) < TOLERANCE);
-}
-
-} // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 5cfec77..364661b 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -81,6 +81,11 @@
     MOCK_CONST_METHOD1(getColorModes, std::vector<ui::ColorMode>(DisplayId));
     MOCK_METHOD3(setActiveColorMode, status_t(DisplayId, ui::ColorMode, ui::RenderIntent));
     MOCK_CONST_METHOD0(isUsingVrComposer, bool());
+    MOCK_CONST_METHOD1(isVsyncPeriodSwitchSupported, bool(DisplayId));
+    MOCK_CONST_METHOD1(getDisplayVsyncPeriod, nsecs_t(DisplayId));
+    MOCK_METHOD4(setActiveConfigWithConstraints,
+                 status_t(DisplayId, size_t, const HWC2::VsyncPeriodChangeConstraints&,
+                          HWC2::VsyncPeriodChangeTimeline*));
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
     MOCK_CONST_METHOD0(getComposer, android::Hwc2::Composer*());
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 88acd04..2e030a1 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -23,10 +23,8 @@
 #include <compositionengine/mock/Output.h>
 #include <gtest/gtest.h>
 
-#include "FloatRectMatcher.h"
 #include "MockHWC2.h"
 #include "MockHWComposer.h"
-#include "RectMatcher.h"
 #include "RegionMatcher.h"
 
 namespace android::compositionengine {
@@ -159,19 +157,19 @@
     mLayerFEState.geomUsesSourceCrop = false;
 
     const FloatRect expected{};
-    EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(expected));
+    EXPECT_THAT(calculateOutputSourceCrop(), expected);
 }
 
 TEST_F(OutputLayerSourceCropTest, correctForSimpleDefaultCase) {
     const FloatRect expected{0.f, 0.f, 1920.f, 1080.f};
-    EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(expected));
+    EXPECT_THAT(calculateOutputSourceCrop(), expected);
 }
 
 TEST_F(OutputLayerSourceCropTest, handlesBoundsOutsideViewport) {
     mLayerFEState.geomLayerBounds = FloatRect{-2000.f, -2000.f, 2000.f, 2000.f};
 
     const FloatRect expected{0.f, 0.f, 1920.f, 1080.f};
-    EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(expected));
+    EXPECT_THAT(calculateOutputSourceCrop(), expected);
 }
 
 TEST_F(OutputLayerSourceCropTest, handlesBoundsOutsideViewportRotated) {
@@ -179,7 +177,7 @@
     mLayerFEState.geomLayerTransform.set(HAL_TRANSFORM_ROT_90, 1920, 1080);
 
     const FloatRect expected{0.f, 0.f, 1080.f, 1080.f};
-    EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(expected));
+    EXPECT_THAT(calculateOutputSourceCrop(), expected);
 }
 
 TEST_F(OutputLayerSourceCropTest, calculateOutputSourceCropWorksWithATransformedBuffer) {
@@ -218,7 +216,7 @@
         mLayerFEState.geomBufferTransform = entry.buffer;
         mOutputState.orientation = entry.display;
 
-        EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(entry.expected)) << "entry " << i;
+        EXPECT_THAT(calculateOutputSourceCrop(), entry.expected) << "entry " << i;
     }
 }
 
@@ -226,14 +224,14 @@
     mLayerFEState.geomContentCrop = Rect{0, 0, 960, 540};
 
     const FloatRect expected{0.f, 0.f, 960.f, 540.f};
-    EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(expected));
+    EXPECT_THAT(calculateOutputSourceCrop(), expected);
 }
 
 TEST_F(OutputLayerSourceCropTest, viewportAffectsCrop) {
     mOutputState.viewport = Rect{0, 0, 960, 540};
 
     const FloatRect expected{0.f, 0.f, 960.f, 540.f};
-    EXPECT_THAT(calculateOutputSourceCrop(), FloatRectEq(expected));
+    EXPECT_THAT(calculateOutputSourceCrop(), expected);
 }
 
 /*
@@ -265,50 +263,50 @@
 
 TEST_F(OutputLayerDisplayFrameTest, correctForSimpleDefaultCase) {
     const Rect expected{0, 0, 1920, 1080};
-    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+    EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
 TEST_F(OutputLayerDisplayFrameTest, fullActiveTransparentRegionReturnsEmptyFrame) {
     mLayerFEState.transparentRegionHint = Region{Rect{0, 0, 1920, 1080}};
     const Rect expected{0, 0, 0, 0};
-    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+    EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
 TEST_F(OutputLayerDisplayFrameTest, cropAffectsDisplayFrame) {
     mLayerFEState.geomCrop = Rect{100, 200, 300, 500};
     const Rect expected{100, 200, 300, 500};
-    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+    EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
 TEST_F(OutputLayerDisplayFrameTest, cropAffectsDisplayFrameRotated) {
     mLayerFEState.geomCrop = Rect{100, 200, 300, 500};
     mLayerFEState.geomLayerTransform.set(HAL_TRANSFORM_ROT_90, 1920, 1080);
     const Rect expected{1420, 100, 1720, 300};
-    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+    EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
 TEST_F(OutputLayerDisplayFrameTest, emptyGeomCropIsNotUsedToComputeFrame) {
     mLayerFEState.geomCrop = Rect{};
     const Rect expected{0, 0, 1920, 1080};
-    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+    EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
 TEST_F(OutputLayerDisplayFrameTest, geomLayerBoundsAffectsFrame) {
     mLayerFEState.geomLayerBounds = FloatRect{0.f, 0.f, 960.f, 540.f};
     const Rect expected{0, 0, 960, 540};
-    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+    EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
 TEST_F(OutputLayerDisplayFrameTest, viewportAffectsFrame) {
     mOutputState.viewport = Rect{0, 0, 960, 540};
     const Rect expected{0, 0, 960, 540};
-    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+    EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
 TEST_F(OutputLayerDisplayFrameTest, outputTransformAffectsDisplayFrame) {
     mOutputState.transform = ui::Transform{HAL_TRANSFORM_ROT_90};
     const Rect expected{-1080, 0, 0, 1920};
-    EXPECT_THAT(calculateOutputDisplayFrame(), RectEq(expected));
+    EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
 /*
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 09ddc60..87beb0d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -16,6 +16,7 @@
 
 #include <cmath>
 
+#include <android-base/stringprintf.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/impl/Output.h>
 #include <compositionengine/impl/OutputCompositionState.h>
@@ -31,17 +32,28 @@
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
+#include "CallOrderStateMachineHelper.h"
+#include "MockHWC2.h"
 #include "RegionMatcher.h"
-#include "TransformMatcher.h"
 
 namespace android::compositionengine {
 namespace {
 
 using testing::_;
+using testing::ByMove;
+using testing::ByRef;
+using testing::DoAll;
+using testing::ElementsAreArray;
+using testing::Eq;
 using testing::InSequence;
+using testing::Invoke;
+using testing::IsEmpty;
+using testing::Mock;
+using testing::Property;
 using testing::Ref;
 using testing::Return;
 using testing::ReturnRef;
+using testing::SetArgPointee;
 using testing::StrictMock;
 
 constexpr auto TR_IDENT = 0u;
@@ -51,6 +63,9 @@
 const mat4 kNonIdentityHalf = mat4() * 0.5;
 const mat4 kNonIdentityQuarter = mat4() * 0.25;
 
+constexpr OutputColorSetting kVendorSpecifiedOutputColorSetting =
+        static_cast<OutputColorSetting>(0x100);
+
 struct OutputPartialMockBase : public impl::Output {
     // compositionengine::Output overrides
     const OutputCompositionState& getState() const override { return mState; }
@@ -104,8 +119,70 @@
     std::shared_ptr<Output> mOutput = createOutput(mCompositionEngine);
 };
 
+// Extension of the base test useful for checking interactions with the LayerFE
+// functions to latch composition state.
+struct OutputLatchFEStateTest : public OutputTest {
+    OutputLatchFEStateTest() {
+        EXPECT_CALL(*mOutputLayer1, getLayer()).WillRepeatedly(ReturnRef(mLayer1));
+        EXPECT_CALL(*mOutputLayer2, getLayer()).WillRepeatedly(ReturnRef(mLayer2));
+        EXPECT_CALL(*mOutputLayer3, getLayer()).WillRepeatedly(ReturnRef(mLayer3));
+
+        EXPECT_CALL(*mOutputLayer1, getLayerFE()).WillRepeatedly(ReturnRef(mLayer1FE));
+        EXPECT_CALL(*mOutputLayer2, getLayerFE()).WillRepeatedly(ReturnRef(mLayer2FE));
+        EXPECT_CALL(*mOutputLayer3, getLayerFE()).WillRepeatedly(ReturnRef(mLayer3FE));
+
+        EXPECT_CALL(mLayer1, editFEState()).WillRepeatedly(ReturnRef(mLayer1FEState));
+        EXPECT_CALL(mLayer2, editFEState()).WillRepeatedly(ReturnRef(mLayer2FEState));
+        EXPECT_CALL(mLayer3, editFEState()).WillRepeatedly(ReturnRef(mLayer3FEState));
+    }
+
+    void injectLayer(std::unique_ptr<mock::OutputLayer> layer) {
+        mOutput->injectOutputLayerForTest(std::unique_ptr<OutputLayer>(layer.release()));
+    }
+
+    std::unique_ptr<mock::OutputLayer> mOutputLayer1{new StrictMock<mock::OutputLayer>};
+    std::unique_ptr<mock::OutputLayer> mOutputLayer2{new StrictMock<mock::OutputLayer>};
+    std::unique_ptr<mock::OutputLayer> mOutputLayer3{new StrictMock<mock::OutputLayer>};
+
+    StrictMock<mock::Layer> mLayer1;
+    StrictMock<mock::Layer> mLayer2;
+    StrictMock<mock::Layer> mLayer3;
+
+    StrictMock<mock::LayerFE> mLayer1FE;
+    StrictMock<mock::LayerFE> mLayer2FE;
+    StrictMock<mock::LayerFE> mLayer3FE;
+
+    LayerFECompositionState mLayer1FEState;
+    LayerFECompositionState mLayer2FEState;
+    LayerFECompositionState mLayer3FEState;
+};
+
 const Rect OutputTest::kDefaultDisplaySize{100, 200};
 
+using ColorProfile = compositionengine::Output::ColorProfile;
+
+void dumpColorProfile(ColorProfile profile, std::string& result, const char* name) {
+    android::base::StringAppendF(&result, "%s (%s[%d] %s[%d] %s[%d] %s[%d]) ", name,
+                                 toString(profile.mode).c_str(), profile.mode,
+                                 toString(profile.dataspace).c_str(), profile.dataspace,
+                                 toString(profile.renderIntent).c_str(), profile.renderIntent,
+                                 toString(profile.colorSpaceAgnosticDataspace).c_str(),
+                                 profile.colorSpaceAgnosticDataspace);
+}
+
+// Checks for a ColorProfile match
+MATCHER_P(ColorProfileEq, expected, "") {
+    std::string buf;
+    buf.append("ColorProfiles are not equal\n");
+    dumpColorProfile(expected, buf, "expected value");
+    dumpColorProfile(arg, buf, "actual value");
+    *result_listener << buf;
+
+    return (expected.mode == arg.mode) && (expected.dataspace == arg.dataspace) &&
+            (expected.renderIntent == arg.renderIntent) &&
+            (expected.colorSpaceAgnosticDataspace == arg.colorSpaceAgnosticDataspace);
+}
+
 /*
  * Basic construction
  */
@@ -170,7 +247,7 @@
 
     mOutput->setProjection(transform, orientation, frame, viewport, scissor, needsFiltering);
 
-    EXPECT_THAT(mOutput->getState().transform, TransformEq(transform));
+    EXPECT_THAT(mOutput->getState().transform, transform);
     EXPECT_EQ(orientation, mOutput->getState().orientation);
     EXPECT_EQ(frame, mOutput->getState().frame);
     EXPECT_EQ(viewport, mOutput->getState().viewport);
@@ -295,10 +372,12 @@
 }
 
 /*
- * Output::setColorMode
+ * Output::setColorProfile
  */
 
-TEST_F(OutputTest, setColorModeSetsStateAndDirtiesOutputIfChanged) {
+using OutputSetColorProfileTest = OutputTest;
+
+TEST_F(OutputSetColorProfileTest, setsStateAndDirtiesOutputIfChanged) {
     using ColorProfile = Output::ColorProfile;
 
     EXPECT_CALL(*mDisplayColorProfile,
@@ -319,7 +398,7 @@
     EXPECT_THAT(mOutput->getState().dirtyRegion, RegionEq(Region(kDefaultDisplaySize)));
 }
 
-TEST_F(OutputTest, setColorModeDoesNothingIfNoChange) {
+TEST_F(OutputSetColorProfileTest, doesNothingIfNoChange) {
     using ColorProfile = Output::ColorProfile;
 
     EXPECT_CALL(*mDisplayColorProfile,
@@ -535,26 +614,138 @@
 }
 
 /*
+ * Output::updateLayerStateFromFE()
+ */
+
+using OutputUpdateLayerStateFromFETest = OutputLatchFEStateTest;
+
+TEST_F(OutputUpdateLayerStateFromFETest, handlesNoOutputLayerCase) {
+    CompositionRefreshArgs refreshArgs;
+
+    mOutput->updateLayerStateFromFE(refreshArgs);
+}
+
+TEST_F(OutputUpdateLayerStateFromFETest, latchesContentStateForAllContainedLayers) {
+    EXPECT_CALL(mLayer1FE,
+                latchCompositionState(Ref(mLayer1FEState), LayerFE::StateSubset::Content));
+    EXPECT_CALL(mLayer2FE,
+                latchCompositionState(Ref(mLayer2FEState), LayerFE::StateSubset::Content));
+    EXPECT_CALL(mLayer3FE,
+                latchCompositionState(Ref(mLayer3FEState), LayerFE::StateSubset::Content));
+
+    // Note: Must be performed after any expectations on these mocks
+    injectLayer(std::move(mOutputLayer1));
+    injectLayer(std::move(mOutputLayer2));
+    injectLayer(std::move(mOutputLayer3));
+
+    CompositionRefreshArgs refreshArgs;
+    refreshArgs.updatingGeometryThisFrame = false;
+
+    mOutput->updateLayerStateFromFE(refreshArgs);
+}
+
+TEST_F(OutputUpdateLayerStateFromFETest, latchesGeometryAndContentStateForAllContainedLayers) {
+    EXPECT_CALL(mLayer1FE,
+                latchCompositionState(Ref(mLayer1FEState),
+                                      LayerFE::StateSubset::GeometryAndContent));
+    EXPECT_CALL(mLayer2FE,
+                latchCompositionState(Ref(mLayer2FEState),
+                                      LayerFE::StateSubset::GeometryAndContent));
+    EXPECT_CALL(mLayer3FE,
+                latchCompositionState(Ref(mLayer3FEState),
+                                      LayerFE::StateSubset::GeometryAndContent));
+
+    // Note: Must be performed after any expectations on these mocks
+    injectLayer(std::move(mOutputLayer1));
+    injectLayer(std::move(mOutputLayer2));
+    injectLayer(std::move(mOutputLayer3));
+
+    CompositionRefreshArgs refreshArgs;
+    refreshArgs.updatingGeometryThisFrame = true;
+
+    mOutput->updateLayerStateFromFE(refreshArgs);
+}
+
+/*
  * Output::updateAndWriteCompositionState()
  */
 
-TEST_F(OutputTest, updateAndWriteCompositionState_takesEarlyOutIfNotEnabled) {
-    mOutput->editState().isEnabled = false;
+using OutputUpdateAndWriteCompositionStateTest = OutputLatchFEStateTest;
+
+TEST_F(OutputUpdateAndWriteCompositionStateTest, doesNothingIfLayers) {
+    mOutput->editState().isEnabled = true;
 
     CompositionRefreshArgs args;
     mOutput->updateAndWriteCompositionState(args);
 }
 
-TEST_F(OutputTest, updateAndWriteCompositionState_updatesLayers) {
-    mOutput->editState().isEnabled = true;
-    mock::OutputLayer* outputLayer = new StrictMock<mock::OutputLayer>();
-    mOutput->injectOutputLayerForTest(std::unique_ptr<OutputLayer>(outputLayer));
+TEST_F(OutputUpdateAndWriteCompositionStateTest, doesNothingIfOutputNotEnabled) {
+    mOutput->editState().isEnabled = false;
 
-    EXPECT_CALL(*outputLayer, updateCompositionState(true, true)).Times(1);
-    EXPECT_CALL(*outputLayer, writeStateToHWC(true)).Times(1);
+    injectLayer(std::move(mOutputLayer1));
+    injectLayer(std::move(mOutputLayer2));
+    injectLayer(std::move(mOutputLayer3));
+
+    CompositionRefreshArgs args;
+    mOutput->updateAndWriteCompositionState(args);
+}
+
+TEST_F(OutputUpdateAndWriteCompositionStateTest, updatesLayerContentForAllLayers) {
+    EXPECT_CALL(*mOutputLayer1, updateCompositionState(false, false));
+    EXPECT_CALL(*mOutputLayer1, writeStateToHWC(false));
+    EXPECT_CALL(*mOutputLayer2, updateCompositionState(false, false));
+    EXPECT_CALL(*mOutputLayer2, writeStateToHWC(false));
+    EXPECT_CALL(*mOutputLayer3, updateCompositionState(false, false));
+    EXPECT_CALL(*mOutputLayer3, writeStateToHWC(false));
+
+    injectLayer(std::move(mOutputLayer1));
+    injectLayer(std::move(mOutputLayer2));
+    injectLayer(std::move(mOutputLayer3));
+
+    mOutput->editState().isEnabled = true;
+
+    CompositionRefreshArgs args;
+    args.updatingGeometryThisFrame = false;
+    args.devOptForceClientComposition = false;
+    mOutput->updateAndWriteCompositionState(args);
+}
+
+TEST_F(OutputUpdateAndWriteCompositionStateTest, updatesLayerGeometryAndContentForAllLayers) {
+    EXPECT_CALL(*mOutputLayer1, updateCompositionState(true, false));
+    EXPECT_CALL(*mOutputLayer1, writeStateToHWC(true));
+    EXPECT_CALL(*mOutputLayer2, updateCompositionState(true, false));
+    EXPECT_CALL(*mOutputLayer2, writeStateToHWC(true));
+    EXPECT_CALL(*mOutputLayer3, updateCompositionState(true, false));
+    EXPECT_CALL(*mOutputLayer3, writeStateToHWC(true));
+
+    injectLayer(std::move(mOutputLayer1));
+    injectLayer(std::move(mOutputLayer2));
+    injectLayer(std::move(mOutputLayer3));
+
+    mOutput->editState().isEnabled = true;
 
     CompositionRefreshArgs args;
     args.updatingGeometryThisFrame = true;
+    args.devOptForceClientComposition = false;
+    mOutput->updateAndWriteCompositionState(args);
+}
+
+TEST_F(OutputUpdateAndWriteCompositionStateTest, forcesClientCompositionForAllLayers) {
+    EXPECT_CALL(*mOutputLayer1, updateCompositionState(false, true));
+    EXPECT_CALL(*mOutputLayer1, writeStateToHWC(false));
+    EXPECT_CALL(*mOutputLayer2, updateCompositionState(false, true));
+    EXPECT_CALL(*mOutputLayer2, writeStateToHWC(false));
+    EXPECT_CALL(*mOutputLayer3, updateCompositionState(false, true));
+    EXPECT_CALL(*mOutputLayer3, writeStateToHWC(false));
+
+    injectLayer(std::move(mOutputLayer1));
+    injectLayer(std::move(mOutputLayer2));
+    injectLayer(std::move(mOutputLayer3));
+
+    mOutput->editState().isEnabled = true;
+
+    CompositionRefreshArgs args;
+    args.updatingGeometryThisFrame = false;
     args.devOptForceClientComposition = true;
     mOutput->updateAndWriteCompositionState(args);
 }
@@ -565,8 +756,8 @@
 
 struct OutputPrepareFrameTest : public testing::Test {
     struct OutputPartialMock : public OutputPartialMockBase {
-        // Sets up the helper functions called by prepareFrame to use a mock
-        // implementations.
+        // Sets up the helper functions called by the function under test to use
+        // mock implementations.
         MOCK_METHOD0(chooseCompositionStrategy, void());
     };
 
@@ -615,14 +806,647 @@
 }
 
 /*
+ * Output::prepare()
+ */
+
+struct OutputPrepareTest : public testing::Test {
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // Sets up the helper functions called by the function under test to use
+        // mock implementations.
+        MOCK_METHOD2(rebuildLayerStacks,
+                     void(const compositionengine::CompositionRefreshArgs&,
+                          compositionengine::LayerFESet&));
+    };
+
+    StrictMock<OutputPartialMock> mOutput;
+    CompositionRefreshArgs mRefreshArgs;
+    LayerFESet mGeomSnapshots;
+};
+
+TEST_F(OutputPrepareTest, justInvokesRebuildLayerStacks) {
+    InSequence seq;
+    EXPECT_CALL(mOutput, rebuildLayerStacks(Ref(mRefreshArgs), Ref(mGeomSnapshots)));
+
+    mOutput.prepare(mRefreshArgs, mGeomSnapshots);
+}
+
+/*
+ * Output::rebuildLayerStacks()
+ */
+
+struct OutputRebuildLayerStacksTest : public testing::Test {
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // Sets up the helper functions called by the function under test to use
+        // mock implementations.
+        MOCK_METHOD2(collectVisibleLayers,
+                     void(const compositionengine::CompositionRefreshArgs&,
+                          compositionengine::Output::CoverageState&));
+    };
+
+    OutputRebuildLayerStacksTest() {
+        mOutput.mState.isEnabled = true;
+        mOutput.mState.transform = kIdentityTransform;
+        mOutput.mState.bounds = kOutputBounds;
+
+        mRefreshArgs.updatingOutputGeometryThisFrame = true;
+
+        mCoverageAboveCoveredLayersToSet = Region(Rect(0, 0, 10, 10));
+
+        EXPECT_CALL(mOutput, collectVisibleLayers(Ref(mRefreshArgs), _))
+                .WillRepeatedly(Invoke(this, &OutputRebuildLayerStacksTest::setTestCoverageValues));
+    }
+
+    void setTestCoverageValues(const CompositionRefreshArgs&,
+                               compositionengine::Output::CoverageState& state) {
+        state.aboveCoveredLayers = mCoverageAboveCoveredLayersToSet;
+        state.aboveOpaqueLayers = mCoverageAboveOpaqueLayersToSet;
+        state.dirtyRegion = mCoverageDirtyRegionToSet;
+    }
+
+    static const ui::Transform kIdentityTransform;
+    static const ui::Transform kRotate90Transform;
+    static const Rect kOutputBounds;
+
+    StrictMock<OutputPartialMock> mOutput;
+    CompositionRefreshArgs mRefreshArgs;
+    LayerFESet mGeomSnapshots;
+    Region mCoverageAboveCoveredLayersToSet;
+    Region mCoverageAboveOpaqueLayersToSet;
+    Region mCoverageDirtyRegionToSet;
+};
+
+const ui::Transform OutputRebuildLayerStacksTest::kIdentityTransform{TR_IDENT, 1920, 1080};
+const ui::Transform OutputRebuildLayerStacksTest::kRotate90Transform{TR_ROT_90, 1920, 1080};
+const Rect OutputRebuildLayerStacksTest::kOutputBounds{0, 0, 1920, 1080};
+
+TEST_F(OutputRebuildLayerStacksTest, doesNothingIfNotEnabled) {
+    mOutput.mState.isEnabled = false;
+
+    mOutput.rebuildLayerStacks(mRefreshArgs, mGeomSnapshots);
+}
+
+TEST_F(OutputRebuildLayerStacksTest, doesNothingIfNotUpdatingGeometryThisFrame) {
+    mRefreshArgs.updatingOutputGeometryThisFrame = false;
+
+    mOutput.rebuildLayerStacks(mRefreshArgs, mGeomSnapshots);
+}
+
+TEST_F(OutputRebuildLayerStacksTest, computesUndefinedRegionWithNoRotationAndFullCoverage) {
+    mOutput.mState.transform = kIdentityTransform;
+
+    mCoverageAboveOpaqueLayersToSet = Region(Rect(0, 0, 1920, 1080));
+
+    mOutput.rebuildLayerStacks(mRefreshArgs, mGeomSnapshots);
+
+    EXPECT_THAT(mOutput.mState.undefinedRegion, RegionEq(Region(Rect(0, 0, 0, 0))));
+}
+
+TEST_F(OutputRebuildLayerStacksTest, computesUndefinedRegionWithNoRotationAndPartialCoverage) {
+    mOutput.mState.transform = kIdentityTransform;
+
+    mCoverageAboveOpaqueLayersToSet = Region(Rect(0, 0, 960, 1080));
+
+    mOutput.rebuildLayerStacks(mRefreshArgs, mGeomSnapshots);
+
+    EXPECT_THAT(mOutput.mState.undefinedRegion, RegionEq(Region(Rect(960, 0, 1920, 1080))));
+}
+
+TEST_F(OutputRebuildLayerStacksTest, computesUndefinedRegionWith90RotationAndFullCoverage) {
+    mOutput.mState.transform = kRotate90Transform;
+
+    mCoverageAboveOpaqueLayersToSet = Region(Rect(0, 0, 1080, 1920));
+
+    mOutput.rebuildLayerStacks(mRefreshArgs, mGeomSnapshots);
+
+    EXPECT_THAT(mOutput.mState.undefinedRegion, RegionEq(Region(Rect(0, 0, 0, 0))));
+}
+
+TEST_F(OutputRebuildLayerStacksTest, computesUndefinedRegionWith90RotationAndPartialCoverage) {
+    mOutput.mState.transform = kRotate90Transform;
+
+    mCoverageAboveOpaqueLayersToSet = Region(Rect(0, 0, 1080, 960));
+
+    mOutput.rebuildLayerStacks(mRefreshArgs, mGeomSnapshots);
+
+    EXPECT_THAT(mOutput.mState.undefinedRegion, RegionEq(Region(Rect(0, 0, 960, 1080))));
+}
+
+TEST_F(OutputRebuildLayerStacksTest, addsToDirtyRegionWithNoRotation) {
+    mOutput.mState.transform = kIdentityTransform;
+    mOutput.mState.dirtyRegion = Region(Rect(960, 0, 1920, 1080));
+
+    mCoverageDirtyRegionToSet = Region(Rect(0, 0, 960, 1080));
+
+    mOutput.rebuildLayerStacks(mRefreshArgs, mGeomSnapshots);
+
+    EXPECT_THAT(mOutput.mState.dirtyRegion, RegionEq(Region(Rect(0, 0, 1920, 1080))));
+}
+
+TEST_F(OutputRebuildLayerStacksTest, addsToDirtyRegionWith90Rotation) {
+    mOutput.mState.transform = kRotate90Transform;
+    mOutput.mState.dirtyRegion = Region(Rect(0, 960, 1080, 1920));
+
+    mCoverageDirtyRegionToSet = Region(Rect(0, 0, 1080, 960));
+
+    mOutput.rebuildLayerStacks(mRefreshArgs, mGeomSnapshots);
+
+    EXPECT_THAT(mOutput.mState.dirtyRegion, RegionEq(Region(Rect(0, 0, 1080, 1920))));
+}
+
+/*
+ * Output::collectVisibleLayers()
+ */
+
+struct OutputCollectVisibleLayersTest : public testing::Test {
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // Sets up the helper functions called by the function under test to use
+        // mock implementations.
+        MOCK_METHOD2(ensureOutputLayerIfVisible,
+                     void(std::shared_ptr<compositionengine::Layer>,
+                          compositionengine::Output::CoverageState&));
+        MOCK_METHOD1(setReleasedLayers, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD0(finalizePendingOutputLayers, void());
+    };
+
+    struct Layer {
+        Layer() {
+            EXPECT_CALL(outputLayer, getState()).WillRepeatedly(ReturnRef(outputLayerState));
+            EXPECT_CALL(outputLayer, editState()).WillRepeatedly(ReturnRef(outputLayerState));
+        }
+
+        StrictMock<mock::OutputLayer> outputLayer;
+        std::shared_ptr<StrictMock<mock::Layer>> layer{new StrictMock<mock::Layer>()};
+        impl::OutputLayerCompositionState outputLayerState;
+    };
+
+    OutputCollectVisibleLayersTest() {
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(3));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0))
+                .WillRepeatedly(Return(&mLayer1.outputLayer));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1))
+                .WillRepeatedly(Return(&mLayer2.outputLayer));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(2))
+                .WillRepeatedly(Return(&mLayer3.outputLayer));
+
+        mRefreshArgs.layers.push_back(mLayer1.layer);
+        mRefreshArgs.layers.push_back(mLayer2.layer);
+        mRefreshArgs.layers.push_back(mLayer3.layer);
+    }
+
+    StrictMock<OutputPartialMock> mOutput;
+    CompositionRefreshArgs mRefreshArgs;
+    LayerFESet mGeomSnapshots;
+    Output::CoverageState mCoverageState{mGeomSnapshots};
+    Layer mLayer1;
+    Layer mLayer2;
+    Layer mLayer3;
+};
+
+TEST_F(OutputCollectVisibleLayersTest, doesMinimalWorkIfNoLayers) {
+    mRefreshArgs.layers.clear();
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0));
+
+    EXPECT_CALL(mOutput, setReleasedLayers(Ref(mRefreshArgs)));
+    EXPECT_CALL(mOutput, finalizePendingOutputLayers());
+
+    mOutput.collectVisibleLayers(mRefreshArgs, mCoverageState);
+}
+
+TEST_F(OutputCollectVisibleLayersTest, processesCandidateLayersReversedAndSetsOutputLayerZ) {
+    // Enforce a call order sequence for this test.
+    InSequence seq;
+
+    // Layer coverage is evaluated from front to back!
+    EXPECT_CALL(mOutput, ensureOutputLayerIfVisible(Eq(mLayer3.layer), Ref(mCoverageState)));
+    EXPECT_CALL(mOutput, ensureOutputLayerIfVisible(Eq(mLayer2.layer), Ref(mCoverageState)));
+    EXPECT_CALL(mOutput, ensureOutputLayerIfVisible(Eq(mLayer1.layer), Ref(mCoverageState)));
+
+    EXPECT_CALL(mOutput, setReleasedLayers(Ref(mRefreshArgs)));
+    EXPECT_CALL(mOutput, finalizePendingOutputLayers());
+
+    mOutput.collectVisibleLayers(mRefreshArgs, mCoverageState);
+
+    // Ensure all output layers have been assigned a simple/flattened z-order.
+    EXPECT_EQ(0u, mLayer1.outputLayerState.z);
+    EXPECT_EQ(1u, mLayer2.outputLayerState.z);
+    EXPECT_EQ(2u, mLayer3.outputLayerState.z);
+}
+
+/*
+ * Output::ensureOutputLayerIfVisible()
+ */
+
+struct OutputEnsureOutputLayerIfVisibleTest : public testing::Test {
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // Sets up the helper functions called by the function under test to use
+        // mock implementations.
+        MOCK_CONST_METHOD1(belongsInOutput, bool(const compositionengine::Layer*));
+        MOCK_CONST_METHOD1(getOutputLayerOrderedByZByIndex, OutputLayer*(size_t));
+        MOCK_METHOD3(ensureOutputLayer,
+                     compositionengine::OutputLayer*(
+                             std::optional<size_t>,
+                             const std::shared_ptr<compositionengine::Layer>&, const sp<LayerFE>&));
+    };
+
+    OutputEnsureOutputLayerIfVisibleTest() {
+        EXPECT_CALL(*mLayer, getLayerFE()).WillRepeatedly(Return(mLayerFE));
+        EXPECT_CALL(*mLayer, getFEState()).WillRepeatedly(ReturnRef(mLayerFEState));
+        EXPECT_CALL(*mLayer, editFEState()).WillRepeatedly(ReturnRef(mLayerFEState));
+
+        EXPECT_CALL(mOutput, belongsInOutput(mLayer.get())).WillRepeatedly(Return(true));
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(1));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u))
+                .WillRepeatedly(Return(&mOutputLayer));
+
+        EXPECT_CALL(mOutputLayer, getState()).WillRepeatedly(ReturnRef(mOutputLayerState));
+        EXPECT_CALL(mOutputLayer, editState()).WillRepeatedly(ReturnRef(mOutputLayerState));
+        EXPECT_CALL(mOutputLayer, getLayer()).WillRepeatedly(ReturnRef(*mLayer.get()));
+
+        mOutput.mState.bounds = Rect(0, 0, 200, 300);
+        mOutput.mState.viewport = Rect(0, 0, 200, 300);
+        mOutput.mState.transform = ui::Transform(TR_IDENT, 200, 300);
+
+        mLayerFEState.isVisible = true;
+        mLayerFEState.isOpaque = true;
+        mLayerFEState.contentDirty = true;
+        mLayerFEState.geomLayerBounds = FloatRect{0, 0, 100, 200};
+        mLayerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
+        mLayerFEState.transparentRegionHint = Region(Rect(0, 0, 100, 100));
+
+        mOutputLayerState.visibleRegion = Region(Rect(0, 0, 50, 200));
+        mOutputLayerState.coveredRegion = Region(Rect(50, 0, 100, 200));
+
+        mGeomSnapshots.insert(mLayerFE);
+    }
+
+    static const Region kEmptyRegion;
+    static const Region kFullBoundsNoRotation;
+    static const Region kRightHalfBoundsNoRotation;
+    static const Region kLowerHalfBoundsNoRotation;
+    static const Region kFullBounds90Rotation;
+
+    StrictMock<OutputPartialMock> mOutput;
+    LayerFESet mGeomSnapshots;
+    Output::CoverageState mCoverageState{mGeomSnapshots};
+
+    std::shared_ptr<mock::Layer> mLayer{new StrictMock<mock::Layer>()};
+    sp<StrictMock<mock::LayerFE>> mLayerFE{new StrictMock<mock::LayerFE>()};
+    LayerFECompositionState mLayerFEState;
+    mock::OutputLayer mOutputLayer;
+    impl::OutputLayerCompositionState mOutputLayerState;
+};
+
+const Region OutputEnsureOutputLayerIfVisibleTest::kEmptyRegion = Region(Rect(0, 0, 0, 0));
+const Region OutputEnsureOutputLayerIfVisibleTest::kFullBoundsNoRotation =
+        Region(Rect(0, 0, 100, 200));
+const Region OutputEnsureOutputLayerIfVisibleTest::kRightHalfBoundsNoRotation =
+        Region(Rect(0, 100, 100, 200));
+const Region OutputEnsureOutputLayerIfVisibleTest::kLowerHalfBoundsNoRotation =
+        Region(Rect(50, 0, 100, 200));
+const Region OutputEnsureOutputLayerIfVisibleTest::kFullBounds90Rotation =
+        Region(Rect(0, 0, 200, 100));
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, doesNothingIfNoLayerFE) {
+    EXPECT_CALL(*mLayer, getLayerFE).WillOnce(Return(sp<LayerFE>()));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, performsGeomLatchBeforeCheckingIfLayerBelongs) {
+    EXPECT_CALL(mOutput, belongsInOutput(mLayer.get())).WillOnce(Return(false));
+    EXPECT_CALL(*mLayerFE.get(),
+                latchCompositionState(Ref(mLayerFEState),
+                                      compositionengine::LayerFE::StateSubset::BasicGeometry));
+
+    mGeomSnapshots.clear();
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest,
+       skipsLatchIfAlreadyLatchedBeforeCheckingIfLayerBelongs) {
+    EXPECT_CALL(mOutput, belongsInOutput(mLayer.get())).WillOnce(Return(false));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, takesEarlyOutIfLayerNotVisible) {
+    mLayerFEState.isVisible = false;
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, takesEarlyOutIfLayerHasEmptyVisibleRegion) {
+    mLayerFEState.geomLayerBounds = FloatRect{0, 0, 0, 0};
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, takesNotSoEarlyOutifDrawRegionEmpty) {
+    mOutput.mState.bounds = Rect(0, 0, 0, 0);
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest,
+       handlesCreatingOutputLayerForOpaqueDirtyNotRotatedLayer) {
+    mLayerFEState.isOpaque = true;
+    mLayerFEState.contentDirty = true;
+    mLayerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(std::nullopt), Eq(mLayer), Eq(mLayerFE)))
+            .WillOnce(Return(&mOutputLayer));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+
+    EXPECT_THAT(mCoverageState.dirtyRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveCoveredLayers, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveOpaqueLayers, RegionEq(kFullBoundsNoRotation));
+
+    EXPECT_THAT(mOutputLayerState.visibleRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.visibleNonTransparentRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.coveredRegion, RegionEq(kEmptyRegion));
+    EXPECT_THAT(mOutputLayerState.outputSpaceVisibleRegion, RegionEq(kFullBoundsNoRotation));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest,
+       handlesUpdatingOutputLayerForOpaqueDirtyNotRotatedLayer) {
+    mLayerFEState.isOpaque = true;
+    mLayerFEState.contentDirty = true;
+    mLayerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
+
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(0u), Eq(mLayer), Eq(mLayerFE)))
+            .WillOnce(Return(&mOutputLayer));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+
+    EXPECT_THAT(mCoverageState.dirtyRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveCoveredLayers, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveOpaqueLayers, RegionEq(kFullBoundsNoRotation));
+
+    EXPECT_THAT(mOutputLayerState.visibleRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.visibleNonTransparentRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.coveredRegion, RegionEq(kEmptyRegion));
+    EXPECT_THAT(mOutputLayerState.outputSpaceVisibleRegion, RegionEq(kFullBoundsNoRotation));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest,
+       handlesCreatingOutputLayerForTransparentDirtyNotRotatedLayer) {
+    mLayerFEState.isOpaque = false;
+    mLayerFEState.contentDirty = true;
+    mLayerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(std::nullopt), Eq(mLayer), Eq(mLayerFE)))
+            .WillOnce(Return(&mOutputLayer));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+
+    EXPECT_THAT(mCoverageState.dirtyRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveCoveredLayers, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveOpaqueLayers, RegionEq(kEmptyRegion));
+
+    EXPECT_THAT(mOutputLayerState.visibleRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.visibleNonTransparentRegion,
+                RegionEq(kRightHalfBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.coveredRegion, RegionEq(kEmptyRegion));
+    EXPECT_THAT(mOutputLayerState.outputSpaceVisibleRegion, RegionEq(kFullBoundsNoRotation));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest,
+       handlesUpdatingOutputLayerForTransparentDirtyNotRotatedLayer) {
+    mLayerFEState.isOpaque = false;
+    mLayerFEState.contentDirty = true;
+    mLayerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
+
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(0u), Eq(mLayer), Eq(mLayerFE)))
+            .WillOnce(Return(&mOutputLayer));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+
+    EXPECT_THAT(mCoverageState.dirtyRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveCoveredLayers, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveOpaqueLayers, RegionEq(kEmptyRegion));
+
+    EXPECT_THAT(mOutputLayerState.visibleRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.visibleNonTransparentRegion,
+                RegionEq(kRightHalfBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.coveredRegion, RegionEq(kEmptyRegion));
+    EXPECT_THAT(mOutputLayerState.outputSpaceVisibleRegion, RegionEq(kFullBoundsNoRotation));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest,
+       handlesCreatingOutputLayerForOpaqueNonDirtyNotRotatedLayer) {
+    mLayerFEState.isOpaque = true;
+    mLayerFEState.contentDirty = false;
+    mLayerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(std::nullopt), Eq(mLayer), Eq(mLayerFE)))
+            .WillOnce(Return(&mOutputLayer));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+
+    EXPECT_THAT(mCoverageState.dirtyRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveCoveredLayers, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveOpaqueLayers, RegionEq(kFullBoundsNoRotation));
+
+    EXPECT_THAT(mOutputLayerState.visibleRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.visibleNonTransparentRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.coveredRegion, RegionEq(kEmptyRegion));
+    EXPECT_THAT(mOutputLayerState.outputSpaceVisibleRegion, RegionEq(kFullBoundsNoRotation));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest,
+       handlesUpdatingOutputLayerForOpaqueNonDirtyNotRotatedLayer) {
+    mLayerFEState.isOpaque = true;
+    mLayerFEState.contentDirty = false;
+    mLayerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
+
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(0u), Eq(mLayer), Eq(mLayerFE)))
+            .WillOnce(Return(&mOutputLayer));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+
+    EXPECT_THAT(mCoverageState.dirtyRegion, RegionEq(kLowerHalfBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveCoveredLayers, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveOpaqueLayers, RegionEq(kFullBoundsNoRotation));
+
+    EXPECT_THAT(mOutputLayerState.visibleRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.visibleNonTransparentRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.coveredRegion, RegionEq(kEmptyRegion));
+    EXPECT_THAT(mOutputLayerState.outputSpaceVisibleRegion, RegionEq(kFullBoundsNoRotation));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest,
+       handlesCreatingOutputLayerForOpaqueDirtyRotated90Layer) {
+    mLayerFEState.isOpaque = true;
+    mLayerFEState.contentDirty = true;
+    mLayerFEState.geomLayerBounds = FloatRect{0, 0, 200, 100};
+    mLayerFEState.geomLayerTransform = ui::Transform(TR_ROT_90, 100, 200);
+    mOutputLayerState.visibleRegion = Region(Rect(0, 0, 100, 100));
+    mOutputLayerState.coveredRegion = Region(Rect(100, 0, 200, 100));
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(std::nullopt), Eq(mLayer), Eq(mLayerFE)))
+            .WillOnce(Return(&mOutputLayer));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+
+    EXPECT_THAT(mCoverageState.dirtyRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveCoveredLayers, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveOpaqueLayers, RegionEq(kFullBoundsNoRotation));
+
+    EXPECT_THAT(mOutputLayerState.visibleRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.visibleNonTransparentRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.coveredRegion, RegionEq(kEmptyRegion));
+    EXPECT_THAT(mOutputLayerState.outputSpaceVisibleRegion, RegionEq(kFullBoundsNoRotation));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest,
+       handlesUpdatingOutputLayerForOpaqueDirtyRotated90Layer) {
+    mLayerFEState.isOpaque = true;
+    mLayerFEState.contentDirty = true;
+    mLayerFEState.geomLayerBounds = FloatRect{0, 0, 200, 100};
+    mLayerFEState.geomLayerTransform = ui::Transform(TR_ROT_90, 100, 200);
+    mOutputLayerState.visibleRegion = Region(Rect(0, 0, 100, 100));
+    mOutputLayerState.coveredRegion = Region(Rect(100, 0, 200, 100));
+
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(0u), Eq(mLayer), Eq(mLayerFE)))
+            .WillOnce(Return(&mOutputLayer));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+
+    EXPECT_THAT(mCoverageState.dirtyRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveCoveredLayers, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveOpaqueLayers, RegionEq(kFullBoundsNoRotation));
+
+    EXPECT_THAT(mOutputLayerState.visibleRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.visibleNonTransparentRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.coveredRegion, RegionEq(kEmptyRegion));
+    EXPECT_THAT(mOutputLayerState.outputSpaceVisibleRegion, RegionEq(kFullBoundsNoRotation));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest,
+       handlesCreatingOutputLayerForOpaqueDirtyNotRotatedLayerRotatedOutput) {
+    mLayerFEState.isOpaque = true;
+    mLayerFEState.contentDirty = true;
+    mLayerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
+
+    mOutput.mState.viewport = Rect(0, 0, 300, 200);
+    mOutput.mState.transform = ui::Transform(TR_ROT_90, 200, 300);
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(std::nullopt), Eq(mLayer), Eq(mLayerFE)))
+            .WillOnce(Return(&mOutputLayer));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+
+    EXPECT_THAT(mCoverageState.dirtyRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveCoveredLayers, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveOpaqueLayers, RegionEq(kFullBoundsNoRotation));
+
+    EXPECT_THAT(mOutputLayerState.visibleRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.visibleNonTransparentRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.coveredRegion, RegionEq(kEmptyRegion));
+    EXPECT_THAT(mOutputLayerState.outputSpaceVisibleRegion, RegionEq(kFullBounds90Rotation));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest,
+       handlesUpdatingOutputLayerForOpaqueDirtyNotRotatedLayerRotatedOutput) {
+    mLayerFEState.isOpaque = true;
+    mLayerFEState.contentDirty = true;
+    mLayerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
+
+    mOutput.mState.viewport = Rect(0, 0, 300, 200);
+    mOutput.mState.transform = ui::Transform(TR_ROT_90, 200, 300);
+
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(0u), Eq(mLayer), Eq(mLayerFE)))
+            .WillOnce(Return(&mOutputLayer));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+
+    EXPECT_THAT(mCoverageState.dirtyRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveCoveredLayers, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mCoverageState.aboveOpaqueLayers, RegionEq(kFullBoundsNoRotation));
+
+    EXPECT_THAT(mOutputLayerState.visibleRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.visibleNonTransparentRegion, RegionEq(kFullBoundsNoRotation));
+    EXPECT_THAT(mOutputLayerState.coveredRegion, RegionEq(kEmptyRegion));
+    EXPECT_THAT(mOutputLayerState.outputSpaceVisibleRegion, RegionEq(kFullBounds90Rotation));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest,
+       handlesCreatingOutputLayerForOpaqueDirtyArbitraryTransformLayer) {
+    ui::Transform arbitraryTransform;
+    arbitraryTransform.set(1, 1, -1, 1);
+    arbitraryTransform.set(0, 100);
+
+    mLayerFEState.isOpaque = true;
+    mLayerFEState.contentDirty = true;
+    mLayerFEState.geomLayerBounds = FloatRect{0, 0, 100, 200};
+    mLayerFEState.geomLayerTransform = arbitraryTransform;
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(std::nullopt), Eq(mLayer), Eq(mLayerFE)))
+            .WillOnce(Return(&mOutputLayer));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+
+    const Region kRegion = Region(Rect(0, 0, 300, 300));
+    const Region kRegionClipped = Region(Rect(0, 0, 200, 300));
+
+    EXPECT_THAT(mCoverageState.dirtyRegion, RegionEq(kRegion));
+    EXPECT_THAT(mCoverageState.aboveCoveredLayers, RegionEq(kRegion));
+    EXPECT_THAT(mCoverageState.aboveOpaqueLayers, RegionEq(kEmptyRegion));
+
+    EXPECT_THAT(mOutputLayerState.visibleRegion, RegionEq(kRegion));
+    EXPECT_THAT(mOutputLayerState.visibleNonTransparentRegion, RegionEq(kRegion));
+    EXPECT_THAT(mOutputLayerState.coveredRegion, RegionEq(kEmptyRegion));
+    EXPECT_THAT(mOutputLayerState.outputSpaceVisibleRegion, RegionEq(kRegionClipped));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, coverageAccumulatesTest) {
+    mLayerFEState.isOpaque = false;
+    mLayerFEState.contentDirty = true;
+    mLayerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
+
+    mCoverageState.dirtyRegion = Region(Rect(0, 0, 500, 500));
+    mCoverageState.aboveCoveredLayers = Region(Rect(50, 0, 150, 200));
+    mCoverageState.aboveOpaqueLayers = Region(Rect(50, 0, 150, 200));
+
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(0u), Eq(mLayer), Eq(mLayerFE)))
+            .WillOnce(Return(&mOutputLayer));
+
+    mOutput.ensureOutputLayerIfVisible(mLayer, mCoverageState);
+
+    const Region kExpectedDirtyRegion = Region(Rect(0, 0, 500, 500));
+    const Region kExpectedAboveCoveredRegion = Region(Rect(0, 0, 150, 200));
+    const Region kExpectedAboveOpaqueRegion = Region(Rect(50, 0, 150, 200));
+    const Region kExpectedLayerVisibleRegion = Region(Rect(0, 0, 50, 200));
+    const Region kExpectedLayerCoveredRegion = Region(Rect(50, 0, 100, 200));
+    const Region kExpectedLayerVisibleNonTransparentRegion = Region(Rect(0, 100, 50, 200));
+
+    EXPECT_THAT(mCoverageState.dirtyRegion, RegionEq(kExpectedDirtyRegion));
+    EXPECT_THAT(mCoverageState.aboveCoveredLayers, RegionEq(kExpectedAboveCoveredRegion));
+    EXPECT_THAT(mCoverageState.aboveOpaqueLayers, RegionEq(kExpectedAboveOpaqueRegion));
+
+    EXPECT_THAT(mOutputLayerState.visibleRegion, RegionEq(kExpectedLayerVisibleRegion));
+    EXPECT_THAT(mOutputLayerState.visibleNonTransparentRegion,
+                RegionEq(kExpectedLayerVisibleNonTransparentRegion));
+    EXPECT_THAT(mOutputLayerState.coveredRegion, RegionEq(kExpectedLayerCoveredRegion));
+    EXPECT_THAT(mOutputLayerState.outputSpaceVisibleRegion, RegionEq(kExpectedLayerVisibleRegion));
+}
+
+/*
  * Output::present()
  */
 
 struct OutputPresentTest : public testing::Test {
     struct OutputPartialMock : public OutputPartialMockBase {
-        // All child helper functions Output::present() are defined as mocks,
-        // and those are tested separately, allowing the present() test to
-        // just cover the high level flow.
+        // 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,
                      void(const compositionengine::CompositionRefreshArgs&));
@@ -657,49 +1481,1202 @@
  * Output::updateColorProfile()
  */
 
-// TODO(b/144060211) - Add coverage
+struct OutputUpdateColorProfileTest : public testing::Test {
+    using TestType = OutputUpdateColorProfileTest;
+
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // Sets up the helper functions called by the function under test to use
+        // mock implementations.
+        MOCK_METHOD1(setColorProfile, void(const ColorProfile&));
+    };
+
+    struct Layer {
+        Layer() {
+            EXPECT_CALL(mOutputLayer, getLayer()).WillRepeatedly(ReturnRef(mLayer));
+            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(mLayerFE));
+            EXPECT_CALL(mLayer, getFEState()).WillRepeatedly(ReturnRef(mLayerFEState));
+        }
+
+        StrictMock<mock::OutputLayer> mOutputLayer;
+        StrictMock<mock::Layer> mLayer;
+        StrictMock<mock::LayerFE> mLayerFE;
+        LayerFECompositionState mLayerFEState;
+    };
+
+    OutputUpdateColorProfileTest() {
+        mOutput.setDisplayColorProfileForTest(
+                std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
+        mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0))
+                .WillRepeatedly(Return(&mLayer1.mOutputLayer));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1))
+                .WillRepeatedly(Return(&mLayer2.mOutputLayer));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(2))
+                .WillRepeatedly(Return(&mLayer3.mOutputLayer));
+    }
+
+    struct ExecuteState : public CallOrderStateMachineHelper<TestType, ExecuteState> {
+        void execute() { getInstance()->mOutput.updateColorProfile(getInstance()->mRefreshArgs); }
+    };
+
+    mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock<mock::DisplayColorProfile>();
+    mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
+    StrictMock<OutputPartialMock> mOutput;
+
+    Layer mLayer1;
+    Layer mLayer2;
+    Layer mLayer3;
+
+    CompositionRefreshArgs mRefreshArgs;
+};
+
+// TODO(b/144522012): Refactor Output::updateColorProfile and the related code
+// to make it easier to write unit tests.
+
+TEST_F(OutputUpdateColorProfileTest, setsAColorProfileWhenUnmanaged) {
+    // When the outputColorSetting is set to kUnmanaged, the implementation sets
+    // a simple default color profile without looking at anything else.
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(3));
+    EXPECT_CALL(mOutput,
+                setColorProfile(ColorProfileEq(
+                        ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
+                                     ui::RenderIntent::COLORIMETRIC, ui::Dataspace::UNKNOWN})));
+
+    mRefreshArgs.outputColorSetting = OutputColorSetting::kUnmanaged;
+    mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
+
+    mOutput.updateColorProfile(mRefreshArgs);
+}
+
+struct OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile
+      : public OutputUpdateColorProfileTest {
+    OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile() {
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0));
+        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
+        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
+    }
+
+    struct ExpectBestColorModeCallResultUsedToSetColorProfileState
+          : public CallOrderStateMachineHelper<
+                    TestType, ExpectBestColorModeCallResultUsedToSetColorProfileState> {
+        [[nodiscard]] auto expectBestColorModeCallResultUsedToSetColorProfile(
+                ui::ColorMode colorMode, ui::Dataspace dataspace, ui::RenderIntent renderIntent) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile,
+                        getBestColorMode(ui::Dataspace::V0_SRGB, ui::RenderIntent::ENHANCE, _, _,
+                                         _))
+                    .WillOnce(DoAll(SetArgPointee<2>(dataspace), SetArgPointee<3>(colorMode),
+                                    SetArgPointee<4>(renderIntent)));
+            EXPECT_CALL(getInstance()->mOutput,
+                        setColorProfile(
+                                ColorProfileEq(ColorProfile{colorMode, dataspace, renderIntent,
+                                                            ui::Dataspace::UNKNOWN})));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Call this member function to start using the mini-DSL defined above.
+    [[nodiscard]] auto verify() {
+        return ExpectBestColorModeCallResultUsedToSetColorProfileState::make(this);
+    }
+};
+
+TEST_F(OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile,
+       Native_Unknown_Colorimetric_Set) {
+    verify().expectBestColorModeCallResultUsedToSetColorProfile(ui::ColorMode::NATIVE,
+                                                                ui::Dataspace::UNKNOWN,
+                                                                ui::RenderIntent::COLORIMETRIC)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile,
+       DisplayP3_DisplayP3_Enhance_Set) {
+    verify().expectBestColorModeCallResultUsedToSetColorProfile(ui::ColorMode::DISPLAY_P3,
+                                                                ui::Dataspace::DISPLAY_P3,
+                                                                ui::RenderIntent::ENHANCE)
+            .execute();
+}
+
+struct OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile
+      : public OutputUpdateColorProfileTest {
+    OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile() {
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0));
+        EXPECT_CALL(*mDisplayColorProfile,
+                    getBestColorMode(ui::Dataspace::V0_SRGB, ui::RenderIntent::ENHANCE, _, _, _))
+                .WillRepeatedly(DoAll(SetArgPointee<2>(ui::Dataspace::UNKNOWN),
+                                      SetArgPointee<3>(ui::ColorMode::NATIVE),
+                                      SetArgPointee<4>(ui::RenderIntent::COLORIMETRIC)));
+        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
+    }
+
+    struct IfColorSpaceAgnosticDataspaceSetToState
+          : public CallOrderStateMachineHelper<TestType, IfColorSpaceAgnosticDataspaceSetToState> {
+        [[nodiscard]] auto ifColorSpaceAgnosticDataspaceSetTo(ui::Dataspace dataspace) {
+            getInstance()->mRefreshArgs.colorSpaceAgnosticDataspace = dataspace;
+            return nextState<ThenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspaceState>();
+        }
+    };
+
+    struct ThenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspaceState
+          : public CallOrderStateMachineHelper<
+                    TestType, ThenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspaceState> {
+        [[nodiscard]] auto thenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspace(
+                ui::Dataspace dataspace) {
+            EXPECT_CALL(getInstance()->mOutput,
+                        setColorProfile(ColorProfileEq(
+                                ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
+                                             ui::RenderIntent::COLORIMETRIC, dataspace})));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Call this member function to start using the mini-DSL defined above.
+    [[nodiscard]] auto verify() { return IfColorSpaceAgnosticDataspaceSetToState::make(this); }
+};
+
+TEST_F(OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile, DisplayP3) {
+    verify().ifColorSpaceAgnosticDataspaceSetTo(ui::Dataspace::DISPLAY_P3)
+            .thenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspace(ui::Dataspace::DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile, V0_SRGB) {
+    verify().ifColorSpaceAgnosticDataspaceSetTo(ui::Dataspace::V0_SRGB)
+            .thenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspace(ui::Dataspace::V0_SRGB)
+            .execute();
+}
+
+struct OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference
+      : public OutputUpdateColorProfileTest {
+    // Internally the implementation looks through the dataspaces of all the
+    // visible layers. The topmost one that also has an actual dataspace
+    // preference set is used to drive subsequent choices.
+
+    OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference() {
+        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
+        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
+
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(3));
+        EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
+    }
+
+    struct IfTopLayerDataspaceState
+          : public CallOrderStateMachineHelper<TestType, IfTopLayerDataspaceState> {
+        [[nodiscard]] auto ifTopLayerIs(ui::Dataspace dataspace) {
+            getInstance()->mLayer3.mLayerFEState.dataspace = dataspace;
+            return nextState<AndIfMiddleLayerDataspaceState>();
+        }
+        [[nodiscard]] auto ifTopLayerHasNoPreference() {
+            return ifTopLayerIs(ui::Dataspace::UNKNOWN);
+        }
+    };
+
+    struct AndIfMiddleLayerDataspaceState
+          : public CallOrderStateMachineHelper<TestType, AndIfMiddleLayerDataspaceState> {
+        [[nodiscard]] auto andIfMiddleLayerIs(ui::Dataspace dataspace) {
+            getInstance()->mLayer2.mLayerFEState.dataspace = dataspace;
+            return nextState<AndIfBottomLayerDataspaceState>();
+        }
+        [[nodiscard]] auto andIfMiddleLayerHasNoPreference() {
+            return andIfMiddleLayerIs(ui::Dataspace::UNKNOWN);
+        }
+    };
+
+    struct AndIfBottomLayerDataspaceState
+          : public CallOrderStateMachineHelper<TestType, AndIfBottomLayerDataspaceState> {
+        [[nodiscard]] auto andIfBottomLayerIs(ui::Dataspace dataspace) {
+            getInstance()->mLayer1.mLayerFEState.dataspace = dataspace;
+            return nextState<ThenExpectBestColorModeCallUsesState>();
+        }
+        [[nodiscard]] auto andIfBottomLayerHasNoPreference() {
+            return andIfBottomLayerIs(ui::Dataspace::UNKNOWN);
+        }
+    };
+
+    struct ThenExpectBestColorModeCallUsesState
+          : public CallOrderStateMachineHelper<TestType, ThenExpectBestColorModeCallUsesState> {
+        [[nodiscard]] auto thenExpectBestColorModeCallUses(ui::Dataspace dataspace) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile,
+                        getBestColorMode(dataspace, _, _, _, _));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Call this member function to start using the mini-DSL defined above.
+    [[nodiscard]] auto verify() { return IfTopLayerDataspaceState::make(this); }
+};
+
+TEST_F(OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference,
+       noStrongLayerPrefenceUses_V0_SRGB) {
+    // If none of the layers indicate a preference, then V0_SRGB is the
+    // preferred choice (subject to additional checks).
+    verify().ifTopLayerHasNoPreference()
+            .andIfMiddleLayerHasNoPreference()
+            .andIfBottomLayerHasNoPreference()
+            .thenExpectBestColorModeCallUses(ui::Dataspace::V0_SRGB)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference,
+       ifTopmostUses_DisplayP3_Then_DisplayP3_Chosen) {
+    // If only the topmost layer has a preference, then that is what is chosen.
+    verify().ifTopLayerIs(ui::Dataspace::DISPLAY_P3)
+            .andIfMiddleLayerHasNoPreference()
+            .andIfBottomLayerHasNoPreference()
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference,
+       ifMiddleUses_DisplayP3_Then_DisplayP3_Chosen) {
+    // If only the middle layer has a preference, that that is what is chosen.
+    verify().ifTopLayerHasNoPreference()
+            .andIfMiddleLayerIs(ui::Dataspace::DISPLAY_P3)
+            .andIfBottomLayerHasNoPreference()
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference,
+       ifBottomUses_DisplayP3_Then_DisplayP3_Chosen) {
+    // If only the middle layer has a preference, that that is what is chosen.
+    verify().ifTopLayerHasNoPreference()
+            .andIfMiddleLayerHasNoPreference()
+            .andIfBottomLayerIs(ui::Dataspace::DISPLAY_P3)
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference,
+       ifTopUses_DisplayBT2020_AndBottomUses_DisplayP3_Then_DisplayBT2020_Chosen) {
+    // If multiple layers have a preference, the topmost value is what is used.
+    verify().ifTopLayerIs(ui::Dataspace::DISPLAY_BT2020)
+            .andIfMiddleLayerHasNoPreference()
+            .andIfBottomLayerIs(ui::Dataspace::DISPLAY_P3)
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_BT2020)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference,
+       ifTopUses_DisplayP3_AndBottomUses_V0_SRGB_Then_DisplayP3_Chosen) {
+    // If multiple layers have a preference, the topmost value is what is used.
+    verify().ifTopLayerIs(ui::Dataspace::DISPLAY_P3)
+            .andIfMiddleLayerHasNoPreference()
+            .andIfBottomLayerIs(ui::Dataspace::DISPLAY_BT2020)
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_P3)
+            .execute();
+}
+
+struct OutputUpdateColorProfileTest_ForceOutputColorOverrides
+      : public OutputUpdateColorProfileTest {
+    // If CompositionRefreshArgs::forceOutputColorMode is set to some specific
+    // values, it overrides the layer dataspace choice.
+
+    OutputUpdateColorProfileTest_ForceOutputColorOverrides() {
+        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
+        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
+
+        mLayer1.mLayerFEState.dataspace = ui::Dataspace::DISPLAY_BT2020;
+
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(1));
+        EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
+    }
+
+    struct IfForceOutputColorModeState
+          : public CallOrderStateMachineHelper<TestType, IfForceOutputColorModeState> {
+        [[nodiscard]] auto ifForceOutputColorMode(ui::ColorMode colorMode) {
+            getInstance()->mRefreshArgs.forceOutputColorMode = colorMode;
+            return nextState<ThenExpectBestColorModeCallUsesState>();
+        }
+        [[nodiscard]] auto ifNoOverride() { return ifForceOutputColorMode(ui::ColorMode::NATIVE); }
+    };
+
+    struct ThenExpectBestColorModeCallUsesState
+          : public CallOrderStateMachineHelper<TestType, ThenExpectBestColorModeCallUsesState> {
+        [[nodiscard]] auto thenExpectBestColorModeCallUses(ui::Dataspace dataspace) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile,
+                        getBestColorMode(dataspace, _, _, _, _));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Call this member function to start using the mini-DSL defined above.
+    [[nodiscard]] auto verify() { return IfForceOutputColorModeState::make(this); }
+};
+
+TEST_F(OutputUpdateColorProfileTest_ForceOutputColorOverrides, NoOverride_DoesNotOverride) {
+    // By default the layer state is used to set the preferred dataspace
+    verify().ifNoOverride()
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_BT2020)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_ForceOutputColorOverrides, SRGB_Override_USES_V0_SRGB) {
+    // Setting ui::ColorMode::SRGB overrides it with ui::Dataspace::V0_SRGB
+    verify().ifForceOutputColorMode(ui::ColorMode::SRGB)
+            .thenExpectBestColorModeCallUses(ui::Dataspace::V0_SRGB)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_ForceOutputColorOverrides, DisplayP3_Override_Uses_DisplayP3) {
+    // Setting ui::ColorMode::DISPLAY_P3 overrides it with ui::Dataspace::DISPLAY_P3
+    verify().ifForceOutputColorMode(ui::ColorMode::DISPLAY_P3)
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_P3)
+            .execute();
+}
+
+// HDR output requires all layers to be compatible with the chosen HDR
+// dataspace, along with there being proper support.
+struct OutputUpdateColorProfileTest_Hdr : public OutputUpdateColorProfileTest {
+    OutputUpdateColorProfileTest_Hdr() {
+        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
+        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2));
+        EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
+    }
+
+    static constexpr ui::Dataspace kNonHdrDataspace = ui::Dataspace::DISPLAY_P3;
+    static constexpr ui::Dataspace BT2020_PQ = ui::Dataspace::BT2020_PQ;
+    static constexpr ui::Dataspace BT2020_HLG = ui::Dataspace::BT2020_HLG;
+    static constexpr ui::Dataspace DISPLAY_P3 = ui::Dataspace::DISPLAY_P3;
+
+    struct IfTopLayerDataspaceState
+          : public CallOrderStateMachineHelper<TestType, IfTopLayerDataspaceState> {
+        [[nodiscard]] auto ifTopLayerIs(ui::Dataspace dataspace) {
+            getInstance()->mLayer2.mLayerFEState.dataspace = dataspace;
+            return nextState<AndTopLayerCompositionTypeState>();
+        }
+        [[nodiscard]] auto ifTopLayerIsNotHdr() { return ifTopLayerIs(kNonHdrDataspace); }
+    };
+
+    struct AndTopLayerCompositionTypeState
+          : public CallOrderStateMachineHelper<TestType, AndTopLayerCompositionTypeState> {
+        [[nodiscard]] auto andTopLayerIsREComposed(bool renderEngineComposed) {
+            getInstance()->mLayer2.mLayerFEState.forceClientComposition = renderEngineComposed;
+            return nextState<AndIfBottomLayerDataspaceState>();
+        }
+    };
+
+    struct AndIfBottomLayerDataspaceState
+          : public CallOrderStateMachineHelper<TestType, AndIfBottomLayerDataspaceState> {
+        [[nodiscard]] auto andIfBottomLayerIs(ui::Dataspace dataspace) {
+            getInstance()->mLayer1.mLayerFEState.dataspace = dataspace;
+            return nextState<AndBottomLayerCompositionTypeState>();
+        }
+        [[nodiscard]] auto andIfBottomLayerIsNotHdr() {
+            return andIfBottomLayerIs(kNonHdrDataspace);
+        }
+    };
+
+    struct AndBottomLayerCompositionTypeState
+          : public CallOrderStateMachineHelper<TestType, AndBottomLayerCompositionTypeState> {
+        [[nodiscard]] auto andBottomLayerIsREComposed(bool renderEngineComposed) {
+            getInstance()->mLayer1.mLayerFEState.forceClientComposition = renderEngineComposed;
+            return nextState<AndIfHasLegacySupportState>();
+        }
+    };
+
+    struct AndIfHasLegacySupportState
+          : public CallOrderStateMachineHelper<TestType, AndIfHasLegacySupportState> {
+        [[nodiscard]] auto andIfLegacySupportFor(ui::Dataspace dataspace, bool legacySupport) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile, hasLegacyHdrSupport(dataspace))
+                    .WillOnce(Return(legacySupport));
+            return nextState<ThenExpectBestColorModeCallUsesState>();
+        }
+    };
+
+    struct ThenExpectBestColorModeCallUsesState
+          : public CallOrderStateMachineHelper<TestType, ThenExpectBestColorModeCallUsesState> {
+        [[nodiscard]] auto thenExpectBestColorModeCallUses(ui::Dataspace dataspace) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile,
+                        getBestColorMode(dataspace, _, _, _, _));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Call this member function to start using the mini-DSL defined above.
+    [[nodiscard]] auto verify() { return IfTopLayerDataspaceState::make(this); }
+};
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_PQ_HW_Uses_PQ) {
+    // If all layers use BT2020_PQ, and there are no other special conditions,
+    // BT2020_PQ is used.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_PQ_HW_IfPQHasLegacySupport_Uses_DisplayP3) {
+    // BT2020_PQ is not used if there is only legacy support for it.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, true)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_PQ_RE_Uses_PQ) {
+    // BT2020_PQ is still used if the bottom layer is RenderEngine composed.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(true)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_RE_On_PQ_HW_Uses_DisplayP3) {
+    // BT2020_PQ is not used if the top layer is RenderEngine composed.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(true)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_HLG_HW_Uses_PQ) {
+    // If there is mixed HLG/PQ use, and the topmost layer is PQ, then PQ is used if there
+    // are no other special conditions.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_HLG_HW_IfPQHasLegacySupport_Uses_DisplayP3) {
+    // BT2020_PQ is not used if there is only legacy support for it.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, true)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_HLG_RE_Uses_PQ) {
+    // BT2020_PQ is used if the bottom HLG layer is RenderEngine composed.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(true)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_RE_On_HLG_HW_Uses_DisplayP3) {
+    // BT2020_PQ is not used if the top PQ layer is RenderEngine composed.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(true)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_PQ_HW_Uses_PQ) {
+    // If there is mixed HLG/PQ use, and the topmost layer is HLG, then PQ is
+    // used if there are no other special conditions.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_PQ_HW_IfPQHasLegacySupport_Uses_DisplayP3) {
+    // BT2020_PQ is not used if there is only legacy support for it.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, true)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_PQ_RE_Uses_DisplayP3) {
+    // BT2020_PQ is not used if the bottom PQ layer is RenderEngine composed.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(true)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_RE_On_PQ_HW_Uses_PQ) {
+    // BT2020_PQ is still used if the top HLG layer is RenderEngine composed.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(true)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_HLG_HW_Uses_HLG) {
+    // If all layers use HLG then HLG is used if there are no other special
+    // conditions.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_HLG, false)
+            .thenExpectBestColorModeCallUses(BT2020_HLG)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_HLG_HW_IfPQHasLegacySupport_Uses_DisplayP3) {
+    // BT2020_HLG is not used if there is legacy support for it.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_HLG, true)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_HLG_RE_Uses_HLG) {
+    // BT2020_HLG is used even if the bottom layer is client composed.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(true)
+            .andIfLegacySupportFor(BT2020_HLG, false)
+            .thenExpectBestColorModeCallUses(BT2020_HLG)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_RE_On_HLG_HW_Uses_HLG) {
+    // BT2020_HLG is used even if the top layer is client composed.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(true)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_HLG, false)
+            .thenExpectBestColorModeCallUses(BT2020_HLG)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_NonHdr_HW_Uses_PQ) {
+    // Even if there are non-HDR layers present, BT2020_PQ can still be used.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIsNotHdr()
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_NonHdr_RE_Uses_HLG) {
+    // If all layers use HLG then HLG is used if there are no other special
+    // conditions.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIsNotHdr()
+            .andBottomLayerIsREComposed(true)
+            .andIfLegacySupportFor(BT2020_HLG, false)
+            .thenExpectBestColorModeCallUses(BT2020_HLG)
+            .execute();
+}
+
+struct OutputUpdateColorProfile_AffectsChosenRenderIntentTest
+      : public OutputUpdateColorProfileTest {
+    // The various values for CompositionRefreshArgs::outputColorSetting affect
+    // the chosen renderIntent, along with whether the preferred dataspace is an
+    // HDR dataspace or not.
+
+    OutputUpdateColorProfile_AffectsChosenRenderIntentTest() {
+        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
+        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
+        mLayer1.mLayerFEState.dataspace = ui::Dataspace::BT2020_PQ;
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(1));
+        EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
+        EXPECT_CALL(*mDisplayColorProfile, hasLegacyHdrSupport(ui::Dataspace::BT2020_PQ))
+                .WillRepeatedly(Return(false));
+    }
+
+    // The tests here involve enough state and GMock setup that using a mini-DSL
+    // makes the tests much more readable, and allows the test to focus more on
+    // the intent than on some of the details.
+
+    static constexpr ui::Dataspace kNonHdrDataspace = ui::Dataspace::DISPLAY_P3;
+    static constexpr ui::Dataspace kHdrDataspace = ui::Dataspace::BT2020_PQ;
+
+    struct IfDataspaceChosenState
+          : public CallOrderStateMachineHelper<TestType, IfDataspaceChosenState> {
+        [[nodiscard]] auto ifDataspaceChosenIs(ui::Dataspace dataspace) {
+            getInstance()->mLayer1.mLayerFEState.dataspace = dataspace;
+            return nextState<AndOutputColorSettingState>();
+        }
+        [[nodiscard]] auto ifDataspaceChosenIsNonHdr() {
+            return ifDataspaceChosenIs(kNonHdrDataspace);
+        }
+        [[nodiscard]] auto ifDataspaceChosenIsHdr() { return ifDataspaceChosenIs(kHdrDataspace); }
+    };
+
+    struct AndOutputColorSettingState
+          : public CallOrderStateMachineHelper<TestType, AndOutputColorSettingState> {
+        [[nodiscard]] auto andOutputColorSettingIs(OutputColorSetting setting) {
+            getInstance()->mRefreshArgs.outputColorSetting = setting;
+            return nextState<ThenExpectBestColorModeCallUsesState>();
+        }
+    };
+
+    struct ThenExpectBestColorModeCallUsesState
+          : public CallOrderStateMachineHelper<TestType, ThenExpectBestColorModeCallUsesState> {
+        [[nodiscard]] auto thenExpectBestColorModeCallUses(ui::RenderIntent intent) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile,
+                        getBestColorMode(getInstance()->mLayer1.mLayerFEState.dataspace, intent, _,
+                                         _, _));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Tests call one of these two helper member functions to start using the
+    // mini-DSL defined above.
+    [[nodiscard]] auto verify() { return IfDataspaceChosenState::make(this); }
+};
+
+TEST_F(OutputUpdateColorProfile_AffectsChosenRenderIntentTest,
+       Managed_NonHdr_Prefers_Colorimetric) {
+    verify().ifDataspaceChosenIsNonHdr()
+            .andOutputColorSettingIs(OutputColorSetting::kManaged)
+            .thenExpectBestColorModeCallUses(ui::RenderIntent::COLORIMETRIC)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfile_AffectsChosenRenderIntentTest,
+       Managed_Hdr_Prefers_ToneMapColorimetric) {
+    verify().ifDataspaceChosenIsHdr()
+            .andOutputColorSettingIs(OutputColorSetting::kManaged)
+            .thenExpectBestColorModeCallUses(ui::RenderIntent::TONE_MAP_COLORIMETRIC)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfile_AffectsChosenRenderIntentTest, Enhanced_NonHdr_Prefers_Enhance) {
+    verify().ifDataspaceChosenIsNonHdr()
+            .andOutputColorSettingIs(OutputColorSetting::kEnhanced)
+            .thenExpectBestColorModeCallUses(ui::RenderIntent::ENHANCE)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfile_AffectsChosenRenderIntentTest,
+       Enhanced_Hdr_Prefers_ToneMapEnhance) {
+    verify().ifDataspaceChosenIsHdr()
+            .andOutputColorSettingIs(OutputColorSetting::kEnhanced)
+            .thenExpectBestColorModeCallUses(ui::RenderIntent::TONE_MAP_ENHANCE)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfile_AffectsChosenRenderIntentTest, Vendor_NonHdr_Prefers_Vendor) {
+    verify().ifDataspaceChosenIsNonHdr()
+            .andOutputColorSettingIs(kVendorSpecifiedOutputColorSetting)
+            .thenExpectBestColorModeCallUses(
+                    static_cast<ui::RenderIntent>(kVendorSpecifiedOutputColorSetting))
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfile_AffectsChosenRenderIntentTest, Vendor_Hdr_Prefers_Vendor) {
+    verify().ifDataspaceChosenIsHdr()
+            .andOutputColorSettingIs(kVendorSpecifiedOutputColorSetting)
+            .thenExpectBestColorModeCallUses(
+                    static_cast<ui::RenderIntent>(kVendorSpecifiedOutputColorSetting))
+            .execute();
+}
 
 /*
  * Output::beginFrame()
  */
 
+struct OutputBeginFrameTest : public ::testing::Test {
+    using TestType = OutputBeginFrameTest;
+
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // Sets up the helper functions called by the function under test to use
+        // mock implementations.
+        MOCK_CONST_METHOD1(getDirtyRegion, Region(bool));
+    };
+
+    OutputBeginFrameTest() {
+        mOutput.setDisplayColorProfileForTest(
+                std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
+        mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+    }
+
+    struct IfGetDirtyRegionExpectationState
+          : public CallOrderStateMachineHelper<TestType, IfGetDirtyRegionExpectationState> {
+        [[nodiscard]] auto ifGetDirtyRegionReturns(Region dirtyRegion) {
+            EXPECT_CALL(getInstance()->mOutput, getDirtyRegion(false))
+                    .WillOnce(Return(dirtyRegion));
+            return nextState<AndIfGetOutputLayerCountExpectationState>();
+        }
+    };
+
+    struct AndIfGetOutputLayerCountExpectationState
+          : public CallOrderStateMachineHelper<TestType, AndIfGetOutputLayerCountExpectationState> {
+        [[nodiscard]] auto andIfGetOutputLayerCountReturns(size_t layerCount) {
+            EXPECT_CALL(getInstance()->mOutput, getOutputLayerCount()).WillOnce(Return(layerCount));
+            return nextState<AndIfLastCompositionHadVisibleLayersState>();
+        }
+    };
+
+    struct AndIfLastCompositionHadVisibleLayersState
+          : public CallOrderStateMachineHelper<TestType,
+                                               AndIfLastCompositionHadVisibleLayersState> {
+        [[nodiscard]] auto andIfLastCompositionHadVisibleLayersIs(bool hadOutputLayers) {
+            getInstance()->mOutput.mState.lastCompositionHadVisibleLayers = hadOutputLayers;
+            return nextState<ThenExpectRenderSurfaceBeginFrameCallState>();
+        }
+    };
+
+    struct ThenExpectRenderSurfaceBeginFrameCallState
+          : public CallOrderStateMachineHelper<TestType,
+                                               ThenExpectRenderSurfaceBeginFrameCallState> {
+        [[nodiscard]] auto thenExpectRenderSurfaceBeginFrameCall(bool mustRecompose) {
+            EXPECT_CALL(*getInstance()->mRenderSurface, beginFrame(mustRecompose));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    struct ExecuteState : public CallOrderStateMachineHelper<TestType, ExecuteState> {
+        [[nodiscard]] auto execute() {
+            getInstance()->mOutput.beginFrame();
+            return nextState<CheckPostconditionHadVisibleLayersState>();
+        }
+    };
+
+    struct CheckPostconditionHadVisibleLayersState
+          : public CallOrderStateMachineHelper<TestType, CheckPostconditionHadVisibleLayersState> {
+        void checkPostconditionHadVisibleLayers(bool expected) {
+            EXPECT_EQ(expected, getInstance()->mOutput.mState.lastCompositionHadVisibleLayers);
+        }
+    };
+
+    // Tests call one of these two helper member functions to start using the
+    // mini-DSL defined above.
+    [[nodiscard]] auto verify() { return IfGetDirtyRegionExpectationState::make(this); }
+
+    static const Region kEmptyRegion;
+    static const Region kNotEmptyRegion;
+
+    mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock<mock::DisplayColorProfile>();
+    mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
+    StrictMock<OutputPartialMock> mOutput;
+};
+
+const Region OutputBeginFrameTest::kEmptyRegion{Rect{0, 0, 0, 0}};
+const Region OutputBeginFrameTest::kNotEmptyRegion{Rect{0, 0, 1, 1}};
+
+TEST_F(OutputBeginFrameTest, hasDirtyHasLayersHadLayersLastFrame) {
+    verify().ifGetDirtyRegionReturns(kNotEmptyRegion)
+            .andIfGetOutputLayerCountReturns(1u)
+            .andIfLastCompositionHadVisibleLayersIs(true)
+            .thenExpectRenderSurfaceBeginFrameCall(true)
+            .execute()
+            .checkPostconditionHadVisibleLayers(true);
+}
+
+TEST_F(OutputBeginFrameTest, hasDirtyNotHasLayersHadLayersLastFrame) {
+    verify().ifGetDirtyRegionReturns(kNotEmptyRegion)
+            .andIfGetOutputLayerCountReturns(0u)
+            .andIfLastCompositionHadVisibleLayersIs(true)
+            .thenExpectRenderSurfaceBeginFrameCall(true)
+            .execute()
+            .checkPostconditionHadVisibleLayers(false);
+}
+
+TEST_F(OutputBeginFrameTest, hasDirtyHasLayersNotHadLayersLastFrame) {
+    verify().ifGetDirtyRegionReturns(kNotEmptyRegion)
+            .andIfGetOutputLayerCountReturns(1u)
+            .andIfLastCompositionHadVisibleLayersIs(false)
+            .thenExpectRenderSurfaceBeginFrameCall(true)
+            .execute()
+            .checkPostconditionHadVisibleLayers(true);
+}
+
+TEST_F(OutputBeginFrameTest, hasDirtyNotHasLayersNotHadLayersLastFrame) {
+    verify().ifGetDirtyRegionReturns(kNotEmptyRegion)
+            .andIfGetOutputLayerCountReturns(0u)
+            .andIfLastCompositionHadVisibleLayersIs(false)
+            .thenExpectRenderSurfaceBeginFrameCall(false)
+            .execute()
+            .checkPostconditionHadVisibleLayers(false);
+}
+
+TEST_F(OutputBeginFrameTest, notHasDirtyHasLayersHadLayersLastFrame) {
+    verify().ifGetDirtyRegionReturns(kEmptyRegion)
+            .andIfGetOutputLayerCountReturns(1u)
+            .andIfLastCompositionHadVisibleLayersIs(true)
+            .thenExpectRenderSurfaceBeginFrameCall(false)
+            .execute()
+            .checkPostconditionHadVisibleLayers(true);
+}
+
+TEST_F(OutputBeginFrameTest, notHasDirtyNotHasLayersHadLayersLastFrame) {
+    verify().ifGetDirtyRegionReturns(kEmptyRegion)
+            .andIfGetOutputLayerCountReturns(0u)
+            .andIfLastCompositionHadVisibleLayersIs(true)
+            .thenExpectRenderSurfaceBeginFrameCall(false)
+            .execute()
+            .checkPostconditionHadVisibleLayers(true);
+}
+
+TEST_F(OutputBeginFrameTest, notHasDirtyHasLayersNotHadLayersLastFrame) {
+    verify().ifGetDirtyRegionReturns(kEmptyRegion)
+            .andIfGetOutputLayerCountReturns(1u)
+            .andIfLastCompositionHadVisibleLayersIs(false)
+            .thenExpectRenderSurfaceBeginFrameCall(false)
+            .execute()
+            .checkPostconditionHadVisibleLayers(false);
+}
+
+TEST_F(OutputBeginFrameTest, notHasDirtyNotHasLayersNotHadLayersLastFrame) {
+    verify().ifGetDirtyRegionReturns(kEmptyRegion)
+            .andIfGetOutputLayerCountReturns(0u)
+            .andIfLastCompositionHadVisibleLayersIs(false)
+            .thenExpectRenderSurfaceBeginFrameCall(false)
+            .execute()
+            .checkPostconditionHadVisibleLayers(false);
+}
+
 /*
  * Output::devOptRepaintFlash()
  */
 
-// TODO(b/144060211) - Add coverage
+struct OutputDevOptRepaintFlashTest : public testing::Test {
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // Sets up the helper functions called by the function under test to use
+        // mock implementations.
+        MOCK_CONST_METHOD1(getDirtyRegion, Region(bool));
+        MOCK_METHOD1(composeSurfaces, std::optional<base::unique_fd>(const Region&));
+        MOCK_METHOD0(postFramebuffer, void());
+        MOCK_METHOD0(prepareFrame, void());
+    };
+
+    OutputDevOptRepaintFlashTest() {
+        mOutput.setDisplayColorProfileForTest(
+                std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
+        mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+    }
+
+    static const Region kEmptyRegion;
+    static const Region kNotEmptyRegion;
+
+    StrictMock<OutputPartialMock> mOutput;
+    mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock<mock::DisplayColorProfile>();
+    mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
+    CompositionRefreshArgs mRefreshArgs;
+};
+
+const Region OutputDevOptRepaintFlashTest::kEmptyRegion{Rect{0, 0, 0, 0}};
+const Region OutputDevOptRepaintFlashTest::kNotEmptyRegion{Rect{0, 0, 1, 1}};
+
+TEST_F(OutputDevOptRepaintFlashTest, doesNothingIfFlashDelayNotSet) {
+    mRefreshArgs.devOptFlashDirtyRegionsDelay = {};
+    mRefreshArgs.repaintEverything = true;
+    mOutput.mState.isEnabled = true;
+
+    mOutput.devOptRepaintFlash(mRefreshArgs);
+}
+
+TEST_F(OutputDevOptRepaintFlashTest, postsAndPreparesANewFrameIfNotEnabled) {
+    mRefreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::microseconds(1);
+    mRefreshArgs.repaintEverything = true;
+    mOutput.mState.isEnabled = false;
+
+    InSequence seq;
+    EXPECT_CALL(mOutput, postFramebuffer());
+    EXPECT_CALL(mOutput, prepareFrame());
+
+    mOutput.devOptRepaintFlash(mRefreshArgs);
+}
+
+TEST_F(OutputDevOptRepaintFlashTest, postsAndPreparesANewFrameIfNotDirty) {
+    mRefreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::microseconds(1);
+    mRefreshArgs.repaintEverything = true;
+    mOutput.mState.isEnabled = true;
+
+    InSequence seq;
+    EXPECT_CALL(mOutput, getDirtyRegion(true)).WillOnce(Return(kEmptyRegion));
+    EXPECT_CALL(mOutput, postFramebuffer());
+    EXPECT_CALL(mOutput, prepareFrame());
+
+    mOutput.devOptRepaintFlash(mRefreshArgs);
+}
+
+TEST_F(OutputDevOptRepaintFlashTest, alsoComposesSurfacesAndQueuesABufferIfDirty) {
+    mRefreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::microseconds(1);
+    mRefreshArgs.repaintEverything = false;
+    mOutput.mState.isEnabled = true;
+
+    InSequence seq;
+    EXPECT_CALL(mOutput, getDirtyRegion(false)).WillOnce(Return(kNotEmptyRegion));
+    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(kNotEmptyRegion)));
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_));
+    EXPECT_CALL(mOutput, postFramebuffer());
+    EXPECT_CALL(mOutput, prepareFrame());
+
+    mOutput.devOptRepaintFlash(mRefreshArgs);
+}
 
 /*
  * Output::finishFrame()
  */
 
-// TODO(b/144060211) - Add coverage
+struct OutputFinishFrameTest : public testing::Test {
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // Sets up the helper functions called by the function under test to use
+        // mock implementations.
+        MOCK_METHOD1(composeSurfaces, std::optional<base::unique_fd>(const Region&));
+        MOCK_METHOD0(postFramebuffer, void());
+    };
+
+    OutputFinishFrameTest() {
+        mOutput.setDisplayColorProfileForTest(
+                std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
+        mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+    }
+
+    StrictMock<OutputPartialMock> mOutput;
+    mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock<mock::DisplayColorProfile>();
+    mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
+    CompositionRefreshArgs mRefreshArgs;
+};
+
+TEST_F(OutputFinishFrameTest, ifNotEnabledDoesNothing) {
+    mOutput.mState.isEnabled = false;
+
+    mOutput.finishFrame(mRefreshArgs);
+}
+
+TEST_F(OutputFinishFrameTest, takesEarlyOutifComposeSurfacesReturnsNoFence) {
+    mOutput.mState.isEnabled = true;
+
+    InSequence seq;
+    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION)));
+
+    mOutput.finishFrame(mRefreshArgs);
+}
+
+TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFence) {
+    mOutput.mState.isEnabled = true;
+
+    InSequence seq;
+    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION)))
+            .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_));
+
+    mOutput.finishFrame(mRefreshArgs);
+}
 
 /*
  * Output::postFramebuffer()
  */
 
-// TODO(b/144060211) - Add coverage
+struct OutputPostFramebufferTest : public testing::Test {
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // Sets up the helper functions called by the function under test to use
+        // mock implementations.
+        MOCK_METHOD0(presentAndGetFrameFences, compositionengine::Output::FrameFences());
+    };
+
+    struct Layer {
+        Layer() {
+            EXPECT_CALL(outputLayer, getLayerFE()).WillRepeatedly(ReturnRef(layerFE));
+            EXPECT_CALL(outputLayer, getHwcLayer()).WillRepeatedly(Return(&hwc2Layer));
+        }
+
+        StrictMock<mock::OutputLayer> outputLayer;
+        StrictMock<mock::LayerFE> layerFE;
+        StrictMock<HWC2::mock::Layer> hwc2Layer;
+    };
+
+    OutputPostFramebufferTest() {
+        mOutput.setDisplayColorProfileForTest(
+                std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
+        mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(3u));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u))
+                .WillRepeatedly(Return(&mLayer1.outputLayer));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1u))
+                .WillRepeatedly(Return(&mLayer2.outputLayer));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(2u))
+                .WillRepeatedly(Return(&mLayer3.outputLayer));
+    }
+
+    StrictMock<OutputPartialMock> mOutput;
+    mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock<mock::DisplayColorProfile>();
+    mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
+
+    Layer mLayer1;
+    Layer mLayer2;
+    Layer mLayer3;
+};
+
+TEST_F(OutputPostFramebufferTest, ifNotEnabledDoesNothing) {
+    mOutput.mState.isEnabled = false;
+
+    mOutput.postFramebuffer();
+}
+
+TEST_F(OutputPostFramebufferTest, ifEnabledMustFlipThenPresentThenSendPresentCompleted) {
+    mOutput.mState.isEnabled = true;
+
+    compositionengine::Output::FrameFences frameFences;
+
+    // This should happen even if there are no output layers.
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+
+    // For this test in particular we want to make sure the call expectations
+    // setup below are satisfied in the specific order.
+    InSequence seq;
+
+    EXPECT_CALL(*mRenderSurface, flip());
+    EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    mOutput.postFramebuffer();
+}
+
+TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) {
+    // Simulate getting release fences from each layer, and ensure they are passed to the
+    // front-end layer interface for each layer correctly.
+
+    mOutput.mState.isEnabled = true;
+
+    // Create three unique fence instances
+    sp<Fence> layer1Fence = new Fence();
+    sp<Fence> layer2Fence = new Fence();
+    sp<Fence> layer3Fence = new Fence();
+
+    Output::FrameFences frameFences;
+    frameFences.layerFences.emplace(&mLayer1.hwc2Layer, layer1Fence);
+    frameFences.layerFences.emplace(&mLayer2.hwc2Layer, layer2Fence);
+    frameFences.layerFences.emplace(&mLayer3.hwc2Layer, layer3Fence);
+
+    EXPECT_CALL(*mRenderSurface, flip());
+    EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    // Compare the pointers values of each fence to make sure the correct ones
+    // 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,
+                onLayerDisplayed(Property(&sp<Fence>::get, Eq(layer1Fence.get()))));
+    EXPECT_CALL(mLayer2.layerFE,
+                onLayerDisplayed(Property(&sp<Fence>::get, Eq(layer2Fence.get()))));
+    EXPECT_CALL(mLayer3.layerFE,
+                onLayerDisplayed(Property(&sp<Fence>::get, Eq(layer3Fence.get()))));
+
+    mOutput.postFramebuffer();
+}
+
+TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) {
+    mOutput.mState.isEnabled = true;
+    mOutput.mState.usesClientComposition = true;
+
+    sp<Fence> clientTargetAcquireFence = new Fence();
+    sp<Fence> layer1Fence = new Fence();
+    sp<Fence> layer2Fence = new Fence();
+    sp<Fence> layer3Fence = new Fence();
+    Output::FrameFences frameFences;
+    frameFences.clientTargetAcquireFence = clientTargetAcquireFence;
+    frameFences.layerFences.emplace(&mLayer1.hwc2Layer, layer1Fence);
+    frameFences.layerFences.emplace(&mLayer2.hwc2Layer, layer2Fence);
+    frameFences.layerFences.emplace(&mLayer3.hwc2Layer, layer3Fence);
+
+    EXPECT_CALL(*mRenderSurface, flip());
+    EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    // 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));
+
+    mOutput.postFramebuffer();
+}
+
+TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) {
+    mOutput.mState.isEnabled = true;
+    mOutput.mState.usesClientComposition = true;
+
+    // This should happen even if there are no (current) output layers.
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+
+    // Load up the released layers with some mock instances
+    sp<StrictMock<mock::LayerFE>> releasedLayer1{new StrictMock<mock::LayerFE>()};
+    sp<StrictMock<mock::LayerFE>> releasedLayer2{new StrictMock<mock::LayerFE>()};
+    sp<StrictMock<mock::LayerFE>> releasedLayer3{new StrictMock<mock::LayerFE>()};
+    Output::ReleasedLayers layers;
+    layers.push_back(releasedLayer1);
+    layers.push_back(releasedLayer2);
+    layers.push_back(releasedLayer3);
+    mOutput.setReleasedLayers(std::move(layers));
+
+    // Set up a fake present fence
+    sp<Fence> presentFence = new Fence();
+    Output::FrameFences frameFences;
+    frameFences.presentFence = presentFence;
+
+    EXPECT_CALL(*mRenderSurface, flip());
+    EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    // Each released layer should be given the presentFence.
+    EXPECT_CALL(*releasedLayer1,
+                onLayerDisplayed(Property(&sp<Fence>::get, Eq(presentFence.get()))));
+    EXPECT_CALL(*releasedLayer2,
+                onLayerDisplayed(Property(&sp<Fence>::get, Eq(presentFence.get()))));
+    EXPECT_CALL(*releasedLayer3,
+                onLayerDisplayed(Property(&sp<Fence>::get, Eq(presentFence.get()))));
+
+    mOutput.postFramebuffer();
+
+    // After the call the list of released layers should have been cleared.
+    EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
+}
 
 /*
  * Output::composeSurfaces()
  */
 
 struct OutputComposeSurfacesTest : public testing::Test {
-    static constexpr uint32_t kDefaultOutputOrientation = TR_IDENT;
-    static constexpr ui::Dataspace kDefaultOutputDataspace = ui::Dataspace::DISPLAY_P3;
-
-    static const Rect kDefaultOutputFrame;
-    static const Rect kDefaultOutputViewport;
-    static const Rect kDefaultOutputScissor;
-    static const mat4 kDefaultColorTransformMat;
+    using TestType = OutputComposeSurfacesTest;
 
     struct OutputPartialMock : public OutputPartialMockBase {
-        // Sets up the helper functions called by composeSurfaces to use a mock
-        // implementations.
+        // Sets up the helper functions called by the function under test to use
+        // mock implementations.
         MOCK_CONST_METHOD0(getSkipColorTransform, bool());
-        MOCK_METHOD2(generateClientCompositionRequests,
-                     std::vector<renderengine::LayerSettings>(bool, Region&));
+        MOCK_METHOD3(generateClientCompositionRequests,
+                     std::vector<renderengine::LayerSettings>(bool, Region&, ui::Dataspace));
         MOCK_METHOD2(appendRegionFlashRequests,
                      void(const Region&, std::vector<renderengine::LayerSettings>&));
         MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
@@ -710,80 +2687,404 @@
                 std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
         mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
 
-        mOutput.editState().frame = kDefaultOutputFrame;
-        mOutput.editState().viewport = kDefaultOutputViewport;
-        mOutput.editState().scissor = kDefaultOutputScissor;
-        mOutput.editState().transform = ui::Transform{kDefaultOutputOrientation};
-        mOutput.editState().orientation = kDefaultOutputOrientation;
-        mOutput.editState().dataspace = kDefaultOutputDataspace;
-        mOutput.editState().colorTransformMatrix = kDefaultColorTransformMat;
-        mOutput.editState().isSecure = true;
-        mOutput.editState().needsFiltering = false;
-        mOutput.editState().usesClientComposition = true;
-        mOutput.editState().usesDeviceComposition = false;
+        mOutput.mState.frame = kDefaultOutputFrame;
+        mOutput.mState.viewport = kDefaultOutputViewport;
+        mOutput.mState.scissor = kDefaultOutputScissor;
+        mOutput.mState.transform = ui::Transform{kDefaultOutputOrientation};
+        mOutput.mState.orientation = kDefaultOutputOrientation;
+        mOutput.mState.dataspace = kDefaultOutputDataspace;
+        mOutput.mState.colorTransformMatrix = kDefaultColorTransformMat;
+        mOutput.mState.isSecure = false;
+        mOutput.mState.needsFiltering = false;
+        mOutput.mState.usesClientComposition = true;
+        mOutput.mState.usesDeviceComposition = false;
 
-        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u));
-        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u))
-                .WillRepeatedly(Return(&mOutputLayer1));
-        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1u))
-                .WillRepeatedly(Return(&mOutputLayer2));
         EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
         EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
+        EXPECT_CALL(mCompositionEngine, getTimeStats())
+                .WillRepeatedly(ReturnRef(*mTimeStats.get()));
+        EXPECT_CALL(*mDisplayColorProfile, getHdrCapabilities())
+                .WillRepeatedly(ReturnRef(kHdrCapabilities));
     }
 
+    struct ExecuteState : public CallOrderStateMachineHelper<TestType, ExecuteState> {
+        auto execute() {
+            getInstance()->mReadyFence = getInstance()->mOutput.composeSurfaces(kDebugRegion);
+            return nextState<FenceCheckState>();
+        }
+    };
+
+    struct FenceCheckState : public CallOrderStateMachineHelper<TestType, FenceCheckState> {
+        void expectNoFenceWasReturned() { EXPECT_FALSE(getInstance()->mReadyFence); }
+
+        void expectAFenceWasReturned() { EXPECT_TRUE(getInstance()->mReadyFence); }
+    };
+
+    // Call this member function to start using the mini-DSL defined above.
+    [[nodiscard]] auto verify() { return ExecuteState::make(this); }
+
+    static constexpr uint32_t kDefaultOutputOrientation = TR_IDENT;
+    static constexpr ui::Dataspace kDefaultOutputDataspace = ui::Dataspace::UNKNOWN;
+    static constexpr ui::Dataspace kExpensiveOutputDataspace = ui::Dataspace::DISPLAY_P3;
+    static constexpr float kDefaultMaxLuminance = 0.9f;
+    static constexpr float kDefaultAvgLuminance = 0.7f;
+    static constexpr float kDefaultMinLuminance = 0.1f;
+
+    static const Rect kDefaultOutputFrame;
+    static const Rect kDefaultOutputViewport;
+    static const Rect kDefaultOutputScissor;
+    static const mat4 kDefaultColorTransformMat;
+
+    static const Region kDebugRegion;
+    static const HdrCapabilities kHdrCapabilities;
+
     StrictMock<mock::CompositionEngine> mCompositionEngine;
     StrictMock<renderengine::mock::RenderEngine> mRenderEngine;
+    // TODO: make this is a proper mock.
+    std::shared_ptr<TimeStats> mTimeStats = std::make_shared<android::impl::TimeStats>();
     mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock<mock::DisplayColorProfile>();
     mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
-    StrictMock<mock::OutputLayer> mOutputLayer1;
-    StrictMock<mock::OutputLayer> mOutputLayer2;
     StrictMock<OutputPartialMock> mOutput;
     sp<GraphicBuffer> mOutputBuffer = new GraphicBuffer();
+
+    std::optional<base::unique_fd> mReadyFence;
 };
 
 const Rect OutputComposeSurfacesTest::kDefaultOutputFrame{1001, 1002, 1003, 1004};
 const Rect OutputComposeSurfacesTest::kDefaultOutputViewport{1005, 1006, 1007, 1008};
 const Rect OutputComposeSurfacesTest::kDefaultOutputScissor{1009, 1010, 1011, 1012};
 const mat4 OutputComposeSurfacesTest::kDefaultColorTransformMat{mat4() * 0.5};
-
-// TODO(b/121291683): Expand unit test coverage for composeSurfaces beyond these
-// basic tests.
+const Region OutputComposeSurfacesTest::kDebugRegion{Rect{100, 101, 102, 103}};
+const HdrCapabilities OutputComposeSurfacesTest::
+        kHdrCapabilities{{},
+                         OutputComposeSurfacesTest::kDefaultMaxLuminance,
+                         OutputComposeSurfacesTest::kDefaultAvgLuminance,
+                         OutputComposeSurfacesTest::kDefaultMinLuminance};
 
 TEST_F(OutputComposeSurfacesTest, doesNothingIfNoClientComposition) {
-    mOutput.editState().usesClientComposition = false;
+    mOutput.mState.usesClientComposition = false;
 
-    Region debugRegion;
-    std::optional<base::unique_fd> readyFence = mOutput.composeSurfaces(debugRegion);
-    EXPECT_TRUE(readyFence);
+    verify().execute().expectAFenceWasReturned();
 }
 
-TEST_F(OutputComposeSurfacesTest, worksIfNoClientLayersQueued) {
-    const Region kDebugRegion{Rect{100, 101, 102, 103}};
+TEST_F(OutputComposeSurfacesTest, doesMinimalWorkIfDequeueBufferFails) {
+    EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+            .WillRepeatedly(Return(std::vector<renderengine::LayerSettings>{}));
+    EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+            .WillRepeatedly(Return());
 
-    constexpr float kDefaultMaxLuminance = 1.0f;
-    constexpr float kDefaultAvgLuminance = 0.7f;
-    constexpr float kDefaultMinLuminance = 0.1f;
-    HdrCapabilities HdrCapabilities{{},
-                                    kDefaultMaxLuminance,
-                                    kDefaultAvgLuminance,
-                                    kDefaultMinLuminance};
+    EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillOnce(Return(nullptr));
 
-    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillOnce(Return(false));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, true, _, _)).Times(1);
+    verify().execute().expectNoFenceWasReturned();
+}
 
-    EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillOnce(Return(true));
-    EXPECT_CALL(*mDisplayColorProfile, getHdrCapabilities()).WillOnce(ReturnRef(HdrCapabilities));
+TEST_F(OutputComposeSurfacesTest, handlesZeroCompositionRequests) {
+    EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+            .WillRepeatedly(Return(std::vector<renderengine::LayerSettings>{}));
+    EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+            .WillRepeatedly(Return());
 
-    EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillOnce(Return(mOutputBuffer));
+    EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, IsEmpty(), _, true, _, _))
+            .WillRepeatedly(Return(NO_ERROR));
 
-    EXPECT_CALL(mOutput, getSkipColorTransform()).WillOnce(Return(false));
-    EXPECT_CALL(mOutput, generateClientCompositionRequests(false, _)).Times(1);
-    EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _)).Times(1);
-    EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true)).Times(1);
-    EXPECT_CALL(mOutput, setExpensiveRenderingExpected(false)).Times(1);
+    verify().execute().expectAFenceWasReturned();
+}
 
-    std::optional<base::unique_fd> readyFence = mOutput.composeSurfaces(kDebugRegion);
-    EXPECT_TRUE(readyFence);
+TEST_F(OutputComposeSurfacesTest, buildsAndRendersRequestList) {
+    renderengine::LayerSettings r1;
+    renderengine::LayerSettings r2;
+
+    r1.geometry.boundaries = FloatRect{1, 2, 3, 4};
+    r2.geometry.boundaries = FloatRect{5, 6, 7, 8};
+
+    EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+            .WillRepeatedly(Return(std::vector<renderengine::LayerSettings>{r1}));
+    EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+            .WillRepeatedly(
+                    Invoke([&](const Region&,
+                               std::vector<renderengine::LayerSettings>& clientCompositionLayers) {
+                        clientCompositionLayers.emplace_back(r2);
+                    }));
+
+    EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAreArray({r1, r2}), _, true, _, _))
+            .WillRepeatedly(Return(NO_ERROR));
+
+    verify().execute().expectAFenceWasReturned();
+}
+
+struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComposeSurfacesTest {
+    OutputComposeSurfacesTest_UsesExpectedDisplaySettings() {
+        EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+        EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+                .WillRepeatedly(Return(std::vector<renderengine::LayerSettings>{}));
+        EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+                .WillRepeatedly(Return());
+        EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
+    }
+
+    struct MixedCompositionState
+          : public CallOrderStateMachineHelper<TestType, MixedCompositionState> {
+        auto ifMixedCompositionIs(bool used) {
+            getInstance()->mOutput.mState.usesDeviceComposition = used;
+            return nextState<OutputUsesHdrState>();
+        }
+    };
+
+    struct OutputUsesHdrState : public CallOrderStateMachineHelper<TestType, OutputUsesHdrState> {
+        auto andIfUsesHdr(bool used) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile, hasWideColorGamut())
+                    .WillOnce(Return(used));
+            return nextState<SkipColorTransformState>();
+        }
+    };
+
+    struct SkipColorTransformState
+          : public CallOrderStateMachineHelper<TestType, SkipColorTransformState> {
+        auto andIfSkipColorTransform(bool skip) {
+            // May be called zero or one times.
+            EXPECT_CALL(getInstance()->mOutput, getSkipColorTransform())
+                    .WillRepeatedly(Return(skip));
+            return nextState<ExpectDisplaySettingsState>();
+        }
+    };
+
+    struct ExpectDisplaySettingsState
+          : public CallOrderStateMachineHelper<TestType, ExpectDisplaySettingsState> {
+        auto thenExpectDisplaySettingsUsed(renderengine::DisplaySettings settings) {
+            EXPECT_CALL(getInstance()->mRenderEngine, drawLayers(settings, _, _, true, _, _))
+                    .WillOnce(Return(NO_ERROR));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Call this member function to start using the mini-DSL defined above.
+    [[nodiscard]] auto verify() { return MixedCompositionState::make(this); }
+};
+
+TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedComposition) {
+    verify().ifMixedCompositionIs(true)
+            .andIfUsesHdr(true)
+            .andIfSkipColorTransform(false)
+            .thenExpectDisplaySettingsUsed({kDefaultOutputScissor, kDefaultOutputScissor, mat4(),
+                                            kDefaultMaxLuminance, kDefaultOutputDataspace, mat4(),
+                                            Region::INVALID_REGION, kDefaultOutputOrientation})
+            .execute()
+            .expectAFenceWasReturned();
+}
+
+TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrMixedComposition) {
+    verify().ifMixedCompositionIs(true)
+            .andIfUsesHdr(false)
+            .andIfSkipColorTransform(false)
+            .thenExpectDisplaySettingsUsed({kDefaultOutputScissor, kDefaultOutputScissor, mat4(),
+                                            kDefaultMaxLuminance, kDefaultOutputDataspace, mat4(),
+                                            Region::INVALID_REGION, kDefaultOutputOrientation})
+            .execute()
+            .expectAFenceWasReturned();
+}
+
+TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrOnlyClientComposition) {
+    verify().ifMixedCompositionIs(false)
+            .andIfUsesHdr(true)
+            .andIfSkipColorTransform(false)
+            .thenExpectDisplaySettingsUsed({kDefaultOutputScissor, kDefaultOutputScissor, mat4(),
+                                            kDefaultMaxLuminance, kDefaultOutputDataspace,
+                                            kDefaultColorTransformMat, Region::INVALID_REGION,
+                                            kDefaultOutputOrientation})
+            .execute()
+            .expectAFenceWasReturned();
+}
+
+TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrOnlyClientComposition) {
+    verify().ifMixedCompositionIs(false)
+            .andIfUsesHdr(false)
+            .andIfSkipColorTransform(false)
+            .thenExpectDisplaySettingsUsed({kDefaultOutputScissor, kDefaultOutputScissor, mat4(),
+                                            kDefaultMaxLuminance, kDefaultOutputDataspace,
+                                            kDefaultColorTransformMat, Region::INVALID_REGION,
+                                            kDefaultOutputOrientation})
+            .execute()
+            .expectAFenceWasReturned();
+}
+
+TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
+       usesExpectedDisplaySettingsForHdrOnlyClientCompositionWithSkipClientTransform) {
+    verify().ifMixedCompositionIs(false)
+            .andIfUsesHdr(true)
+            .andIfSkipColorTransform(true)
+            .thenExpectDisplaySettingsUsed({kDefaultOutputScissor, kDefaultOutputScissor, mat4(),
+                                            kDefaultMaxLuminance, kDefaultOutputDataspace, mat4(),
+                                            Region::INVALID_REGION, kDefaultOutputOrientation})
+            .execute()
+            .expectAFenceWasReturned();
+}
+
+struct OutputComposeSurfacesTest_HandlesProtectedContent : public OutputComposeSurfacesTest {
+    struct Layer {
+        Layer() {
+            EXPECT_CALL(mOutputLayer, getLayer()).WillRepeatedly(ReturnRef(mLayer));
+            EXPECT_CALL(mLayer, getFEState()).WillRepeatedly(ReturnRef(mLayerFEState));
+        }
+
+        StrictMock<mock::OutputLayer> mOutputLayer;
+        StrictMock<mock::Layer> mLayer;
+        LayerFECompositionState mLayerFEState;
+    };
+
+    OutputComposeSurfacesTest_HandlesProtectedContent() {
+        mLayer1.mLayerFEState.hasProtectedContent = false;
+        mLayer2.mLayerFEState.hasProtectedContent = false;
+
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u))
+                .WillRepeatedly(Return(&mLayer1.mOutputLayer));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1u))
+                .WillRepeatedly(Return(&mLayer2.mOutputLayer));
+
+        EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+
+        EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+
+        EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, _))
+                .WillRepeatedly(Return(std::vector<renderengine::LayerSettings>{}));
+        EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+                .WillRepeatedly(Return());
+        EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
+        EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, true, _, _))
+                .WillRepeatedly(Return(NO_ERROR));
+    }
+
+    Layer mLayer1;
+    Layer mLayer2;
+};
+
+TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifDisplayIsNotSecure) {
+    mOutput.mState.isSecure = false;
+    mLayer2.mLayerFEState.hasProtectedContent = true;
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
+
+    mOutput.composeSurfaces(kDebugRegion);
+}
+
+TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifRenderEngineDoesNotSupportIt) {
+    mOutput.mState.isSecure = true;
+    mLayer2.mLayerFEState.hasProtectedContent = true;
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+
+    mOutput.composeSurfaces(kDebugRegion);
+}
+
+TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNoProtectedContentLayers) {
+    mOutput.mState.isSecure = true;
+    mLayer2.mLayerFEState.hasProtectedContent = false;
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)).WillOnce(Return(false));
+    EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
+    EXPECT_CALL(mRenderEngine, useProtectedContext(false));
+    EXPECT_CALL(*mRenderSurface, setProtected(false));
+
+    mOutput.composeSurfaces(kDebugRegion);
+}
+
+TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNotEnabled) {
+    mOutput.mState.isSecure = true;
+    mLayer2.mLayerFEState.hasProtectedContent = true;
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
+
+    // For this test, we also check the call order of key functions.
+    InSequence seq;
+
+    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false));
+    EXPECT_CALL(mRenderEngine, useProtectedContext(true));
+    EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
+    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true));
+    EXPECT_CALL(*mRenderSurface, setProtected(true));
+    // Must happen after setting the protected content state.
+    EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, true, _, _)).WillOnce(Return(NO_ERROR));
+
+    mOutput.composeSurfaces(kDebugRegion);
+}
+
+TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledEverywhere) {
+    mOutput.mState.isSecure = true;
+    mLayer2.mLayerFEState.hasProtectedContent = true;
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true));
+    EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
+
+    mOutput.composeSurfaces(kDebugRegion);
+}
+
+TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifFailsToEnableInRenderEngine) {
+    mOutput.mState.isSecure = true;
+    mLayer2.mLayerFEState.hasProtectedContent = true;
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false)).WillOnce(Return(false));
+    EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
+    EXPECT_CALL(mRenderEngine, useProtectedContext(true));
+
+    mOutput.composeSurfaces(kDebugRegion);
+}
+
+TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderEngine) {
+    mOutput.mState.isSecure = true;
+    mLayer2.mLayerFEState.hasProtectedContent = true;
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)).WillOnce(Return(true));
+    EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
+    EXPECT_CALL(*mRenderSurface, setProtected(true));
+
+    mOutput.composeSurfaces(kDebugRegion);
+}
+
+TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderSurface) {
+    mOutput.mState.isSecure = true;
+    mLayer2.mLayerFEState.hasProtectedContent = true;
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false));
+    EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
+    EXPECT_CALL(mRenderEngine, useProtectedContext(true));
+
+    mOutput.composeSurfaces(kDebugRegion);
+}
+
+struct OutputComposeSurfacesTest_SetsExpensiveRendering : public OutputComposeSurfacesTest {
+    OutputComposeSurfacesTest_SetsExpensiveRendering() {
+        EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+        EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+        EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+        EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+                .WillRepeatedly(Return());
+        EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
+    }
+};
+
+TEST_F(OutputComposeSurfacesTest_SetsExpensiveRendering, IfExepensiveOutputDataspaceIsUsed) {
+    mOutput.mState.dataspace = kExpensiveOutputDataspace;
+
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kExpensiveOutputDataspace))
+            .WillOnce(Return(std::vector<renderengine::LayerSettings>{}));
+
+    // For this test, we also check the call order of key functions.
+    InSequence seq;
+
+    EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, true, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mOutput, setExpensiveRenderingExpected(false));
+
+    mOutput.composeSurfaces(kDebugRegion);
 }
 
 /*
@@ -794,13 +3095,33 @@
     struct OutputPartialMock : public OutputPartialMockBase {
         // compositionengine::Output overrides
         std::vector<renderengine::LayerSettings> generateClientCompositionRequests(
-                bool supportsProtectedContent, Region& clearRegion) override {
+                bool supportsProtectedContent, Region& clearRegion,
+                ui::Dataspace dataspace) override {
             return impl::Output::generateClientCompositionRequests(supportsProtectedContent,
-                                                                   clearRegion);
+                                                                   clearRegion, dataspace);
         }
     };
 
+    struct Layer {
+        Layer() {
+            EXPECT_CALL(mOutputLayer, getState()).WillRepeatedly(ReturnRef(mOutputLayerState));
+            EXPECT_CALL(mOutputLayer, editState()).WillRepeatedly(ReturnRef(mOutputLayerState));
+            EXPECT_CALL(mOutputLayer, getLayer()).WillRepeatedly(ReturnRef(mLayer));
+            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(mLayerFE));
+            EXPECT_CALL(mLayer, getFEState()).WillRepeatedly(ReturnRef(mLayerFEState));
+        }
+
+        StrictMock<mock::OutputLayer> mOutputLayer;
+        StrictMock<mock::Layer> mLayer;
+        StrictMock<mock::LayerFE> mLayerFE;
+        LayerFECompositionState mLayerFEState;
+        impl::OutputLayerCompositionState mOutputLayerState;
+        renderengine::LayerSettings mRELayerSettings;
+    };
+
     GenerateClientCompositionRequestsTest() {
+        mOutput.mState.needsFiltering = false;
+
         mOutput.setDisplayColorProfileForTest(
                 std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
         mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
@@ -811,218 +3132,507 @@
     StrictMock<OutputPartialMock> mOutput;
 };
 
-// TODO(b/121291683): Add more unit test coverage for generateClientCompositionRequests
+struct GenerateClientCompositionRequestsTest_ThreeLayers
+      : public GenerateClientCompositionRequestsTest {
+    GenerateClientCompositionRequestsTest_ThreeLayers() {
+        mOutput.mState.frame = kDisplayFrame;
+        mOutput.mState.viewport = kDisplayViewport;
+        mOutput.mState.scissor = kDisplayScissor;
+        mOutput.mState.transform = ui::Transform{kDisplayOrientation};
+        mOutput.mState.orientation = kDisplayOrientation;
+        mOutput.mState.needsFiltering = false;
+        mOutput.mState.isSecure = false;
 
-TEST_F(GenerateClientCompositionRequestsTest, worksForLandscapeModeSplitScreen) {
-    // In split-screen landscape mode, the screen is rotated 90 degrees, with
-    // one layer on the left covering the left side of the output, and one layer
-    // on the right covering that side of the output.
+        for (size_t i = 0; i < mLayers.size(); i++) {
+            mLayers[i].mOutputLayerState.clearClientTarget = false;
+            mLayers[i].mOutputLayerState.visibleRegion = Region(kDisplayFrame);
+            mLayers[i].mLayerFEState.isOpaque = true;
+            mLayers[i].mRELayerSettings.geometry.boundaries =
+                    FloatRect{static_cast<float>(i + 1), 0.f, 0.f, 0.f};
+            mLayers[i].mRELayerSettings.source.solidColor = {1.0f, 1.0f, 1.0f};
+            mLayers[i].mRELayerSettings.alpha = 1.0f;
+            mLayers[i].mRELayerSettings.disableBlending = false;
 
-    StrictMock<mock::OutputLayer> leftOutputLayer;
-    StrictMock<mock::OutputLayer> rightOutputLayer;
+            EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(i))
+                    .WillRepeatedly(Return(&mLayers[i].mOutputLayer));
+            EXPECT_CALL(mLayers[i].mOutputLayer, requiresClientComposition())
+                    .WillRepeatedly(Return(true));
+            EXPECT_CALL(mLayers[i].mOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
+        }
 
-    StrictMock<mock::Layer> leftLayer;
-    StrictMock<mock::LayerFE> leftLayerFE;
-    StrictMock<mock::Layer> rightLayer;
-    StrictMock<mock::LayerFE> rightLayerFE;
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(mLayers.size()));
+    }
 
-    impl::OutputLayerCompositionState leftOutputLayerState;
-    leftOutputLayerState.clearClientTarget = false;
-    leftOutputLayerState.visibleRegion = Region{Rect{0, 0, 1000, 1000}};
+    static constexpr uint32_t kDisplayOrientation = TR_IDENT;
+    static constexpr ui::Dataspace kDisplayDataspace = ui::Dataspace::UNKNOWN;
 
-    LayerFECompositionState leftLayerFEState;
-    leftLayerFEState.isOpaque = true;
+    static const Rect kDisplayFrame;
+    static const Rect kDisplayViewport;
+    static const Rect kDisplayScissor;
 
-    const half3 leftLayerColor{1.f, 0.f, 0.f};
-    renderengine::LayerSettings leftLayerRESettings;
-    leftLayerRESettings.source.solidColor = leftLayerColor;
+    std::array<Layer, 3> mLayers;
+};
 
-    impl::OutputLayerCompositionState rightOutputLayerState;
-    rightOutputLayerState.clearClientTarget = false;
-    rightOutputLayerState.visibleRegion = Region{Rect{1000, 0, 2000, 1000}};
+const Rect GenerateClientCompositionRequestsTest_ThreeLayers::kDisplayFrame(0, 0, 100, 200);
+const Rect GenerateClientCompositionRequestsTest_ThreeLayers::kDisplayViewport(0, 0, 101, 201);
+const Rect GenerateClientCompositionRequestsTest_ThreeLayers::kDisplayScissor(0, 0, 102, 202);
 
-    LayerFECompositionState rightLayerFEState;
-    rightLayerFEState.isOpaque = true;
+TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, handlesNoClientCompostionLayers) {
+    EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
+    EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
+    EXPECT_CALL(mLayers[2].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
 
-    const half3 rightLayerColor{0.f, 1.f, 0.f};
-    renderengine::LayerSettings rightLayerRESettings;
-    rightLayerRESettings.source.solidColor = rightLayerColor;
-
-    EXPECT_CALL(leftOutputLayer, getState()).WillRepeatedly(ReturnRef(leftOutputLayerState));
-    EXPECT_CALL(leftOutputLayer, getLayer()).WillRepeatedly(ReturnRef(leftLayer));
-    EXPECT_CALL(leftOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(leftLayerFE));
-    EXPECT_CALL(leftOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
-    EXPECT_CALL(leftOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
-    EXPECT_CALL(leftLayer, getFEState()).WillRepeatedly(ReturnRef(leftLayerFEState));
-    EXPECT_CALL(leftLayerFE, prepareClientComposition(_)).WillOnce(Return(leftLayerRESettings));
-    EXPECT_CALL(leftOutputLayer, editState()).WillRepeatedly(ReturnRef(leftOutputLayerState));
-
-    EXPECT_CALL(rightOutputLayer, getState()).WillRepeatedly(ReturnRef(rightOutputLayerState));
-    EXPECT_CALL(rightOutputLayer, getLayer()).WillRepeatedly(ReturnRef(rightLayer));
-    EXPECT_CALL(rightOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(rightLayerFE));
-    EXPECT_CALL(rightOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
-    EXPECT_CALL(rightOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
-    EXPECT_CALL(rightLayer, getFEState()).WillRepeatedly(ReturnRef(rightLayerFEState));
-    EXPECT_CALL(rightLayerFE, prepareClientComposition(_)).WillOnce(Return(rightLayerRESettings));
-    EXPECT_CALL(rightOutputLayer, editState()).WillRepeatedly(ReturnRef(rightOutputLayerState));
-
-    EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u));
-    EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u))
-            .WillRepeatedly(Return(&leftOutputLayer));
-    EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1u))
-            .WillRepeatedly(Return(&rightOutputLayer));
-
-    const Rect kPortraitFrame(0, 0, 1000, 2000);
-    const Rect kPortraitViewport(0, 0, 2000, 1000);
-    const Rect kPortraitScissor(0, 0, 1000, 2000);
-    const uint32_t kPortraitOrientation = TR_ROT_90;
-
-    mOutput.editState().frame = kPortraitFrame;
-    mOutput.editState().viewport = kPortraitViewport;
-    mOutput.editState().scissor = kPortraitScissor;
-    mOutput.editState().transform = ui::Transform{kPortraitOrientation};
-    mOutput.editState().orientation = kPortraitOrientation;
-    mOutput.editState().needsFiltering = true;
-    mOutput.editState().isSecure = false;
-
-    constexpr bool supportsProtectedContent = false;
-    Region clearRegion;
-    auto requests =
-            mOutput.generateClientCompositionRequests(supportsProtectedContent, clearRegion);
-
-    ASSERT_EQ(2u, requests.size());
-    EXPECT_EQ(leftLayerColor, requests[0].source.solidColor);
-    EXPECT_EQ(rightLayerColor, requests[1].source.solidColor);
-}
-
-TEST_F(GenerateClientCompositionRequestsTest, ignoresLayersThatDoNotIntersectWithViewport) {
-    // Layers whose visible region does not intersect with the viewport will be
-    // skipped when generating client composition request state.
-
-    StrictMock<mock::OutputLayer> outputLayer;
-    StrictMock<mock::Layer> layer;
-    StrictMock<mock::LayerFE> layerFE;
-
-    impl::OutputLayerCompositionState outputLayerState;
-    outputLayerState.clearClientTarget = false;
-    outputLayerState.visibleRegion = Region{Rect{3000, 0, 4000, 1000}};
-
-    LayerFECompositionState layerFEState;
-    layerFEState.isOpaque = true;
-
-    EXPECT_CALL(outputLayer, getState()).WillRepeatedly(ReturnRef(outputLayerState));
-    EXPECT_CALL(outputLayer, getLayer()).WillRepeatedly(ReturnRef(layer));
-    EXPECT_CALL(outputLayer, getLayerFE()).WillRepeatedly(ReturnRef(layerFE));
-    EXPECT_CALL(outputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
-    EXPECT_CALL(outputLayer, needsFiltering()).WillRepeatedly(Return(false));
-    EXPECT_CALL(layer, getFEState()).WillRepeatedly(ReturnRef(layerFEState));
-    EXPECT_CALL(layerFE, prepareClientComposition(_)).Times(0);
-    EXPECT_CALL(outputLayer, editState()).WillRepeatedly(ReturnRef(outputLayerState));
-
-    EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(1u));
-    EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u)).WillRepeatedly(Return(&outputLayer));
-
-    const Rect kPortraitFrame(0, 0, 1000, 2000);
-    const Rect kPortraitViewport(0, 0, 2000, 1000);
-    const Rect kPortraitScissor(0, 0, 1000, 2000);
-    const uint32_t kPortraitOrientation = TR_ROT_90;
-
-    mOutput.editState().frame = kPortraitFrame;
-    mOutput.editState().viewport = kPortraitViewport;
-    mOutput.editState().scissor = kPortraitScissor;
-    mOutput.editState().transform = ui::Transform{kPortraitOrientation};
-    mOutput.editState().orientation = kPortraitOrientation;
-    mOutput.editState().needsFiltering = true;
-    mOutput.editState().isSecure = false;
-
-    constexpr bool supportsProtectedContent = false;
-    Region clearRegion;
-    auto requests =
-            mOutput.generateClientCompositionRequests(supportsProtectedContent, clearRegion);
-
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
+                                                              accumClearRegion, kDisplayDataspace);
     EXPECT_EQ(0u, requests.size());
+    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
 }
 
-TEST_F(GenerateClientCompositionRequestsTest, clearsDeviceLayesAfterFirst) {
+TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, requiresVisibleRegionAfterViewportClip) {
+    mLayers[0].mOutputLayerState.visibleRegion = Region(Rect(10, 10, 10, 10));
+    mLayers[1].mOutputLayerState.visibleRegion = Region(Rect(4000, 0, 4010, 10));
+    mLayers[2].mOutputLayerState.visibleRegion = Region(Rect(-10, -10, 0, 0));
+
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
+                                                              accumClearRegion, kDisplayDataspace);
+    EXPECT_EQ(0u, requests.size());
+    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
+}
+
+TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, gathersClientCompositionRequests) {
+    renderengine::LayerSettings mREShadowSettings;
+    mREShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f};
+
+    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(_)).WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(_))
+            .WillOnce(Return(mLayers[1].mRELayerSettings));
+    EXPECT_CALL(mLayers[1].mLayerFE,
+                prepareShadowClientComposition(mLayers[1].mRELayerSettings, kDisplayViewport,
+                                               kDisplayDataspace))
+            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
+            .WillOnce(Return(mLayers[2].mRELayerSettings));
+    EXPECT_CALL(mLayers[2].mLayerFE,
+                prepareShadowClientComposition(mLayers[2].mRELayerSettings, kDisplayViewport,
+                                               kDisplayDataspace))
+            .WillOnce(Return(mREShadowSettings));
+
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
+                                                              accumClearRegion, kDisplayDataspace);
+    ASSERT_EQ(3u, requests.size());
+    EXPECT_EQ(mLayers[1].mRELayerSettings, requests[0]);
+    EXPECT_EQ(mREShadowSettings, requests[1]);
+    EXPECT_EQ(mLayers[2].mRELayerSettings, requests[2]);
+
+    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
+
+    // Check that a timestamp was set for the layers that generated requests
+    EXPECT_TRUE(0 == mLayers[0].mOutputLayerState.clientCompositionTimestamp);
+    EXPECT_TRUE(0 != mLayers[1].mOutputLayerState.clientCompositionTimestamp);
+    EXPECT_TRUE(0 != mLayers[2].mOutputLayerState.clientCompositionTimestamp);
+}
+
+TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
+       onlyClientComposesClientComposedLayersIfNoClearingNeeded) {
+    EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
+    EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
+    EXPECT_CALL(mLayers[2].mOutputLayer, requiresClientComposition()).WillOnce(Return(true));
+
+    mLayers[0].mOutputLayerState.clearClientTarget = false;
+    mLayers[1].mOutputLayerState.clearClientTarget = false;
+    mLayers[2].mOutputLayerState.clearClientTarget = false;
+
+    mLayers[0].mLayerFEState.isOpaque = true;
+    mLayers[1].mLayerFEState.isOpaque = true;
+    mLayers[2].mLayerFEState.isOpaque = true;
+
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
+            .WillOnce(Return(mLayers[2].mRELayerSettings));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareShadowClientComposition(_, _, _))
+            .WillOnce(Return(std::nullopt));
+
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
+                                                              accumClearRegion, kDisplayDataspace);
+    ASSERT_EQ(1u, requests.size());
+    EXPECT_EQ(mLayers[2].mRELayerSettings, requests[0]);
+
+    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
+}
+
+TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
+       onlyClientComposesClientComposedLayersIfOthersAreNotOpaque) {
+    EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
+    EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
+    EXPECT_CALL(mLayers[2].mOutputLayer, requiresClientComposition()).WillOnce(Return(true));
+
+    mLayers[0].mOutputLayerState.clearClientTarget = true;
+    mLayers[1].mOutputLayerState.clearClientTarget = true;
+    mLayers[2].mOutputLayerState.clearClientTarget = true;
+
+    mLayers[0].mLayerFEState.isOpaque = false;
+    mLayers[1].mLayerFEState.isOpaque = false;
+    mLayers[2].mLayerFEState.isOpaque = false;
+
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
+            .WillOnce(Return(mLayers[2].mRELayerSettings));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareShadowClientComposition(_, _, _))
+            .WillOnce(Return(std::nullopt));
+
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
+                                                              accumClearRegion, kDisplayDataspace);
+    ASSERT_EQ(1u, requests.size());
+    EXPECT_EQ(mLayers[2].mRELayerSettings, requests[0]);
+
+    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
+}
+
+TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, clearsHWCLayersIfOpaqueAndNotFirst) {
     // If client composition is performed with some layers set to use device
     // composition, device layers after the first layer (device or client) will
     // clear the frame buffer if they are opaque and if that layer has a flag
     // set to do so. The first layer is skipped as the frame buffer is already
     // expected to be clear.
 
-    StrictMock<mock::OutputLayer> leftOutputLayer;
-    StrictMock<mock::OutputLayer> rightOutputLayer;
+    EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
+    EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
+    EXPECT_CALL(mLayers[2].mOutputLayer, requiresClientComposition()).WillOnce(Return(true));
 
-    StrictMock<mock::Layer> leftLayer;
-    StrictMock<mock::LayerFE> leftLayerFE;
-    StrictMock<mock::Layer> rightLayer;
-    StrictMock<mock::LayerFE> rightLayerFE;
+    mLayers[0].mOutputLayerState.clearClientTarget = true;
+    mLayers[1].mOutputLayerState.clearClientTarget = true;
+    mLayers[2].mOutputLayerState.clearClientTarget = true;
 
-    impl::OutputLayerCompositionState leftOutputLayerState;
-    leftOutputLayerState.clearClientTarget = true;
-    leftOutputLayerState.visibleRegion = Region{Rect{0, 0, 1000, 1000}};
+    mLayers[0].mLayerFEState.isOpaque = true;
+    mLayers[1].mLayerFEState.isOpaque = true;
+    mLayers[2].mLayerFEState.isOpaque = true;
 
-    LayerFECompositionState leftLayerFEState;
-    leftLayerFEState.isOpaque = true;
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(_))
+            .WillOnce(Return(mLayers[1].mRELayerSettings));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
+            .WillOnce(Return(mLayers[2].mRELayerSettings));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareShadowClientComposition(_, _, _))
+            .WillOnce(Return(std::nullopt));
 
-    impl::OutputLayerCompositionState rightOutputLayerState;
-    rightOutputLayerState.clearClientTarget = true;
-    rightOutputLayerState.visibleRegion = Region{Rect{1000, 0, 2000, 1000}};
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
+                                                              accumClearRegion, kDisplayDataspace);
+    ASSERT_EQ(2u, requests.size());
 
-    LayerFECompositionState rightLayerFEState;
-    rightLayerFEState.isOpaque = true;
+    // The second layer is expected to be rendered as alpha=0 black with no blending
+    EXPECT_EQ(mLayers[1].mRELayerSettings.geometry.boundaries, requests[0].geometry.boundaries);
+    EXPECT_FALSE(requests[0].source.buffer.buffer);
+    EXPECT_EQ((half3{0.f, 0.f, 0.f}), requests[0].source.solidColor);
+    EXPECT_EQ(half{0.f}, requests[0].alpha);
+    EXPECT_EQ(true, requests[0].disableBlending);
 
-    const half3 rightLayerColor{0.f, 1.f, 0.f};
-    renderengine::LayerSettings rightLayerRESettings;
-    rightLayerRESettings.geometry.boundaries = FloatRect{456, 0, 0, 0};
-    rightLayerRESettings.source.solidColor = rightLayerColor;
+    EXPECT_EQ(mLayers[2].mRELayerSettings, requests[1]);
 
-    EXPECT_CALL(leftOutputLayer, getState()).WillRepeatedly(ReturnRef(leftOutputLayerState));
-    EXPECT_CALL(leftOutputLayer, getLayer()).WillRepeatedly(ReturnRef(leftLayer));
-    EXPECT_CALL(leftOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(leftLayerFE));
-    EXPECT_CALL(leftOutputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(leftOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
-    EXPECT_CALL(leftLayer, getFEState()).WillRepeatedly(ReturnRef(leftLayerFEState));
-    EXPECT_CALL(leftOutputLayer, editState()).WillRepeatedly(ReturnRef(leftOutputLayerState));
+    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
+}
 
-    EXPECT_CALL(rightOutputLayer, getState()).WillRepeatedly(ReturnRef(rightOutputLayerState));
-    EXPECT_CALL(rightOutputLayer, getLayer()).WillRepeatedly(ReturnRef(rightLayer));
-    EXPECT_CALL(rightOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(rightLayerFE));
-    EXPECT_CALL(rightOutputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(rightOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
-    EXPECT_CALL(rightLayer, getFEState()).WillRepeatedly(ReturnRef(rightLayerFEState));
-    EXPECT_CALL(rightLayerFE, prepareClientComposition(_)).WillOnce(Return(rightLayerRESettings));
-    EXPECT_CALL(rightOutputLayer, editState()).WillRepeatedly(ReturnRef(rightOutputLayerState));
+TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
+       clippedVisibleRegionUsedToGenerateRequest) {
+    mLayers[0].mOutputLayerState.visibleRegion = Region(Rect(10, 10, 20, 20));
+    mLayers[1].mOutputLayerState.visibleRegion = Region(Rect(-10, -10, 30, 30));
+    mLayers[2].mOutputLayerState.visibleRegion = Region(Rect(-10, 0, 40, 4000));
 
-    EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u));
-    EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u))
-            .WillRepeatedly(Return(&leftOutputLayer));
-    EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1u))
-            .WillRepeatedly(Return(&rightOutputLayer));
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer0TargetSettings{
+            Region(Rect(10, 10, 20, 20)),
+            false, /* identity transform */
+            false, /* needs filtering */
+            false, /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+    };
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
+            Region(Rect(0, 0, 30, 30)),
+            false, /* identity transform */
+            false, /* needs filtering */
+            false, /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+    };
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
+            Region(Rect(0, 0, 40, 201)),
+            false, /* identity transform */
+            false, /* needs filtering */
+            false, /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+    };
+
+    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+
+    static_cast<void>(
+            mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
+                                                      accumClearRegion, kDisplayDataspace));
+}
+
+TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
+       perLayerNeedsFilteringUsedToGenerateRequests) {
+    mOutput.mState.needsFiltering = false;
+    EXPECT_CALL(mLayers[0].mOutputLayer, needsFiltering()).WillRepeatedly(Return(true));
+
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer0TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            true,  /* needs filtering */
+            false, /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+    };
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            false, /* needs filtering */
+            false, /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+    };
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            false, /* needs filtering */
+            false, /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+    };
+
+    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+
+    static_cast<void>(
+            mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
+                                                      accumClearRegion, kDisplayDataspace));
+}
+
+TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
+       wholeOutputNeedsFilteringUsedToGenerateRequests) {
+    mOutput.mState.needsFiltering = true;
+    EXPECT_CALL(mLayers[0].mOutputLayer, needsFiltering()).WillRepeatedly(Return(true));
+
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer0TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            true,  /* needs filtering */
+            false, /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+    };
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            true,  /* needs filtering */
+            false, /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+    };
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            true,  /* needs filtering */
+            false, /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+    };
+
+    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+
+    static_cast<void>(
+            mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
+                                                      accumClearRegion, kDisplayDataspace));
+}
+
+TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
+       wholeOutputSecurityUsedToGenerateRequests) {
+    mOutput.mState.isSecure = true;
+
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer0TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            false, /* needs filtering */
+            true,  /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+    };
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            false, /* needs filtering */
+            true,  /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+    };
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            false, /* needs filtering */
+            true,  /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+    };
+
+    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+
+    static_cast<void>(
+            mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
+                                                      accumClearRegion, kDisplayDataspace));
+}
+
+TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
+       protectedContentSupportUsedToGenerateRequests) {
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer0TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            false, /* needs filtering */
+            false, /* secure */
+            true,  /* supports protected content */
+            accumClearRegion,
+    };
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            false, /* needs filtering */
+            false, /* secure */
+            true,  /* supports protected content */
+            accumClearRegion,
+    };
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            false, /* needs filtering */
+            false, /* secure */
+            true,  /* supports protected content */
+            accumClearRegion,
+    };
+
+    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
+            .WillOnce(Return(std::nullopt));
+
+    static_cast<void>(mOutput.generateClientCompositionRequests(true /* supportsProtectedContent */,
+                                                                accumClearRegion,
+                                                                kDisplayDataspace));
+}
+
+TEST_F(GenerateClientCompositionRequestsTest, handlesLandscapeModeSplitScreenRequests) {
+    // In split-screen landscape mode, the screen is rotated 90 degrees, with
+    // one layer on the left covering the left side of the output, and one layer
+    // on the right covering that side of the output.
 
     const Rect kPortraitFrame(0, 0, 1000, 2000);
     const Rect kPortraitViewport(0, 0, 2000, 1000);
     const Rect kPortraitScissor(0, 0, 1000, 2000);
     const uint32_t kPortraitOrientation = TR_ROT_90;
+    constexpr ui::Dataspace kOutputDataspace = ui::Dataspace::DISPLAY_P3;
 
-    mOutput.editState().frame = kPortraitFrame;
-    mOutput.editState().viewport = kPortraitViewport;
-    mOutput.editState().scissor = kPortraitScissor;
-    mOutput.editState().transform = ui::Transform{kPortraitOrientation};
-    mOutput.editState().orientation = kPortraitOrientation;
-    mOutput.editState().needsFiltering = true;
-    mOutput.editState().isSecure = false;
+    mOutput.mState.frame = kPortraitFrame;
+    mOutput.mState.viewport = kPortraitViewport;
+    mOutput.mState.scissor = kPortraitScissor;
+    mOutput.mState.transform = ui::Transform{kPortraitOrientation};
+    mOutput.mState.orientation = kPortraitOrientation;
+    mOutput.mState.needsFiltering = false;
+    mOutput.mState.isSecure = true;
 
-    constexpr bool supportsProtectedContent = false;
-    Region clearRegion;
-    auto requests =
-            mOutput.generateClientCompositionRequests(supportsProtectedContent, clearRegion);
+    Layer leftLayer;
+    Layer rightLayer;
 
-    const half3 clearColor{0.f, 0.f, 0.f};
+    leftLayer.mOutputLayerState.clearClientTarget = false;
+    leftLayer.mOutputLayerState.visibleRegion = Region(Rect(0, 0, 1000, 1000));
+    leftLayer.mLayerFEState.isOpaque = true;
+    leftLayer.mRELayerSettings.source.solidColor = {1.f, 0.f, 0.f};
 
-    ASSERT_EQ(1u, requests.size());
-    EXPECT_EQ(456.f, requests[0].geometry.boundaries.left);
-    EXPECT_EQ(clearColor, requests[0].source.solidColor);
+    rightLayer.mOutputLayerState.clearClientTarget = false;
+    rightLayer.mOutputLayerState.visibleRegion = Region(Rect(1000, 0, 2000, 1000));
+    rightLayer.mLayerFEState.isOpaque = true;
+    rightLayer.mRELayerSettings.source.solidColor = {0.f, 1.f, 0.f};
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u));
+    EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u))
+            .WillRepeatedly(Return(&leftLayer.mOutputLayer));
+    EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1u))
+            .WillRepeatedly(Return(&rightLayer.mOutputLayer));
+
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+
+    compositionengine::LayerFE::ClientCompositionTargetSettings leftLayerSettings{
+            Region(Rect(0, 0, 1000, 1000)),
+            false, /* identity transform */
+            false, /* needs filtering */
+            true,  /* secure */
+            true,  /* supports protected content */
+            accumClearRegion,
+    };
+
+    EXPECT_CALL(leftLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
+    EXPECT_CALL(leftLayer.mOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
+    EXPECT_CALL(leftLayer.mLayerFE, prepareClientComposition(Eq(ByRef(leftLayerSettings))))
+            .WillOnce(Return(leftLayer.mRELayerSettings));
+    EXPECT_CALL(leftLayer.mLayerFE,
+                prepareShadowClientComposition(leftLayer.mRELayerSettings, kPortraitViewport,
+                                               kOutputDataspace))
+            .WillOnce(Return(std::nullopt));
+
+    compositionengine::LayerFE::ClientCompositionTargetSettings rightLayerSettings{
+            Region(Rect(1000, 0, 2000, 1000)),
+            false, /* identity transform */
+            false, /* needs filtering */
+            true,  /* secure */
+            true,  /* supports protected content */
+            accumClearRegion,
+    };
+
+    EXPECT_CALL(rightLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
+    EXPECT_CALL(rightLayer.mOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
+    EXPECT_CALL(rightLayer.mLayerFE, prepareClientComposition(Eq(ByRef(rightLayerSettings))))
+            .WillOnce(Return(rightLayer.mRELayerSettings));
+    EXPECT_CALL(rightLayer.mLayerFE,
+                prepareShadowClientComposition(rightLayer.mRELayerSettings, kPortraitViewport,
+                                               kOutputDataspace))
+            .WillOnce(Return(std::nullopt));
+
+    constexpr bool supportsProtectedContent = true;
+    auto requests = mOutput.generateClientCompositionRequests(supportsProtectedContent,
+                                                              accumClearRegion, kOutputDataspace);
+    ASSERT_EQ(2u, requests.size());
+    EXPECT_EQ(leftLayer.mRELayerSettings, requests[0]);
+    EXPECT_EQ(rightLayer.mRELayerSettings, requests[1]);
 }
 
 } // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/RectMatcher.h b/services/surfaceflinger/CompositionEngine/tests/RectMatcher.h
deleted file mode 100644
index d4c76bc..0000000
--- a/services/surfaceflinger/CompositionEngine/tests/RectMatcher.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <string>
-
-#include <android-base/stringprintf.h>
-#include <gmock/gmock.h>
-
-namespace {
-
-using android::base::StringAppendF;
-using Rect = android::Rect;
-
-void dumpRect(const Rect& rect, std::string& result, const char* name) {
-    StringAppendF(&result, "%s (%d %d %d %d) ", name, rect.left, rect.top, rect.right, rect.bottom);
-}
-
-// Checks for a region match
-MATCHER_P(RectEq, expected, "") {
-    std::string buf;
-    buf.append("Rects are not equal\n");
-    dumpRect(expected, buf, "expected rect");
-    dumpRect(arg, buf, "actual rect");
-    *result_listener << buf;
-
-    return (expected.left == arg.left) && (expected.top == arg.top) &&
-            (expected.right == arg.right) && (expected.bottom == arg.bottom);
-}
-
-} // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/RegionMatcher.h b/services/surfaceflinger/CompositionEngine/tests/RegionMatcher.h
index 5a4efa9..2adde23 100644
--- a/services/surfaceflinger/CompositionEngine/tests/RegionMatcher.h
+++ b/services/surfaceflinger/CompositionEngine/tests/RegionMatcher.h
@@ -20,24 +20,22 @@
 
 namespace {
 
-// Checks for a region match
-MATCHER_P(RegionEq, expected, "") {
-    std::string buf;
-    buf.append("Regions are not equal\n");
-    expected.dump(buf, "expected region");
-    arg.dump(buf, "actual region");
-    *result_listener << buf;
+using Region = android::Region;
 
-    size_t expectedRectCount = 0;
-    android::Rect const* expectedRects = expected.getArray(&expectedRectCount);
-    size_t actualRectCount = 0;
-    android::Rect const* actualRects = arg.getArray(&actualRectCount);
+struct RegionMatcher : public testing::MatcherInterface<const Region&> {
+    const Region expected;
 
-    if (expectedRectCount != actualRectCount) return false;
-    for (size_t i = 0; i < expectedRectCount; i++) {
-        if (expectedRects[i] != actualRects[i]) return false;
+    explicit RegionMatcher(const Region& expectedValue) : expected(expectedValue) {}
+
+    bool MatchAndExplain(const Region& actual, testing::MatchResultListener*) const override {
+        return expected.hasSameRects(actual);
     }
-    return true;
+
+    void DescribeTo(::std::ostream* os) const override { PrintTo(expected, os); }
+};
+
+testing::Matcher<const Region&> RegionEq(const Region& expected) {
+    return MakeMatcher(new RegionMatcher(expected));
 }
 
 } // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
index da3f4fb..fd47e45 100644
--- a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
@@ -192,8 +192,8 @@
     mSurface.prepareFrame(true, true);
 }
 
-TEST_F(RenderSurfaceTest, prepareFrameHandlesOnlyGlesComposition) {
-    EXPECT_CALL(*mDisplaySurface, prepareFrame(DisplaySurface::COMPOSITION_GLES))
+TEST_F(RenderSurfaceTest, prepareFrameHandlesOnlyGpuComposition) {
+    EXPECT_CALL(*mDisplaySurface, prepareFrame(DisplaySurface::COMPOSITION_GPU))
             .WillOnce(Return(NO_ERROR));
 
     mSurface.prepareFrame(true, false);
diff --git a/services/surfaceflinger/CompositionEngine/tests/TransformMatcher.h b/services/surfaceflinger/CompositionEngine/tests/TransformMatcher.h
deleted file mode 100644
index ea07bed..0000000
--- a/services/surfaceflinger/CompositionEngine/tests/TransformMatcher.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <string>
-
-#include <gmock/gmock.h>
-
-namespace {
-
-// Check for a transform match
-MATCHER_P(TransformEq, expected, "") {
-    std::string buf;
-    buf.append("Transforms are not equal\n");
-    expected.dump(buf, "expected transform");
-    arg.dump(buf, "actual transform");
-    *result_listener << buf;
-
-    const float TOLERANCE = 1e-3f;
-
-    for (int i = 0; i < 3; i++) {
-        for (int j = 0; j < 3; j++) {
-            if (std::fabs(expected[i][j] - arg[i][j]) > TOLERANCE) {
-                return false;
-            }
-        }
-    }
-
-    return true;
-}
-
-} // namespace
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 89123df..84ec597 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -132,11 +132,11 @@
 }
 
 // ----------------------------------------------------------------------------
-void DisplayDevice::setActiveConfig(int mode) {
+void DisplayDevice::setActiveConfig(HwcConfigIndexType mode) {
     mActiveConfig = mode;
 }
 
-int DisplayDevice::getActiveConfig()  const {
+HwcConfigIndexType DisplayDevice::getActiveConfig() const {
     return mActiveConfig;
 }
 
@@ -285,7 +285,7 @@
 
     result.append("   ");
     StringAppendF(&result, "powerMode=%d, ", mPowerMode);
-    StringAppendF(&result, "activeConfig=%d, ", mActiveConfig);
+    StringAppendF(&result, "activeConfig=%d, ", mActiveConfig.value());
     getCompositionDisplay()->dump(result);
 }
 
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index ce4e1e6..79a1185 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -43,6 +43,7 @@
 #include "DisplayHardware/DisplayIdentification.h"
 #include "DisplayHardware/PowerAdvisor.h"
 #include "RenderArea.h"
+#include "Scheduler/HwcStrongTypes.h"
 
 namespace android {
 
@@ -141,8 +142,8 @@
     /* ------------------------------------------------------------------------
      * Display active config management.
      */
-    int getActiveConfig() const;
-    void setActiveConfig(int mode);
+    HwcConfigIndexType getActiveConfig() const;
+    void setActiveConfig(HwcConfigIndexType mode);
 
     // release HWC resources (if any) for removable displays
     void disconnect();
@@ -186,7 +187,7 @@
     // Current power mode
     int mPowerMode;
     // Current active config
-    int mActiveConfig;
+    HwcConfigIndexType mActiveConfig;
 
     // TODO(b/74619554): Remove special cases for primary display.
     const bool mIsPrimary;
@@ -246,6 +247,7 @@
                       uint32_t reqHeight, ui::Dataspace reqDataSpace,
                       ui::Transform::orientation_flags rotation, bool allowSecureLayers = true)
           : RenderArea(reqWidth, reqHeight, CaptureFill::OPAQUE, reqDataSpace,
+                       device->getViewport(),
                        getDisplayRotation(rotation, device->getInstallOrientation())),
             mDevice(device),
             mSourceCrop(sourceCrop),
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
index aef1c75..dc71128 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
@@ -95,7 +95,7 @@
 
 // assume NO_RESOURCES when Status::isOk returns false
 constexpr Error kDefaultError = Error::NO_RESOURCES;
-constexpr V2_4::Error kDefaultError_2_4 = V2_4::Error::NO_RESOURCES;
+constexpr V2_4::Error kDefaultError_2_4 = static_cast<V2_4::Error>(kDefaultError);
 
 template<typename T, typename U>
 T unwrapRet(Return<T>& ret, const U& default_val)
@@ -247,7 +247,12 @@
 void Composer::registerCallback(const sp<IComposerCallback>& callback)
 {
     android::hardware::setMinSchedulerPolicy(callback, SCHED_FIFO, 2);
-    auto ret = mClient->registerCallback(callback);
+    auto ret = [&]() {
+        if (mClient_2_4) {
+            return mClient_2_4->registerCallback_2_4(callback);
+        }
+        return mClient->registerCallback(callback);
+    }();
     if (!ret.isOk()) {
         ALOGE("failed to register IComposerCallback");
     }
@@ -413,15 +418,28 @@
         IComposerClient::Attribute attribute, int32_t* outValue)
 {
     Error error = kDefaultError;
-    mClient->getDisplayAttribute(display, config, attribute,
-            [&](const auto& tmpError, const auto& tmpValue) {
-                error = tmpError;
-                if (error != Error::NONE) {
-                    return;
-                }
+    if (mClient_2_4) {
+        mClient_2_4->getDisplayAttribute_2_4(display, config, attribute,
+                                             [&](const auto& tmpError, const auto& tmpValue) {
+                                                 error = static_cast<Error>(tmpError);
+                                                 if (error != Error::NONE) {
+                                                     return;
+                                                 }
 
-                *outValue = tmpValue;
-            });
+                                                 *outValue = tmpValue;
+                                             });
+    } else {
+        mClient->getDisplayAttribute(display, config,
+                                     static_cast<V2_1::IComposerClient::Attribute>(attribute),
+                                     [&](const auto& tmpError, const auto& tmpValue) {
+                                         error = tmpError;
+                                         if (error != Error::NONE) {
+                                             return;
+                                         }
+
+                                         *outValue = tmpValue;
+                                     });
+    }
 
     return error;
 }
@@ -1200,13 +1218,14 @@
     return static_cast<Error>(error);
 }
 
-Error Composer::getDisplayConnectionType(Display display,
-                                         IComposerClient::DisplayConnectionType* outType) {
+V2_4::Error Composer::getDisplayConnectionType(Display display,
+                                               IComposerClient::DisplayConnectionType* outType) {
+    using Error = V2_4::Error;
     if (!mClient_2_4) {
         return Error::UNSUPPORTED;
     }
 
-    V2_4::Error error = kDefaultError_2_4;
+    Error error = kDefaultError_2_4;
     mClient_2_4->getDisplayConnectionType(display, [&](const auto& tmpError, const auto& tmpType) {
         error = tmpError;
         if (error != V2_4::Error::NONE) {
@@ -1216,7 +1235,50 @@
         *outType = tmpType;
     });
 
-    return static_cast<V2_1::Error>(error);
+    return error;
+}
+
+V2_4::Error Composer::getDisplayVsyncPeriod(Display display, VsyncPeriodNanos* outVsyncPeriod) {
+    using Error = V2_4::Error;
+    if (!mClient_2_4) {
+        return Error::UNSUPPORTED;
+    }
+
+    Error error = kDefaultError_2_4;
+    mClient_2_4->getDisplayVsyncPeriod(display,
+                                       [&](const auto& tmpError, const auto& tmpVsyncPeriod) {
+                                           error = tmpError;
+                                           if (error != Error::NONE) {
+                                               return;
+                                           }
+
+                                           *outVsyncPeriod = tmpVsyncPeriod;
+                                       });
+
+    return error;
+}
+
+V2_4::Error Composer::setActiveConfigWithConstraints(
+        Display display, Config config,
+        const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
+        VsyncPeriodChangeTimeline* outTimeline) {
+    using Error = V2_4::Error;
+    if (!mClient_2_4) {
+        return Error::UNSUPPORTED;
+    }
+
+    Error error = kDefaultError_2_4;
+    mClient_2_4->setActiveConfigWithConstraints(display, config, vsyncPeriodChangeConstraints,
+                                                [&](const auto& tmpError, const auto& tmpTimeline) {
+                                                    error = tmpError;
+                                                    if (error != Error::NONE) {
+                                                        return;
+                                                    }
+
+                                                    *outTimeline = tmpTimeline;
+                                                });
+
+    return error;
 }
 
 CommandReader::~CommandReader()
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index e743e59..336fdd8 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -62,12 +62,14 @@
 using V2_1::Config;
 using V2_1::Display;
 using V2_1::Error;
-using V2_1::IComposerCallback;
 using V2_1::Layer;
 using V2_3::CommandReaderBase;
 using V2_3::CommandWriterBase;
 using V2_4::IComposer;
+using V2_4::IComposerCallback;
 using V2_4::IComposerClient;
+using V2_4::VsyncPeriodChangeTimeline;
+using V2_4::VsyncPeriodNanos;
 using DisplayCapability = IComposerClient::DisplayCapability;
 using PerFrameMetadata = IComposerClient::PerFrameMetadata;
 using PerFrameMetadataKey = IComposerClient::PerFrameMetadataKey;
@@ -208,10 +210,17 @@
     virtual Error setDisplayBrightness(Display display, float brightness) = 0;
 
     // Composer HAL 2.4
+    virtual bool isVsyncPeriodSwitchSupported() = 0;
     virtual Error getDisplayCapabilities(Display display,
                                          std::vector<DisplayCapability>* outCapabilities) = 0;
-    virtual Error getDisplayConnectionType(Display display,
-                                           IComposerClient::DisplayConnectionType* outType) = 0;
+    virtual V2_4::Error getDisplayConnectionType(
+            Display display, IComposerClient::DisplayConnectionType* outType) = 0;
+    virtual V2_4::Error getDisplayVsyncPeriod(Display display,
+                                              VsyncPeriodNanos* outVsyncPeriod) = 0;
+    virtual V2_4::Error setActiveConfigWithConstraints(
+            Display display, Config config,
+            const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
+            VsyncPeriodChangeTimeline* outTimeline) = 0;
 };
 
 namespace impl {
@@ -423,10 +432,16 @@
     Error setDisplayBrightness(Display display, float brightness) override;
 
     // Composer HAL 2.4
+    bool isVsyncPeriodSwitchSupported() override { return mClient_2_4 != nullptr; }
     Error getDisplayCapabilities(Display display,
                                  std::vector<DisplayCapability>* outCapabilities) override;
-    Error getDisplayConnectionType(Display display,
-                                   IComposerClient::DisplayConnectionType* outType) override;
+    V2_4::Error getDisplayConnectionType(Display display,
+                                         IComposerClient::DisplayConnectionType* outType) override;
+    V2_4::Error getDisplayVsyncPeriod(Display display, VsyncPeriodNanos* outVsyncPeriod) override;
+    V2_4::Error setActiveConfigWithConstraints(
+            Display display, Config config,
+            const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
+            VsyncPeriodChangeTimeline* outTimeline) override;
 
 private:
 #if defined(USE_VR_COMPOSER) && USE_VR_COMPOSER
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 6f7428a..34254e0 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -81,7 +81,26 @@
 
     Return<void> onVsync(Hwc2::Display display, int64_t timestamp) override
     {
-        mCallback->onVsyncReceived(mSequenceId, display, timestamp);
+        mCallback->onVsyncReceived(mSequenceId, display, timestamp, std::nullopt);
+        return Void();
+    }
+
+    Return<void> onVsync_2_4(Hwc2::Display display, int64_t timestamp,
+                             Hwc2::VsyncPeriodNanos vsyncPeriodNanos) override {
+        // TODO(b/140201379): use vsyncPeriodNanos in the new DispSync
+        mCallback->onVsyncReceived(mSequenceId, display, timestamp,
+                                   std::make_optional(vsyncPeriodNanos));
+        return Void();
+    }
+
+    Return<void> onVsyncPeriodTimingChanged(
+            Hwc2::Display display,
+            const Hwc2::VsyncPeriodChangeTimeline& updatedTimeline) override {
+        hwc_vsync_period_change_timeline_t timeline;
+        timeline.newVsyncAppliedTimeNanos = updatedTimeline.newVsyncAppliedTimeNanos;
+        timeline.refreshRequired = updatedTimeline.refreshRequired;
+        timeline.refreshTimeNanos = updatedTimeline.refreshTimeNanos;
+        mCallback->onVsyncPeriodTimingChangedReceived(mSequenceId, display, timeline);
         return Void();
     }
 
@@ -330,6 +349,36 @@
     return Error::None;
 }
 
+bool Display::isVsyncPeriodSwitchSupported() const {
+    ALOGV("[%" PRIu64 "] isVsyncPeriodSwitchSupported()", mId);
+
+    return mComposer.isVsyncPeriodSwitchSupported();
+}
+
+Error Display::getDisplayVsyncPeriod(nsecs_t* outVsyncPeriod) const {
+    ALOGV("[%" PRIu64 "] getDisplayVsyncPeriod", mId);
+
+    Error error;
+
+    if (isVsyncPeriodSwitchSupported()) {
+        Hwc2::VsyncPeriodNanos vsyncPeriodNanos = 0;
+        auto intError = mComposer.getDisplayVsyncPeriod(mId, &vsyncPeriodNanos);
+        error = static_cast<Error>(intError);
+        *outVsyncPeriod = static_cast<nsecs_t>(vsyncPeriodNanos);
+    } else {
+        // Get the default vsync period
+        hwc2_config_t configId = 0;
+        auto intError_2_1 = mComposer.getActiveConfig(mId, &configId);
+        error = static_cast<Error>(intError_2_1);
+        if (error == Error::None) {
+            auto config = mConfigs.at(configId);
+            *outVsyncPeriod = config->getVsyncPeriod();
+        }
+    }
+
+    return error;
+}
+
 Error Display::getActiveConfigIndex(int* outIndex) const {
     ALOGV("[%" PRIu64 "] getActiveConfigIndex", mId);
     hwc2_config_t configId = 0;
@@ -345,6 +394,7 @@
     auto pos = mConfigs.find(configId);
     if (pos != mConfigs.end()) {
         *outIndex = std::distance(mConfigs.begin(), pos);
+        ALOGV("[%" PRIu64 "] index = %d", mId, *outIndex);
     } else {
         ALOGE("[%" PRIu64 "] getActiveConfig returned unknown config %u", mId, configId);
         // Return no error, but the caller needs to check for a negative index
@@ -582,6 +632,46 @@
     return Error::None;
 }
 
+Error Display::setActiveConfigWithConstraints(
+        const std::shared_ptr<const HWC2::Display::Config>& config,
+        const VsyncPeriodChangeConstraints& constraints, VsyncPeriodChangeTimeline* outTimeline) {
+    ALOGV("[%" PRIu64 "] setActiveConfigWithConstraints", mId);
+    if (config->getDisplayId() != mId) {
+        ALOGE("setActiveConfigWithConstraints received config %u for the wrong display %" PRIu64
+              " (expected %" PRIu64 ")",
+              config->getId(), config->getDisplayId(), mId);
+        return Error::BadConfig;
+    }
+
+    if (isVsyncPeriodSwitchSupported()) {
+        Hwc2::IComposerClient::VsyncPeriodChangeConstraints hwc2Constraints;
+        hwc2Constraints.desiredTimeNanos = constraints.desiredTimeNanos;
+        hwc2Constraints.seamlessRequired = constraints.seamlessRequired;
+
+        Hwc2::VsyncPeriodChangeTimeline vsyncPeriodChangeTimeline = {};
+        auto intError =
+                mComposer.setActiveConfigWithConstraints(mId, config->getId(), hwc2Constraints,
+                                                         &vsyncPeriodChangeTimeline);
+        outTimeline->newVsyncAppliedTimeNanos = vsyncPeriodChangeTimeline.newVsyncAppliedTimeNanos;
+        outTimeline->refreshRequired = vsyncPeriodChangeTimeline.refreshRequired;
+        outTimeline->refreshTimeNanos = vsyncPeriodChangeTimeline.refreshTimeNanos;
+        return static_cast<Error>(intError);
+    }
+
+    // Use legacy setActiveConfig instead
+    ALOGV("fallback to legacy setActiveConfig");
+    const auto now = systemTime();
+    if (constraints.desiredTimeNanos > now || constraints.seamlessRequired) {
+        ALOGE("setActiveConfigWithConstraints received constraints that can't be satisfied");
+    }
+
+    auto intError_2_4 = mComposer.setActiveConfig(mId, config->getId());
+    outTimeline->newVsyncAppliedTimeNanos = std::max(now, constraints.desiredTimeNanos);
+    outTimeline->refreshRequired = true;
+    outTimeline->refreshTimeNanos = now;
+    return static_cast<Error>(intError_2_4);
+}
+
 Error Display::setActiveConfig(const std::shared_ptr<const Config>& config)
 {
     if (config->getDisplayId() != mId) {
@@ -742,12 +832,13 @@
     ALOGV("[%" PRIu64 "] loadConfig(%u)", mId, configId);
 
     auto config = Config::Builder(*this, configId)
-            .setWidth(getAttribute(configId, Attribute::Width))
-            .setHeight(getAttribute(configId, Attribute::Height))
-            .setVsyncPeriod(getAttribute(configId, Attribute::VsyncPeriod))
-            .setDpiX(getAttribute(configId, Attribute::DpiX))
-            .setDpiY(getAttribute(configId, Attribute::DpiY))
-            .build();
+                          .setWidth(getAttribute(configId, Attribute::Width))
+                          .setHeight(getAttribute(configId, Attribute::Height))
+                          .setVsyncPeriod(getAttribute(configId, Attribute::VsyncPeriod))
+                          .setDpiX(getAttribute(configId, Attribute::DpiX))
+                          .setDpiY(getAttribute(configId, Attribute::DpiY))
+                          .setConfigGroup(getAttribute(configId, Attribute::ConfigGroup))
+                          .build();
     mConfigs.emplace(configId, std::move(config));
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index b7cdf7f..5abebab 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -54,6 +54,8 @@
 
 class Display;
 class Layer;
+using VsyncPeriodChangeConstraints = hwc_vsync_period_change_constraints_t;
+using VsyncPeriodChangeTimeline = hwc_vsync_period_change_timeline_t;
 
 // Implement this interface to receive hardware composer events.
 //
@@ -70,8 +72,12 @@
                                    Connection connection) = 0;
     virtual void onRefreshReceived(int32_t sequenceId,
                                    hwc2_display_t display) = 0;
-    virtual void onVsyncReceived(int32_t sequenceId, hwc2_display_t display,
-                                 int64_t timestamp) = 0;
+    virtual void onVsyncReceived(int32_t sequenceId, hwc2_display_t display, int64_t timestamp,
+                                 std::optional<hwc2_vsync_period_t> vsyncPeriod) = 0;
+    virtual void onVsyncPeriodTimingChangedReceived(
+            int32_t sequenceId, hwc2_display_t display,
+            const hwc_vsync_period_change_timeline_t& updatedTimeline) = 0;
+
     virtual ~ComposerCallback() = default;
 };
 
@@ -170,6 +176,10 @@
                 }
                 return *this;
             }
+            Builder& setConfigGroup(int32_t configGroup) {
+                mConfig->mConfigGroup = configGroup;
+                return *this;
+            }
 
         private:
             float getDefaultDensity();
@@ -184,6 +194,7 @@
         nsecs_t getVsyncPeriod() const { return mVsyncPeriod; }
         float getDpiX() const { return mDpiX; }
         float getDpiY() const { return mDpiY; }
+        int32_t getConfigGroup() const { return mConfigGroup; }
 
     private:
         Config(Display& display, hwc2_config_t id);
@@ -196,12 +207,14 @@
         nsecs_t mVsyncPeriod;
         float mDpiX;
         float mDpiY;
+        int32_t mConfigGroup;
     };
 
     virtual hwc2_display_t getId() const = 0;
     virtual bool isConnected() const = 0;
     virtual void setConnected(bool connected) = 0; // For use by Device only
     virtual const std::unordered_set<DisplayCapability>& getCapabilities() const = 0;
+    virtual bool isVsyncPeriodSwitchSupported() const = 0;
 
     [[clang::warn_unused_result]] virtual Error acceptChanges() = 0;
     [[clang::warn_unused_result]] virtual Error createLayer(Layer** outLayer) = 0;
@@ -264,6 +277,12 @@
             uint32_t* outNumTypes, uint32_t* outNumRequests,
             android::sp<android::Fence>* outPresentFence, uint32_t* state) = 0;
     [[clang::warn_unused_result]] virtual Error setDisplayBrightness(float brightness) const = 0;
+    [[clang::warn_unused_result]] virtual Error getDisplayVsyncPeriod(
+            nsecs_t* outVsyncPeriod) const = 0;
+    [[clang::warn_unused_result]] virtual Error setActiveConfigWithConstraints(
+            const std::shared_ptr<const HWC2::Display::Config>& config,
+            const VsyncPeriodChangeConstraints& constraints,
+            VsyncPeriodChangeTimeline* outTimeline) = 0;
 };
 
 namespace impl {
@@ -323,6 +342,10 @@
     Error presentOrValidate(uint32_t* outNumTypes, uint32_t* outNumRequests,
                             android::sp<android::Fence>* outPresentFence, uint32_t* state) override;
     Error setDisplayBrightness(float brightness) const override;
+    Error getDisplayVsyncPeriod(nsecs_t* outVsyncPeriod) const override;
+    Error setActiveConfigWithConstraints(const std::shared_ptr<const HWC2::Display::Config>& config,
+                                         const VsyncPeriodChangeConstraints& constraints,
+                                         VsyncPeriodChangeTimeline* outTimeline) override;
 
     // Other Display methods
     hwc2_display_t getId() const override { return mId; }
@@ -331,6 +354,7 @@
     const std::unordered_set<DisplayCapability>& getCapabilities() const override {
         return mDisplayCapabilities;
     };
+    virtual bool isVsyncPeriodSwitchSupported() const override;
 
 private:
     int32_t getAttribute(hwc2_config_t configId, Attribute attribute);
@@ -355,7 +379,9 @@
     bool mIsConnected;
     DisplayType mType;
     std::unordered_map<hwc2_layer_t, std::unique_ptr<Layer>> mLayers;
+
     std::unordered_map<hwc2_config_t, std::shared_ptr<const Config>> mConfigs;
+
     std::once_flag mDisplayCapabilityQueryFlag;
     std::unordered_set<DisplayCapability> mDisplayCapabilities;
 };
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index d480f7c..d8dad0b 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -247,8 +247,8 @@
     // the refresh period and whatever closest timestamp we have.
     std::lock_guard lock(displayData.lastHwVsyncLock);
     nsecs_t now = systemTime(CLOCK_MONOTONIC);
-    auto vsyncPeriod = getActiveConfig(displayId)->getVsyncPeriod();
-    return now - ((now - displayData.lastHwVsync) % vsyncPeriod);
+    auto vsyncPeriodNanos = getDisplayVsyncPeriod(displayId);
+    return now - ((now - displayData.lastHwVsync) % vsyncPeriodNanos);
 }
 
 bool HWComposer::isConnected(DisplayId displayId) const {
@@ -291,6 +291,23 @@
     return config;
 }
 
+// Composer 2.4
+
+bool HWComposer::isVsyncPeriodSwitchSupported(DisplayId displayId) const {
+    return mDisplayData.at(displayId).hwcDisplay->isVsyncPeriodSwitchSupported();
+}
+
+nsecs_t HWComposer::getDisplayVsyncPeriod(DisplayId displayId) const {
+    nsecs_t vsyncPeriodNanos;
+    auto error = mDisplayData.at(displayId).hwcDisplay->getDisplayVsyncPeriod(&vsyncPeriodNanos);
+    if (error != HWC2::Error::None) {
+        LOG_DISPLAY_ERROR(displayId, "Failed to get Vsync Period");
+        return 0;
+    }
+
+    return vsyncPeriodNanos;
+}
+
 int HWComposer::getActiveConfigIndex(DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, -1);
 
@@ -541,7 +558,9 @@
     return NO_ERROR;
 }
 
-status_t HWComposer::setActiveConfig(DisplayId displayId, size_t configId) {
+status_t HWComposer::setActiveConfigWithConstraints(
+        DisplayId displayId, size_t configId, const HWC2::VsyncPeriodChangeConstraints& constraints,
+        HWC2::VsyncPeriodChangeTimeline* outTimeline) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     auto& displayData = mDisplayData[displayId];
@@ -550,7 +569,9 @@
         return BAD_INDEX;
     }
 
-    auto error = displayData.hwcDisplay->setActiveConfig(displayData.configMap[configId]);
+    auto error =
+            displayData.hwcDisplay->setActiveConfigWithConstraints(displayData.configMap[configId],
+                                                                   constraints, outTimeline);
     RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
     return NO_ERROR;
 }
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index e87c5c3..077e452 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -102,9 +102,6 @@
     // set power mode
     virtual status_t setPowerMode(DisplayId displayId, int mode) = 0;
 
-    // set active config
-    virtual status_t setActiveConfig(DisplayId displayId, size_t configId) = 0;
-
     // Sets a color transform to be applied to the result of composition
     virtual status_t setColorTransform(DisplayId displayId, const mat4& transform) = 0;
 
@@ -180,6 +177,14 @@
 
     virtual bool isUsingVrComposer() const = 0;
 
+    // Composer 2.4
+    virtual bool isVsyncPeriodSwitchSupported(DisplayId displayId) const = 0;
+    virtual nsecs_t getDisplayVsyncPeriod(DisplayId displayId) const = 0;
+    virtual status_t setActiveConfigWithConstraints(
+            DisplayId displayId, size_t configId,
+            const HWC2::VsyncPeriodChangeConstraints& constraints,
+            HWC2::VsyncPeriodChangeTimeline* outTimeline) = 0;
+
     // for debugging ----------------------------------------------------------
     virtual void dump(std::string& out) const = 0;
 
@@ -232,9 +237,6 @@
     // set power mode
     status_t setPowerMode(DisplayId displayId, int mode) override;
 
-    // set active config
-    status_t setActiveConfig(DisplayId displayId, size_t configId) override;
-
     // Sets a color transform to be applied to the result of composition
     status_t setColorTransform(DisplayId displayId, const mat4& transform) override;
 
@@ -305,6 +307,13 @@
 
     bool isUsingVrComposer() const override;
 
+    // Composer 2.4
+    bool isVsyncPeriodSwitchSupported(DisplayId displayId) const override;
+    nsecs_t getDisplayVsyncPeriod(DisplayId displayId) const override;
+    status_t setActiveConfigWithConstraints(DisplayId displayId, size_t configId,
+                                            const HWC2::VsyncPeriodChangeConstraints& constraints,
+                                            HWC2::VsyncPeriodChangeTimeline* outTimeline) override;
+
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
 
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index 4e0e4df..56b0ea6 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -42,13 +42,14 @@
     switch (type) {
         case compositionengine::DisplaySurface::COMPOSITION_UNKNOWN:
             return "UNKNOWN";
-        case compositionengine::DisplaySurface::COMPOSITION_GLES:
-            return "GLES";
+        case compositionengine::DisplaySurface::COMPOSITION_GPU:
+            return "GPU";
         case compositionengine::DisplaySurface::COMPOSITION_HWC:
             return "HWC";
         case compositionengine::DisplaySurface::COMPOSITION_MIXED:
             return "MIXED";
-        default:                                  return "<INVALID>";
+        default:
+            return "<INVALID>";
     }
 }
 
@@ -92,7 +93,7 @@
     mSinkBufferHeight = sinkHeight;
 
     // Pick the buffer format to request from the sink when not rendering to it
-    // with GLES. If the consumer needs CPU access, use the default format
+    // with GPU. If the consumer needs CPU access, use the default format
     // set by the consumer. Otherwise allow gralloc to decide the format based
     // on usage bits.
     int sinkUsage;
@@ -143,10 +144,10 @@
     mDbgState = DBG_STATE_PREPARED;
 
     mCompositionType = compositionType;
-    if (mForceHwcCopy && mCompositionType == COMPOSITION_GLES) {
+    if (mForceHwcCopy && mCompositionType == COMPOSITION_GPU) {
         // Some hardware can do RGB->YUV conversion more efficiently in hardware
         // controlled by HWC than in hardware controlled by the video encoder.
-        // Forcing GLES-composed frames to go through an extra copy by the HWC
+        // Forcing GPU-composed frames to go through an extra copy by the HWC
         // allows the format conversion to happen there, rather than passing RGB
         // directly to the consumer.
         //
@@ -161,18 +162,17 @@
         mDbgLastCompositionType = mCompositionType;
     }
 
-    if (mCompositionType != COMPOSITION_GLES &&
-            (mOutputFormat != mDefaultOutputFormat ||
-             mOutputUsage != GRALLOC_USAGE_HW_COMPOSER)) {
-        // We must have just switched from GLES-only to MIXED or HWC
-        // composition. Stop using the format and usage requested by the GLES
+    if (mCompositionType != COMPOSITION_GPU &&
+        (mOutputFormat != mDefaultOutputFormat || mOutputUsage != GRALLOC_USAGE_HW_COMPOSER)) {
+        // We must have just switched from GPU-only to MIXED or HWC
+        // composition. Stop using the format and usage requested by the GPU
         // driver; they may be suboptimal when HWC is writing to the output
         // buffer. For example, if the output is going to a video encoder, and
         // HWC can write directly to YUV, some hardware can skip a
         // memory-to-memory RGB-to-YUV conversion step.
         //
-        // If we just switched *to* GLES-only mode, we'll change the
-        // format/usage and get a new buffer when the GLES driver calls
+        // If we just switched *to* GPU-only mode, we'll change the
+        // format/usage and get a new buffer when the GPU driver calls
         // dequeueBuffer().
         mOutputFormat = mDefaultOutputFormat;
         mOutputUsage = GRALLOC_USAGE_HW_COMPOSER;
@@ -192,17 +192,16 @@
                 "Unexpected advanceFrame() in %s state on HWC frame",
                 dbgStateStr());
     } else {
-        VDS_LOGW_IF(mDbgState != DBG_STATE_GLES_DONE,
-                "Unexpected advanceFrame() in %s state on GLES/MIXED frame",
-                dbgStateStr());
+        VDS_LOGW_IF(mDbgState != DBG_STATE_GPU_DONE,
+                    "Unexpected advanceFrame() in %s state on GPU/MIXED frame", dbgStateStr());
     }
     mDbgState = DBG_STATE_HWC;
 
     if (mOutputProducerSlot < 0 ||
             (mCompositionType != COMPOSITION_HWC && mFbProducerSlot < 0)) {
         // Last chance bailout if something bad happened earlier. For example,
-        // in a GLES configuration, if the sink disappears then dequeueBuffer
-        // will fail, the GLES driver won't queue a buffer, but SurfaceFlinger
+        // in a graphics API configuration, if the sink disappears then dequeueBuffer
+        // will fail, the GPU driver won't queue a buffer, but SurfaceFlinger
         // will soldier on. So we end up here without a buffer. There should
         // be lots of scary messages in the log just before this.
         VDS_LOGE("advanceFrame: no buffer, bailing out");
@@ -302,9 +301,8 @@
         return mSource[SOURCE_SINK]->requestBuffer(pslot, outBuf);
     }
 
-    VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
-            "Unexpected requestBuffer pslot=%d in %s state",
-            pslot, dbgStateStr());
+    VDS_LOGW_IF(mDbgState != DBG_STATE_GPU, "Unexpected requestBuffer pslot=%d in %s state", pslot,
+                dbgStateStr());
 
     *outBuf = mProducerBuffers[pslot];
     return NO_ERROR;
@@ -374,7 +372,7 @@
 
     VDS_LOGW_IF(mDbgState != DBG_STATE_PREPARED,
             "Unexpected dequeueBuffer() in %s state", dbgStateStr());
-    mDbgState = DBG_STATE_GLES;
+    mDbgState = DBG_STATE_GPU;
 
     VDS_LOGV("dequeueBuffer %dx%d fmt=%d usage=%#" PRIx64, w, h, format, usage);
 
@@ -385,18 +383,18 @@
 
         if (mOutputProducerSlot < 0) {
             // Last chance bailout if something bad happened earlier. For example,
-            // in a GLES configuration, if the sink disappears then dequeueBuffer
-            // will fail, the GLES driver won't queue a buffer, but SurfaceFlinger
+            // in a graphics API configuration, if the sink disappears then dequeueBuffer
+            // will fail, the GPU driver won't queue a buffer, but SurfaceFlinger
             // will soldier on. So we end up here without a buffer. There should
             // be lots of scary messages in the log just before this.
             VDS_LOGE("dequeueBuffer: no buffer, bailing out");
             return NO_MEMORY;
         }
 
-        // We already dequeued the output buffer. If the GLES driver wants
+        // We already dequeued the output buffer. If the GPU driver wants
         // something incompatible, we have to cancel and get a new one. This
         // will mean that HWC will see a different output buffer between
-        // prepare and set, but since we're in GLES-only mode already it
+        // prepare and set, but since we're in GPU-only mode already it
         // shouldn't matter.
 
         usage |= GRALLOC_USAGE_HW_COMPOSER;
@@ -458,10 +456,9 @@
         return mSource[SOURCE_SINK]->queueBuffer(pslot, input, output);
     }
 
-    VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
-            "Unexpected queueBuffer(pslot=%d) in %s state", pslot,
-            dbgStateStr());
-    mDbgState = DBG_STATE_GLES_DONE;
+    VDS_LOGW_IF(mDbgState != DBG_STATE_GPU, "Unexpected queueBuffer(pslot=%d) in %s state", pslot,
+                dbgStateStr());
+    mDbgState = DBG_STATE_GPU_DONE;
 
     VDS_LOGV("queueBuffer pslot=%d", pslot);
 
@@ -488,11 +485,11 @@
         mFbFence = mSlots[item.mSlot].mFence;
 
     } else {
-        LOG_FATAL_IF(mCompositionType != COMPOSITION_GLES,
-                "Unexpected queueBuffer in state %s for compositionType %s",
-                dbgStateStr(), dbgCompositionTypeStr(mCompositionType));
+        LOG_FATAL_IF(mCompositionType != COMPOSITION_GPU,
+                     "Unexpected queueBuffer in state %s for compositionType %s", dbgStateStr(),
+                     dbgCompositionTypeStr(mCompositionType));
 
-        // Extract the GLES release fence for HWC to acquire
+        // Extract the GPU release fence for HWC to acquire
         int64_t timestamp;
         bool isAutoTimestamp;
         android_dataspace dataSpace;
@@ -517,9 +514,8 @@
         return mSource[SOURCE_SINK]->cancelBuffer(mapProducer2SourceSlot(SOURCE_SINK, pslot), fence);
     }
 
-    VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
-            "Unexpected cancelBuffer(pslot=%d) in %s state", pslot,
-            dbgStateStr());
+    VDS_LOGW_IF(mDbgState != DBG_STATE_GPU, "Unexpected cancelBuffer(pslot=%d) in %s state", pslot,
+                dbgStateStr());
     VDS_LOGV("cancelBuffer pslot=%d", pslot);
     Source source = fbSourceForCompositionType(mCompositionType);
     return mSource[source]->cancelBuffer(
@@ -641,8 +637,8 @@
         return result;
     mOutputProducerSlot = mapSource2ProducerSlot(SOURCE_SINK, sslot);
 
-    // On GLES-only frames, we don't have the right output buffer acquire fence
-    // until after GLES calls queueBuffer(). So here we just set the buffer
+    // On GPU-only frames, we don't have the right output buffer acquire fence
+    // until after GPU calls queueBuffer(). So here we just set the buffer
     // (for use in HWC prepare) but not the fence; we'll call this again with
     // the proper fence once we have it.
     result = mHwc.setOutputBuffer(*mDisplayId, Fence::NO_FENCE,
@@ -672,12 +668,18 @@
 
 const char* VirtualDisplaySurface::dbgStateStr() const {
     switch (mDbgState) {
-        case DBG_STATE_IDLE:      return "IDLE";
-        case DBG_STATE_PREPARED:  return "PREPARED";
-        case DBG_STATE_GLES:      return "GLES";
-        case DBG_STATE_GLES_DONE: return "GLES_DONE";
-        case DBG_STATE_HWC:       return "HWC";
-        default:                  return "INVALID";
+        case DBG_STATE_IDLE:
+            return "IDLE";
+        case DBG_STATE_PREPARED:
+            return "PREPARED";
+        case DBG_STATE_GPU:
+            return "GPU";
+        case DBG_STATE_GPU_DONE:
+            return "GPU_DONE";
+        case DBG_STATE_HWC:
+            return "HWC";
+        default:
+            return "INVALID";
     }
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index d6543d1..3cbad8f 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -34,23 +34,23 @@
 class HWComposer;
 class IProducerListener;
 
-/* This DisplaySurface implementation supports virtual displays, where GLES
+/* This DisplaySurface implementation supports virtual displays, where GPU
  * and/or HWC compose into a buffer that is then passed to an arbitrary
  * consumer (the sink) running in another process.
  *
  * The simplest case is when the virtual display will never use the h/w
  * composer -- either the h/w composer doesn't support writing to buffers, or
  * there are more virtual displays than it supports simultaneously. In this
- * case, the GLES driver works directly with the output buffer queue, and
+ * case, the GPU driver works directly with the output buffer queue, and
  * calls to the VirtualDisplay from SurfaceFlinger and DisplayHardware do
  * nothing.
  *
  * If h/w composer might be used, then each frame will fall into one of three
- * configurations: GLES-only, HWC-only, and MIXED composition. In all of these,
+ * configurations: GPU-only, HWC-only, and MIXED composition. In all of these,
  * we must provide a FB target buffer and output buffer for the HWC set() call.
  *
- * In GLES-only composition, the GLES driver is given a buffer from the sink to
- * render into. When the GLES driver queues the buffer to the
+ * In GPU-only composition, the GPU driver is given a buffer from the sink to
+ * render into. When the GPU driver queues the buffer to the
  * VirtualDisplaySurface, the VirtualDisplaySurface holds onto it instead of
  * immediately queueing it to the sink. The buffer is used as both the FB
  * target and output buffer for HWC, though on these frames the HWC doesn't
@@ -65,7 +65,7 @@
  *
  * On MIXED frames, things become more complicated, since some h/w composer
  * implementations can't read from and write to the same buffer. This class has
- * an internal BufferQueue that it uses as a scratch buffer pool. The GLES
+ * an internal BufferQueue that it uses as a scratch buffer pool. The GPU
  * driver is given a scratch buffer to render into. When it finishes rendering,
  * the buffer is queued and then immediately acquired by the
  * VirtualDisplaySurface. The scratch buffer is then used as the FB target
@@ -99,7 +99,7 @@
     virtual ~VirtualDisplaySurface();
 
     //
-    // IGraphicBufferProducer interface, used by the GLES driver.
+    // IGraphicBufferProducer interface, used by the GPU driver.
     //
     virtual status_t requestBuffer(int pslot, sp<GraphicBuffer>* outBuf);
     virtual status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers);
@@ -144,7 +144,7 @@
 
     // Both the sink and scratch buffer pools have their own set of slots
     // ("source slots", or "sslot"). We have to merge these into the single
-    // set of slots used by the GLES producer ("producer slots" or "pslot") and
+    // set of slots used by the graphics producer ("producer slots" or "pslot") and
     // internally in the VirtualDisplaySurface. To minimize the number of times
     // a producer slot switches which source it comes from, we map source slot
     // numbers to producer slot numbers differently for each source.
@@ -166,12 +166,12 @@
 
     // To avoid buffer reallocations, we track the buffer usage and format
     // we used on the previous frame and use it again on the new frame. If
-    // the composition type changes or the GLES driver starts requesting
+    // the composition type changes or the GPU driver starts requesting
     // different usage/format, we'll get a new buffer.
     uint32_t mOutputFormat;
     uint64_t mOutputUsage;
 
-    // Since we present a single producer interface to the GLES driver, but
+    // Since we present a single producer interface to the GPU driver, but
     // are internally muxing between the sink and scratch producers, we have
     // to keep track of which source last returned each producer slot from
     // dequeueBuffer. Each bit in mProducerSlotSource corresponds to a producer
@@ -181,7 +181,7 @@
     sp<GraphicBuffer> mProducerBuffers[BufferQueueDefs::NUM_BUFFER_SLOTS];
 
     // The QueueBufferOutput with the latest info from the sink, and with the
-    // transform hint cleared. Since we defer queueBuffer from the GLES driver
+    // transform hint cleared. Since we defer queueBuffer from the GPU driver
     // to the sink, we have to return the previous version.
     // Moves instead of copies are performed to avoid duplicate
     // FrameEventHistoryDeltas.
@@ -195,7 +195,7 @@
     // Intra-frame state
     //
 
-    // Composition type and GLES buffer source for the current frame.
+    // Composition type and graphics buffer source for the current frame.
     // Valid after prepareFrame(), cleared in onFrameCommitted.
     CompositionType mCompositionType;
 
@@ -221,13 +221,13 @@
     // +-----------+-------------------+-------------+
     // | IDLE      | beginFrame        || BEGUN      |
     // | BEGUN     | prepareFrame      || PREPARED   |
-    // | PREPARED  | dequeueBuffer [1] || GLES       |
+    // | PREPARED  | dequeueBuffer [1] || GPU        |
     // | PREPARED  | advanceFrame [2]  || HWC        |
-    // | GLES      | queueBuffer       || GLES_DONE  |
-    // | GLES_DONE | advanceFrame      || HWC        |
+    // | GPU       | queueBuffer       || GPU_DONE   |
+    // | GPU_DONE  | advanceFrame      || HWC        |
     // | HWC       | onFrameCommitted  || IDLE       |
     // +-----------+-------------------++------------+
-    // [1] COMPOSITION_GLES and COMPOSITION_MIXED frames.
+    // [1] COMPOSITION_GPU and COMPOSITION_MIXED frames.
     // [2] COMPOSITION_HWC frames.
     //
     enum DbgState {
@@ -236,12 +236,12 @@
         // output buffer dequeued, framebuffer source not yet known
         DBG_STATE_BEGUN,
         // output buffer dequeued, framebuffer source known but not provided
-        // to GLES yet.
+        // to GPU yet.
         DBG_STATE_PREPARED,
-        // GLES driver has a buffer dequeued
-        DBG_STATE_GLES,
-        // GLES driver has queued the buffer, we haven't sent it to HWC yet
-        DBG_STATE_GLES_DONE,
+        // GPU driver has a buffer dequeued
+        DBG_STATE_GPU,
+        // GPU driver has queued the buffer, we haven't sent it to HWC yet
+        DBG_STATE_GPU_DONE,
         // HWC has the buffer for this frame
         DBG_STATE_HWC,
     };
diff --git a/services/surfaceflinger/FrameTracer/FrameTracer.cpp b/services/surfaceflinger/FrameTracer/FrameTracer.cpp
index 6f91843..4418116 100644
--- a/services/surfaceflinger/FrameTracer/FrameTracer.cpp
+++ b/services/surfaceflinger/FrameTracer/FrameTracer.cpp
@@ -21,6 +21,7 @@
 #include "FrameTracer.h"
 
 #include <android-base/stringprintf.h>
+#include <perfetto/trace/clock_snapshot.pbzero.h>
 
 #include <algorithm>
 #include <mutex>
@@ -29,6 +30,7 @@
 
 namespace android {
 
+using Clock = perfetto::protos::pbzero::ClockSnapshot::Clock;
 void FrameTracer::initialize() {
     std::call_once(mInitializationFlag, [this]() {
         perfetto::TracingInitArgs args;
@@ -130,6 +132,7 @@
                               uint64_t bufferID, uint64_t frameNumber, nsecs_t timestamp,
                               FrameEvent::BufferEventType type, nsecs_t duration) {
     auto packet = ctx.NewTracePacket();
+    packet->set_timestamp_clock_id(Clock::MONOTONIC);
     packet->set_timestamp(timestamp);
     auto* event = packet->set_graphics_frame_event()->set_buffer_event();
     event->set_buffer_id(static_cast<uint32_t>(bufferID));
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index ce9aab5..35fc4be 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -571,6 +571,53 @@
     return layerSettings;
 }
 
+std::optional<renderengine::LayerSettings> Layer::prepareShadowClientComposition(
+        const renderengine::LayerSettings& casterLayerSettings, const Rect& displayViewport,
+        ui::Dataspace outputDataspace) {
+    renderengine::ShadowSettings shadow = getShadowSettings(displayViewport);
+    if (shadow.length <= 0.f) {
+        return {};
+    }
+
+    const float casterAlpha = casterLayerSettings.alpha;
+    const bool casterIsOpaque = ((casterLayerSettings.source.buffer.buffer != nullptr) &&
+                                 casterLayerSettings.source.buffer.isOpaque);
+
+    renderengine::LayerSettings shadowLayer = casterLayerSettings;
+    shadowLayer.shadow = shadow;
+
+    // If the casting layer is translucent, we need to fill in the shadow underneath the layer.
+    // Otherwise the generated shadow will only be shown around the casting layer.
+    shadowLayer.shadow.casterIsTranslucent = !casterIsOpaque || (casterAlpha < 1.0f);
+    shadowLayer.shadow.ambientColor *= casterAlpha;
+    shadowLayer.shadow.spotColor *= casterAlpha;
+    shadowLayer.sourceDataspace = outputDataspace;
+    shadowLayer.source.buffer.buffer = nullptr;
+
+    if (shadowLayer.shadow.ambientColor.a <= 0.f && shadowLayer.shadow.spotColor.a <= 0.f) {
+        return {};
+    }
+
+    float casterCornerRadius = shadowLayer.geometry.roundedCornersRadius;
+    const FloatRect& cornerRadiusCropRect = casterLayerSettings.geometry.roundedCornersCrop;
+    const FloatRect& casterRect = shadowLayer.geometry.boundaries;
+
+    // crop used to set the corner radius may be larger than the content rect. Adjust the corner
+    // radius accordingly.
+    if (casterCornerRadius > 0.f) {
+        float cropRectOffset = std::max(std::abs(cornerRadiusCropRect.top - casterRect.top),
+                                        std::abs(cornerRadiusCropRect.left - casterRect.left));
+        if (cropRectOffset > casterCornerRadius) {
+            casterCornerRadius = 0;
+        } else {
+            casterCornerRadius -= cropRectOffset;
+        }
+        shadowLayer.geometry.roundedCornersRadius = casterCornerRadius;
+    }
+
+    return shadowLayer;
+}
+
 Hwc2::IComposerClient::Composition Layer::getCompositionType(
         const sp<const DisplayDevice>& display) const {
     const auto outputLayer = findOutputLayerForDisplay(display);
@@ -1800,6 +1847,18 @@
             : RoundedCornerState();
 }
 
+renderengine::ShadowSettings Layer::getShadowSettings(const Rect& viewport) const {
+    renderengine::ShadowSettings state = mFlinger->mDrawingState.globalShadowSettings;
+
+    // Shift the spot light x-position to the middle of the display and then
+    // offset it by casting layer's screen pos.
+    state.lightPos.x = (viewport.width() / 2.f) - mScreenBounds.left;
+    state.lightPos.y -= mScreenBounds.top;
+
+    state.length = mEffectiveShadowRadius;
+    return state;
+}
+
 void Layer::commitChildList() {
     for (size_t i = 0; i < mCurrentChildren.size(); i++) {
         const auto& child = mCurrentChildren[i];
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 286311b..843d3ae 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -216,6 +216,9 @@
         std::deque<sp<CallbackHandle>> callbackHandles;
         bool colorSpaceAgnostic;
         nsecs_t desiredPresentTime = -1;
+
+        // Length of the cast shadow. If the radius is > 0, a shadow of length shadowRadius will
+        // be rendered around the layer.
         float shadowRadius;
     };
 
@@ -507,6 +510,9 @@
     void latchCursorCompositionState(compositionengine::LayerFECompositionState&) const override;
     std::optional<renderengine::LayerSettings> prepareClientComposition(
             compositionengine::LayerFE::ClientCompositionTargetSettings&) override;
+    std::optional<renderengine::LayerSettings> prepareShadowClientComposition(
+            const renderengine::LayerSettings& layerSettings, const Rect& displayViewport,
+            ui::Dataspace outputDataspace) override;
     void onLayerDisplayed(const sp<Fence>& releaseFence) override;
     const char* getDebugName() const override;
 
@@ -601,8 +607,6 @@
 
     virtual sp<GraphicBuffer> getBuffer() const { return nullptr; }
 
-    virtual uint64_t getCurrentFrameNumber() const { return mCurrentFrameNumber; }
-
     /*
      * Returns if a frame is ready
      */
@@ -652,6 +656,8 @@
     // ignored.
     RoundedCornerState getRoundedCornerState() const;
 
+    renderengine::ShadowSettings getShadowSettings(const Rect& viewport) const;
+
     void traverseInReverseZOrder(LayerVector::StateSet stateSet,
                                  const LayerVector::Visitor& visitor);
     void traverseInZOrder(LayerVector::StateSet stateSet, const LayerVector::Visitor& visitor);
@@ -664,6 +670,15 @@
                                   const LayerVector::Visitor& visitor);
 
     size_t getChildrenCount() const;
+
+    // ONLY CALL THIS FROM THE LAYER DTOR!
+    // See b/141111965.  We need to add current children to offscreen layers in
+    // the layer dtor so as not to dangle layers.  Since the layer has not
+    // committed its transaction when the layer is destroyed, we must add
+    // current children.  This is safe in the dtor as we will no longer update
+    // the current state, but should not be called anywhere else!
+    LayerVector& getCurrentChildren() { return mCurrentChildren; }
+
     void addChild(const sp<Layer>& layer);
     // Returns index if removed, or negative value otherwise
     // for symmetry with Vector::remove
@@ -701,6 +716,14 @@
 
     Region debugGetVisibleRegionOnDefaultDisplay() const;
 
+    /**
+     * Returns the cropped buffer size or the layer crop if the layer has no buffer. Return
+     * INVALID_RECT if the layer has no buffer and no crop.
+     * A layer with an invalid buffer size and no crop is considered to be boundless. The layer
+     * bounds are constrained by its parent bounds.
+     */
+    Rect getCroppedBufferSize(const Layer::State& s) const;
+
 protected:
     // constant
     sp<SurfaceFlinger> mFlinger;
@@ -909,13 +932,6 @@
                                        const LayerVector::Visitor& visitor);
     LayerVector makeChildrenTraversalList(LayerVector::StateSet stateSet,
                                           const std::vector<Layer*>& layersInTree);
-    /**
-     * Returns the cropped buffer size or the layer crop if the layer has no buffer. Return
-     * INVALID_RECT if the layer has no buffer and no crop.
-     * A layer with an invalid buffer size and no crop is considered to be boundless. The layer
-     * bounds are constrained by its parent bounds.
-     */
-    Rect getCroppedBufferSize(const Layer::State& s) const;
 
     // Cached properties computed from drawing state
     // Effective transform taking into account parent transforms and any parent scaling.
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 976fedb..38a80a7 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -20,8 +20,6 @@
 
 namespace android {
 
-using RefreshRateType = scheduler::RefreshRateConfigs::RefreshRateType;
-
 RefreshRateOverlay::RefreshRateOverlay(SurfaceFlinger& flinger)
       : mFlinger(flinger), mClient(new Client(&mFlinger)) {
     createLayer();
@@ -51,8 +49,8 @@
     return true;
 }
 
-void RefreshRateOverlay::changeRefreshRate(RefreshRateType type) {
-    const half3& color = (type == RefreshRateType::PERFORMANCE) ? GREEN : RED;
+void RefreshRateOverlay::changeRefreshRate(const RefreshRate& refreshRate) {
+    const half3& color = (refreshRate.fps > 65.0f) ? GREEN : RED;
     mLayer->setColor(color);
     mFlinger.mTransactionFlags.fetch_or(eTransactionMask);
 }
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index ce29bc3..414bc47 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -19,13 +19,13 @@
 
 namespace android {
 
-using RefreshRateType = scheduler::RefreshRateConfigs::RefreshRateType;
+using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
 
 class RefreshRateOverlay {
 public:
     RefreshRateOverlay(SurfaceFlinger& flinger);
 
-    void changeRefreshRate(RefreshRateType type);
+    void changeRefreshRate(const RefreshRate& refreshRate);
 
 private:
     bool createLayer();
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index edc6442..532572f 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -22,13 +22,14 @@
     static float getCaptureFillValue(CaptureFill captureFill);
 
     RenderArea(uint32_t reqWidth, uint32_t reqHeight, CaptureFill captureFill,
-               ui::Dataspace reqDataSpace,
+               ui::Dataspace reqDataSpace, const Rect& displayViewport,
                ui::Transform::orientation_flags rotation = ui::Transform::ROT_0)
           : mReqWidth(reqWidth),
             mReqHeight(reqHeight),
             mReqDataSpace(reqDataSpace),
             mCaptureFill(captureFill),
-            mRotationFlags(rotation) {}
+            mRotationFlags(rotation),
+            mDisplayViewport(displayViewport) {}
 
     virtual ~RenderArea() = default;
 
@@ -80,12 +81,16 @@
 
     virtual const sp<const DisplayDevice> getDisplayDevice() const = 0;
 
+    // Returns the source display viewport.
+    const Rect& getDisplayViewport() const { return mDisplayViewport; }
+
 private:
     const uint32_t mReqWidth;
     const uint32_t mReqHeight;
     const ui::Dataspace mReqDataSpace;
     const CaptureFill mCaptureFill;
     const ui::Transform::orientation_flags mRotationFlags;
+    const Rect mDisplayViewport;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 8d9adc8..ff800c3 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -36,6 +36,7 @@
 #include <utils/Trace.h>
 
 #include "EventThread.h"
+#include "HwcStrongTypes.h"
 
 using namespace std::chrono_literals;
 
@@ -101,10 +102,11 @@
     return event;
 }
 
-DisplayEventReceiver::Event makeConfigChanged(PhysicalDisplayId displayId, int32_t configId) {
+DisplayEventReceiver::Event makeConfigChanged(PhysicalDisplayId displayId,
+                                              HwcConfigIndexType configId) {
     DisplayEventReceiver::Event event;
     event.header = {DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED, displayId, systemTime()};
-    event.config.configId = configId;
+    event.config.configId = configId.value();
     return event;
 }
 
@@ -290,7 +292,7 @@
     mCondition.notify_all();
 }
 
-void EventThread::onConfigChanged(PhysicalDisplayId displayId, int32_t configId) {
+void EventThread::onConfigChanged(PhysicalDisplayId displayId, HwcConfigIndexType configId) {
     std::lock_guard<std::mutex> lock(mMutex);
 
     mPendingEvents.push_back(makeConfigChanged(displayId, configId));
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index a029586..a42546c 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -33,6 +33,7 @@
 #include <private/gui/BitTube.h>
 
 #include <utils/Errors.h>
+#include "HwcStrongTypes.h"
 
 // ---------------------------------------------------------------------------
 namespace android {
@@ -109,7 +110,7 @@
     virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0;
 
     // called when SF changes the active config and apps needs to be notified about the change
-    virtual void onConfigChanged(PhysicalDisplayId displayId, int32_t configId) = 0;
+    virtual void onConfigChanged(PhysicalDisplayId displayId, HwcConfigIndexType configId) = 0;
 
     virtual void dump(std::string& result) const = 0;
 
@@ -146,7 +147,7 @@
 
     void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override;
 
-    void onConfigChanged(PhysicalDisplayId displayId, int32_t configId) override;
+    void onConfigChanged(PhysicalDisplayId displayId, HwcConfigIndexType configId) override;
 
     void dump(std::string& result) const override;
 
diff --git a/services/surfaceflinger/Scheduler/HwcStrongTypes.h b/services/surfaceflinger/Scheduler/HwcStrongTypes.h
new file mode 100644
index 0000000..cfbbdfe
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/HwcStrongTypes.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "StrongTyping.h"
+
+namespace android {
+
+// Strong types for the different indexes as they are referring to a different base.
+using HwcConfigIndexType = StrongTyping<int, struct HwcConfigIndexTypeTag, Compare, Add, Hash>;
+using HwcConfigGroupType = StrongTyping<int, struct HwcConfigGroupTypeTag, Compare>;
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 8b71728..146ec1b 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -39,7 +39,7 @@
 namespace {
 
 bool isLayerActive(const Layer& layer, const LayerInfo& info, nsecs_t threshold) {
-    return layer.isVisible() && (info.isHDR() || info.getLastUpdatedTime() >= threshold);
+    return layer.isVisible() && info.getLastUpdatedTime() >= threshold;
 }
 
 bool traceEnabled() {
@@ -69,7 +69,7 @@
     mLayerInfos.emplace_back(layer, std::move(info));
 }
 
-void LayerHistory::record(Layer* layer, nsecs_t presentTime, bool isHDR, nsecs_t now) {
+void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now) {
     std::lock_guard lock(mLock);
 
     const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
@@ -78,7 +78,6 @@
 
     const auto& info = it->second;
     info->setLastPresentTime(presentTime, now);
-    info->setIsHDR(isHDR);
 
     // Activate layer if inactive.
     if (const auto end = activeLayers().end(); it >= end) {
@@ -89,7 +88,6 @@
 
 LayerHistory::Summary LayerHistory::summarize(nsecs_t now) {
     float maxRefreshRate = 0;
-    bool isHDR = false;
 
     std::lock_guard lock(mLock);
 
@@ -108,13 +106,12 @@
                 trace(layer, std::round(refreshRate));
             }
         }
-        isHDR |= info->isHDR();
     }
     if (CC_UNLIKELY(mTraceEnabled)) {
-        ALOGD("%s: maxRefreshRate=%.2f, isHDR=%d", __FUNCTION__, maxRefreshRate, isHDR);
+        ALOGD("%s: maxRefreshRate=%.2f", __FUNCTION__, maxRefreshRate);
     }
 
-    return {maxRefreshRate, isHDR};
+    return {maxRefreshRate};
 }
 
 void LayerHistory::partitionLayers(nsecs_t now) {
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index bd9aca1..745c4c1 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -46,11 +46,10 @@
     void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate);
 
     // Marks the layer as active, and records the given state to its history.
-    void record(Layer*, nsecs_t presentTime, bool isHDR, nsecs_t now);
+    void record(Layer*, nsecs_t presentTime, nsecs_t now);
 
     struct Summary {
         float maxRefreshRate; // Maximum refresh rate among recently active layers.
-        bool isHDR;           // True if any recently active layer has HDR content.
     };
 
     // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index b86709f..cb81ca2 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -140,9 +140,6 @@
     // updated time, the updated time is the present time.
     void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now);
 
-    bool isHDR() const { return mIsHDR; }
-    void setIsHDR(bool isHDR) { mIsHDR = isHDR; }
-
     bool isRecentlyActive(nsecs_t now) const { return mPresentTimeHistory.isRecentlyActive(now); }
     bool isFrequent(nsecs_t now) const { return mPresentTimeHistory.isFrequent(now); }
 
@@ -167,7 +164,6 @@
     nsecs_t mLastPresentTime = 0;
     RefreshRateHistory mRefreshRateHistory{mHighRefreshRate};
     PresentTimeHistory mPresentTimeHistory;
-    bool mIsHDR = false;
 };
 
 } // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/PhaseOffsets.cpp b/services/surfaceflinger/Scheduler/PhaseOffsets.cpp
index 6be88f8..12832a6 100644
--- a/services/surfaceflinger/Scheduler/PhaseOffsets.cpp
+++ b/services/surfaceflinger/Scheduler/PhaseOffsets.cpp
@@ -48,16 +48,18 @@
             getProperty("debug.sf.phase_offset_threshold_for_next_vsync_ns")
                     .value_or(std::numeric_limits<nsecs_t>::max());
 
-    const Offsets defaultOffsets = getDefaultOffsets(thresholdForNextVsync);
-    const Offsets highFpsOffsets = getHighFpsOffsets(thresholdForNextVsync);
-
-    mOffsets.insert({RefreshRateType::DEFAULT, defaultOffsets});
-    mOffsets.insert({RefreshRateType::PERFORMANCE, highFpsOffsets});
+    mDefaultOffsets = getDefaultOffsets(thresholdForNextVsync);
+    mHighFpsOffsets = getHighFpsOffsets(thresholdForNextVsync);
 }
 
-PhaseOffsets::Offsets PhaseOffsets::getOffsetsForRefreshRate(
-        RefreshRateType refreshRateType) const {
-    return mOffsets.at(refreshRateType);
+PhaseOffsets::Offsets PhaseOffsets::getOffsetsForRefreshRate(float fps) const {
+    // TODO(145561086): Once offsets are common for all refresh rates we can remove the magic
+    // number for refresh rate
+    if (fps > 65.0f) {
+        return mHighFpsOffsets;
+    } else {
+        return mDefaultOffsets;
+    }
 }
 
 void PhaseOffsets::dump(std::string& result) const {
@@ -80,13 +82,13 @@
     const auto earlyAppOffsetNs = getProperty("debug.sf.early_app_phase_offset_ns");
     const auto earlyGlAppOffsetNs = getProperty("debug.sf.early_gl_app_phase_offset_ns");
 
-    return {{RefreshRateType::DEFAULT, earlySfOffsetNs.value_or(sfVsyncPhaseOffsetNs),
+    return {{earlySfOffsetNs.value_or(sfVsyncPhaseOffsetNs),
              earlyAppOffsetNs.value_or(vsyncPhaseOffsetNs)},
 
-            {RefreshRateType::DEFAULT, earlyGlSfOffsetNs.value_or(sfVsyncPhaseOffsetNs),
+            {earlyGlSfOffsetNs.value_or(sfVsyncPhaseOffsetNs),
              earlyGlAppOffsetNs.value_or(vsyncPhaseOffsetNs)},
 
-            {RefreshRateType::DEFAULT, sfVsyncPhaseOffsetNs, vsyncPhaseOffsetNs},
+            {sfVsyncPhaseOffsetNs, vsyncPhaseOffsetNs},
 
             thresholdForNextVsync};
 }
@@ -104,13 +106,13 @@
     const auto highFpsEarlyGlAppOffsetNs =
             getProperty("debug.sf.high_fps_early_gl_app_phase_offset_ns");
 
-    return {{RefreshRateType::PERFORMANCE, highFpsEarlySfOffsetNs.value_or(highFpsLateSfOffsetNs),
+    return {{highFpsEarlySfOffsetNs.value_or(highFpsLateSfOffsetNs),
              highFpsEarlyAppOffsetNs.value_or(highFpsLateAppOffsetNs)},
 
-            {RefreshRateType::PERFORMANCE, highFpsEarlyGlSfOffsetNs.value_or(highFpsLateSfOffsetNs),
+            {highFpsEarlyGlSfOffsetNs.value_or(highFpsLateSfOffsetNs),
              highFpsEarlyGlAppOffsetNs.value_or(highFpsLateAppOffsetNs)},
 
-            {RefreshRateType::PERFORMANCE, highFpsLateSfOffsetNs, highFpsLateAppOffsetNs},
+            {highFpsLateSfOffsetNs, highFpsLateAppOffsetNs},
 
             thresholdForNextVsync};
 }
diff --git a/services/surfaceflinger/Scheduler/PhaseOffsets.h b/services/surfaceflinger/Scheduler/PhaseOffsets.h
index 2c52432..7747f0c 100644
--- a/services/surfaceflinger/Scheduler/PhaseOffsets.h
+++ b/services/surfaceflinger/Scheduler/PhaseOffsets.h
@@ -32,7 +32,6 @@
 class PhaseOffsets {
 public:
     using Offsets = VSyncModulator::OffsetsConfig;
-    using RefreshRateType = RefreshRateConfigs::RefreshRateType;
 
     virtual ~PhaseOffsets();
 
@@ -43,9 +42,9 @@
     }
 
     virtual Offsets getCurrentOffsets() const = 0;
-    virtual Offsets getOffsetsForRefreshRate(RefreshRateType) const = 0;
+    virtual Offsets getOffsetsForRefreshRate(float fps) const = 0;
 
-    virtual void setRefreshRateType(RefreshRateType) = 0;
+    virtual void setRefreshRateFps(float fps) = 0;
 
     virtual void dump(std::string& result) const = 0;
 };
@@ -57,18 +56,14 @@
     PhaseOffsets();
 
     // Returns early, early GL, and late offsets for Apps and SF for a given refresh rate.
-    Offsets getOffsetsForRefreshRate(RefreshRateType) const override;
+    Offsets getOffsetsForRefreshRate(float fps) const override;
 
     // Returns early, early GL, and late offsets for Apps and SF.
-    Offsets getCurrentOffsets() const override {
-        return getOffsetsForRefreshRate(mRefreshRateType);
-    }
+    Offsets getCurrentOffsets() const override { return getOffsetsForRefreshRate(mRefreshRateFps); }
 
     // This function should be called when the device is switching between different
     // refresh rates, to properly update the offsets.
-    void setRefreshRateType(RefreshRateType refreshRateType) override {
-        mRefreshRateType = refreshRateType;
-    }
+    void setRefreshRateFps(float fps) override { mRefreshRateFps = fps; }
 
     // Returns current offsets in human friendly format.
     void dump(std::string& result) const override;
@@ -77,9 +72,10 @@
     static Offsets getDefaultOffsets(nsecs_t thresholdForNextVsync);
     static Offsets getHighFpsOffsets(nsecs_t thresholdForNextVsync);
 
-    std::atomic<RefreshRateType> mRefreshRateType = RefreshRateType::DEFAULT;
+    std::atomic<float> mRefreshRateFps = 0;
 
-    std::unordered_map<RefreshRateType, Offsets> mOffsets;
+    Offsets mDefaultOffsets;
+    Offsets mHighFpsOffsets;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
new file mode 100644
index 0000000..23fb96a
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#include "RefreshRateConfigs.h"
+
+namespace android::scheduler {
+
+using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
+using RefreshRate = RefreshRateConfigs::RefreshRate;
+
+// Returns the refresh rate map. This map won't be modified at runtime, so it's safe to access
+// from multiple threads. This can only be called if refreshRateSwitching() returns true.
+// TODO(b/122916473): Get this information from configs prepared by vendors, instead of
+// baking them in.
+const RefreshRate& RefreshRateConfigs::getRefreshRateForContent(float contentFramerate) const {
+    std::lock_guard lock(mLock);
+    // Find the appropriate refresh rate with minimal error
+    auto iter = min_element(mAvailableRefreshRates.cbegin(), mAvailableRefreshRates.cend(),
+                            [contentFramerate](const auto& lhs, const auto& rhs) -> bool {
+                                return std::abs(lhs->fps - contentFramerate) <
+                                        std::abs(rhs->fps - contentFramerate);
+                            });
+
+    // Some content aligns better on higher refresh rate. For example for 45fps we should choose
+    // 90Hz config. However we should still prefer a lower refresh rate if the content doesn't
+    // align well with both
+    const RefreshRate* bestSoFar = *iter;
+    constexpr float MARGIN = 0.05f;
+    float ratio = (*iter)->fps / contentFramerate;
+    if (std::abs(std::round(ratio) - ratio) > MARGIN) {
+        while (iter != mAvailableRefreshRates.cend()) {
+            ratio = (*iter)->fps / contentFramerate;
+
+            if (std::abs(std::round(ratio) - ratio) <= MARGIN) {
+                bestSoFar = *iter;
+                break;
+            }
+            ++iter;
+        }
+    }
+
+    return *bestSoFar;
+}
+
+const AllRefreshRatesMapType& RefreshRateConfigs::getAllRefreshRates() const {
+    return mRefreshRates;
+}
+
+const RefreshRate& RefreshRateConfigs::getMinRefreshRateByPolicy() const {
+    std::lock_guard lock(mLock);
+    if (!mRefreshRateSwitching) {
+        return *mCurrentRefreshRate;
+    } else {
+        return *mAvailableRefreshRates.front();
+    }
+}
+
+const RefreshRate& RefreshRateConfigs::getMaxRefreshRateByPolicy() const {
+    std::lock_guard lock(mLock);
+    if (!mRefreshRateSwitching) {
+        return *mCurrentRefreshRate;
+    } else {
+        return *mAvailableRefreshRates.back();
+    }
+}
+
+const RefreshRate& RefreshRateConfigs::getCurrentRefreshRate() const {
+    std::lock_guard lock(mLock);
+    return *mCurrentRefreshRate;
+}
+
+void RefreshRateConfigs::setCurrentConfigId(HwcConfigIndexType configId) {
+    std::lock_guard lock(mLock);
+    mCurrentRefreshRate = &mRefreshRates.at(configId);
+}
+
+RefreshRateConfigs::RefreshRateConfigs(bool refreshRateSwitching,
+                                       const std::vector<InputConfig>& configs,
+                                       HwcConfigIndexType currentHwcConfig)
+      : mRefreshRateSwitching(refreshRateSwitching) {
+    init(configs, currentHwcConfig);
+}
+
+RefreshRateConfigs::RefreshRateConfigs(
+        bool refreshRateSwitching,
+        const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
+        HwcConfigIndexType currentConfigId)
+      : mRefreshRateSwitching(refreshRateSwitching) {
+    std::vector<InputConfig> inputConfigs;
+    for (auto configId = HwcConfigIndexType(0); configId < HwcConfigIndexType(configs.size());
+         ++configId) {
+        auto configGroup = HwcConfigGroupType(configs[configId.value()]->getConfigGroup());
+        inputConfigs.push_back(
+                {configId, configGroup, configs[configId.value()]->getVsyncPeriod()});
+    }
+    init(inputConfigs, currentConfigId);
+}
+
+void RefreshRateConfigs::setPolicy(HwcConfigIndexType defaultConfigId, float minRefreshRate,
+                                   float maxRefreshRate) {
+    std::lock_guard lock(mLock);
+    mCurrentGroupId = mRefreshRates.at(defaultConfigId).configGroup;
+    mMinRefreshRateFps = minRefreshRate;
+    mMaxRefreshRateFps = maxRefreshRate;
+    constructAvailableRefreshRates();
+}
+
+void RefreshRateConfigs::getSortedRefreshRateList(
+        const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
+        std::vector<const RefreshRate*>* outRefreshRates) {
+    outRefreshRates->clear();
+    outRefreshRates->reserve(mRefreshRates.size());
+    for (const auto& [type, refreshRate] : mRefreshRates) {
+        if (shouldAddRefreshRate(refreshRate)) {
+            ALOGV("getSortedRefreshRateList: config %d added to list policy",
+                  refreshRate.configId.value());
+            outRefreshRates->push_back(&refreshRate);
+        }
+    }
+
+    std::sort(outRefreshRates->begin(), outRefreshRates->end(),
+              [](const auto refreshRate1, const auto refreshRate2) {
+                  return refreshRate1->vsyncPeriod > refreshRate2->vsyncPeriod;
+              });
+}
+
+void RefreshRateConfigs::constructAvailableRefreshRates() {
+    // Filter configs based on current policy and sort based on vsync period
+    ALOGV("constructRefreshRateMap: group %d min %.2f max %.2f", mCurrentGroupId.value(),
+          mMinRefreshRateFps, mMaxRefreshRateFps);
+    getSortedRefreshRateList(
+            [this](const RefreshRate& refreshRate) REQUIRES(mLock) {
+                return refreshRate.configGroup == mCurrentGroupId &&
+                        refreshRate.fps >= mMinRefreshRateFps &&
+                        refreshRate.fps <= mMaxRefreshRateFps;
+            },
+            &mAvailableRefreshRates);
+}
+
+// NO_THREAD_SAFETY_ANALYSIS since this is called from the constructor
+void RefreshRateConfigs::init(const std::vector<InputConfig>& configs,
+                              HwcConfigIndexType currentHwcConfig) NO_THREAD_SAFETY_ANALYSIS {
+    LOG_ALWAYS_FATAL_IF(configs.empty());
+    LOG_ALWAYS_FATAL_IF(currentHwcConfig.value() >= configs.size());
+
+    auto buildRefreshRate = [&](InputConfig config) -> RefreshRate {
+        const float fps = 1e9f / config.vsyncPeriod;
+        return RefreshRate(config.configId, config.vsyncPeriod, config.configGroup,
+                           base::StringPrintf("%2.ffps", fps), fps);
+    };
+
+    for (const auto& config : configs) {
+        mRefreshRates.emplace(config.configId, buildRefreshRate(config));
+        if (config.configId == currentHwcConfig) {
+            mCurrentRefreshRate = &mRefreshRates.at(config.configId);
+            mCurrentGroupId = config.configGroup;
+        }
+    }
+
+    std::vector<const RefreshRate*> sortedConfigs;
+    getSortedRefreshRateList([](const RefreshRate&) { return true; }, &sortedConfigs);
+    mMinSupportedRefreshRate = sortedConfigs.front();
+    mMaxSupportedRefreshRate = sortedConfigs.back();
+    constructAvailableRefreshRates();
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 2fd100f..fb14dc7 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -23,7 +23,9 @@
 #include <type_traits>
 
 #include "DisplayHardware/HWComposer.h"
+#include "HwcStrongTypes.h"
 #include "Scheduler/SchedulerUtils.h"
+#include "Scheduler/StrongTyping.h"
 
 namespace android::scheduler {
 
@@ -41,172 +43,123 @@
  */
 class RefreshRateConfigs {
 public:
-    // Enum to indicate which vsync rate to run at. Default is the old 60Hz, and performance
-    // is the new 90Hz. Eventually we want to have a way for vendors to map these in the configs.
-    enum class RefreshRateType { DEFAULT, PERFORMANCE };
-
     struct RefreshRate {
+        RefreshRate(HwcConfigIndexType configId, nsecs_t vsyncPeriod,
+                    HwcConfigGroupType configGroup, std::string name, float fps)
+              : configId(configId),
+                vsyncPeriod(vsyncPeriod),
+                configGroup(configGroup),
+                name(std::move(name)),
+                fps(fps) {}
         // This config ID corresponds to the position of the config in the vector that is stored
         // on the device.
-        int configId;
-        // Human readable name of the refresh rate.
-        std::string name;
-        // Refresh rate in frames per second, rounded to the nearest integer.
-        uint32_t fps = 0;
+        const HwcConfigIndexType configId;
         // Vsync period in nanoseconds.
-        nsecs_t vsyncPeriod;
-        // Hwc config Id (returned from HWC2::Display::Config::getId())
-        hwc2_config_t hwcId;
+        const nsecs_t vsyncPeriod;
+        // This configGroup for the config.
+        const HwcConfigGroupType configGroup;
+        // Human readable name of the refresh rate.
+        const std::string name;
+        // Refresh rate in frames per second
+        const float fps = 0;
+
+        bool operator!=(const RefreshRate& other) const {
+            return configId != other.configId || vsyncPeriod != other.vsyncPeriod ||
+                    configGroup != other.configGroup;
+        }
+
+        bool operator==(const RefreshRate& other) const { return !(*this != other); }
     };
 
+    using AllRefreshRatesMapType = std::unordered_map<HwcConfigIndexType, const RefreshRate>;
+
+    // Sets the current policy to choose refresh rates.
+    void setPolicy(HwcConfigIndexType defaultConfigId, float minRefreshRate, float maxRefreshRate)
+            EXCLUDES(mLock);
+
     // Returns true if this device is doing refresh rate switching. This won't change at runtime.
-    bool refreshRateSwitchingSupported() const { return mRefreshRateSwitchingSupported; }
+    bool refreshRateSwitchingSupported() const { return mRefreshRateSwitching; }
 
-    // Returns the refresh rate map. This map won't be modified at runtime, so it's safe to access
-    // from multiple threads. This can only be called if refreshRateSwitching() returns true.
-    // TODO(b/122916473): Get this information from configs prepared by vendors, instead of
-    // baking them in.
-    const std::map<RefreshRateType, RefreshRate>& getRefreshRateMap() const {
-        LOG_ALWAYS_FATAL_IF(!mRefreshRateSwitchingSupported);
-        return mRefreshRateMap;
-    }
+    // Returns all available refresh rates according to the current policy.
+    const RefreshRate& getRefreshRateForContent(float contentFramerate) const EXCLUDES(mLock);
 
-    const RefreshRate& getRefreshRateFromType(RefreshRateType type) const {
-        if (!mRefreshRateSwitchingSupported) {
-            return getCurrentRefreshRate().second;
-        } else {
-            auto refreshRate = mRefreshRateMap.find(type);
-            LOG_ALWAYS_FATAL_IF(refreshRate == mRefreshRateMap.end());
-            return refreshRate->second;
-        }
-    }
+    // Returns all the refresh rates supported by the device. This won't change at runtime.
+    const AllRefreshRatesMapType& getAllRefreshRates() const EXCLUDES(mLock);
 
-    std::pair<RefreshRateType, const RefreshRate&> getCurrentRefreshRate() const {
-        int currentConfig = mCurrentConfig;
-        if (mRefreshRateSwitchingSupported) {
-            for (const auto& [type, refresh] : mRefreshRateMap) {
-                if (refresh.configId == currentConfig) {
-                    return {type, refresh};
-                }
-            }
-            LOG_ALWAYS_FATAL();
-        }
-        return {RefreshRateType::DEFAULT, mRefreshRates[currentConfig]};
-    }
+    // Returns the lowest refresh rate supported by the device. This won't change at runtime.
+    const RefreshRate& getMinRefreshRate() const { return *mMinSupportedRefreshRate; }
 
-    const RefreshRate& getRefreshRateFromConfigId(int configId) const {
-        LOG_ALWAYS_FATAL_IF(configId >= mRefreshRates.size());
-        return mRefreshRates[configId];
-    }
+    // Returns the lowest refresh rate according to the current policy. May change in runtime.
+    const RefreshRate& getMinRefreshRateByPolicy() const EXCLUDES(mLock);
 
-    RefreshRateType getRefreshRateTypeFromHwcConfigId(hwc2_config_t hwcId) const {
-        if (!mRefreshRateSwitchingSupported) return RefreshRateType::DEFAULT;
+    // Returns the highest refresh rate supported by the device. This won't change at runtime.
+    const RefreshRate& getMaxRefreshRate() const { return *mMaxSupportedRefreshRate; }
 
-        for (const auto& [type, refreshRate] : mRefreshRateMap) {
-            if (refreshRate.hwcId == hwcId) {
-                return type;
-            }
-        }
+    // Returns the highest refresh rate according to the current policy. May change in runtime.
+    const RefreshRate& getMaxRefreshRateByPolicy() const EXCLUDES(mLock);
 
-        return RefreshRateType::DEFAULT;
-    }
+    // Returns the current refresh rate
+    const RefreshRate& getCurrentRefreshRate() const EXCLUDES(mLock);
 
-    void setCurrentConfig(int config) {
-        LOG_ALWAYS_FATAL_IF(config >= mRefreshRates.size());
-        mCurrentConfig = config;
-    }
+    // Returns the refresh rate that corresponds to a HwcConfigIndexType. This won't change at
+    // runtime.
+    const RefreshRate& getRefreshRateFromConfigId(HwcConfigIndexType configId) const {
+        return mRefreshRates.at(configId);
+    };
+
+    // Stores the current configId the device operates at
+    void setCurrentConfigId(HwcConfigIndexType configId) EXCLUDES(mLock);
 
     struct InputConfig {
-        hwc2_config_t hwcId = 0;
+        HwcConfigIndexType configId = HwcConfigIndexType(0);
+        HwcConfigGroupType configGroup = HwcConfigGroupType(0);
         nsecs_t vsyncPeriod = 0;
     };
 
     RefreshRateConfigs(bool refreshRateSwitching, const std::vector<InputConfig>& configs,
-                       int currentConfig) {
-        init(refreshRateSwitching, configs, currentConfig);
-    }
-
+                       HwcConfigIndexType currentHwcConfig);
     RefreshRateConfigs(bool refreshRateSwitching,
                        const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
-                       int currentConfig) {
-        std::vector<InputConfig> inputConfigs;
-        for (const auto& config : configs) {
-            inputConfigs.push_back({config->getId(), config->getVsyncPeriod()});
-        }
-        init(refreshRateSwitching, inputConfigs, currentConfig);
-    }
+                       HwcConfigIndexType currentConfigId);
 
 private:
-    void init(bool refreshRateSwitching, const std::vector<InputConfig>& configs,
-              int currentConfig) {
-        mRefreshRateSwitchingSupported = refreshRateSwitching;
-        LOG_ALWAYS_FATAL_IF(configs.empty());
-        LOG_ALWAYS_FATAL_IF(currentConfig >= configs.size());
-        mCurrentConfig = currentConfig;
+    void init(const std::vector<InputConfig>& configs, HwcConfigIndexType currentHwcConfig);
 
-        auto buildRefreshRate = [&](int configId) -> RefreshRate {
-            const nsecs_t vsyncPeriod = configs[configId].vsyncPeriod;
-            const float fps = 1e9 / vsyncPeriod;
-            return {configId, base::StringPrintf("%2.ffps", fps), static_cast<uint32_t>(fps),
-                    vsyncPeriod, configs[configId].hwcId};
-        };
+    void constructAvailableRefreshRates() REQUIRES(mLock);
 
-        for (int i = 0; i < configs.size(); ++i) {
-            mRefreshRates.push_back(buildRefreshRate(i));
-        }
+    void getSortedRefreshRateList(
+            const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
+            std::vector<const RefreshRate*>* outRefreshRates);
 
-        if (!mRefreshRateSwitchingSupported) return;
-
-        auto findDefaultAndPerfConfigs = [&]() -> std::optional<std::pair<int, int>> {
-            if (configs.size() < 2) {
-                return {};
-            }
-
-            std::vector<const RefreshRate*> sortedRefreshRates;
-            for (const auto& refreshRate : mRefreshRates) {
-                sortedRefreshRates.push_back(&refreshRate);
-            }
-            std::sort(sortedRefreshRates.begin(), sortedRefreshRates.end(),
-                      [](const RefreshRate* refreshRate1, const RefreshRate* refreshRate2) {
-                          return refreshRate1->vsyncPeriod > refreshRate2->vsyncPeriod;
-                      });
-
-            // When the configs are ordered by the resync rate, we assume that
-            // the first one is DEFAULT and the second one is PERFORMANCE,
-            // i.e. the higher rate.
-            if (sortedRefreshRates[0]->vsyncPeriod == 0 ||
-                sortedRefreshRates[1]->vsyncPeriod == 0) {
-                return {};
-            }
-
-            return std::pair<int, int>(sortedRefreshRates[0]->configId,
-                                       sortedRefreshRates[1]->configId);
-        };
-
-        auto defaultAndPerfConfigs = findDefaultAndPerfConfigs();
-        if (!defaultAndPerfConfigs) {
-            mRefreshRateSwitchingSupported = false;
-            return;
-        }
-
-        mRefreshRateMap[RefreshRateType::DEFAULT] = mRefreshRates[defaultAndPerfConfigs->first];
-        mRefreshRateMap[RefreshRateType::PERFORMANCE] =
-                mRefreshRates[defaultAndPerfConfigs->second];
-    }
-
-    // Whether this device is doing refresh rate switching or not. This must not change after this
-    // object is initialized.
-    bool mRefreshRateSwitchingSupported;
     // The list of refresh rates, indexed by display config ID. This must not change after this
     // object is initialized.
-    std::vector<RefreshRate> mRefreshRates;
-    // The mapping of refresh rate type to RefreshRate. This must not change after this object is
-    // initialized.
-    std::map<RefreshRateType, RefreshRate> mRefreshRateMap;
-    // The ID of the current config. This will change at runtime. This is set by SurfaceFlinger on
-    // the main thread, and read by the Scheduler (and other objects) on other threads, so it's
-    // atomic.
-    std::atomic<int> mCurrentConfig;
+    AllRefreshRatesMapType mRefreshRates;
+
+    // The list of refresh rates which are available in the current policy, ordered by vsyncPeriod
+    // (the first element is the lowest refresh rate)
+    std::vector<const RefreshRate*> mAvailableRefreshRates GUARDED_BY(mLock);
+
+    // The current config. This will change at runtime. This is set by SurfaceFlinger on
+    // the main thread, and read by the Scheduler (and other objects) on other threads.
+    const RefreshRate* mCurrentRefreshRate GUARDED_BY(mLock);
+
+    // The current config group. This will change at runtime. This is set by SurfaceFlinger on
+    // the main thread, and read by the Scheduler (and other objects) on other threads.
+    HwcConfigGroupType mCurrentGroupId GUARDED_BY(mLock);
+
+    // The min and max FPS allowed by the policy. This will change at runtime and set by
+    // SurfaceFlinger on the main thread.
+    float mMinRefreshRateFps GUARDED_BY(mLock) = 0;
+    float mMaxRefreshRateFps GUARDED_BY(mLock) = std::numeric_limits<float>::max();
+
+    // The min and max refresh rates supported by the device.
+    // This will not change at runtime.
+    const RefreshRate* mMinSupportedRefreshRate;
+    const RefreshRate* mMaxSupportedRefreshRate;
+
+    const bool mRefreshRateSwitching;
+
+    mutable std::mutex mLock;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index 8afc93e..a384dbe 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -25,8 +25,7 @@
 #include "android-base/stringprintf.h"
 #include "utils/Timers.h"
 
-namespace android {
-namespace scheduler {
+namespace android::scheduler {
 
 /**
  * Class to encapsulate statistics about refresh rates that the display is using. When the power
@@ -42,10 +41,10 @@
 
 public:
     RefreshRateStats(const RefreshRateConfigs& refreshRateConfigs, TimeStats& timeStats,
-                     int currentConfigMode, int currentPowerMode)
+                     HwcConfigIndexType currentConfigId, int currentPowerMode)
           : mRefreshRateConfigs(refreshRateConfigs),
             mTimeStats(timeStats),
-            mCurrentConfigMode(currentConfigMode),
+            mCurrentConfigMode(currentConfigId),
             mCurrentPowerMode(currentPowerMode) {}
 
     // Sets power mode.
@@ -59,12 +58,12 @@
 
     // Sets config mode. If the mode has changed, it records how much time was spent in the previous
     // mode.
-    void setConfigMode(int mode) {
-        if (mCurrentConfigMode == mode) {
+    void setConfigMode(HwcConfigIndexType configId) {
+        if (mCurrentConfigMode == configId) {
             return;
         }
         flushTime();
-        mCurrentConfigMode = mode;
+        mCurrentConfigMode = configId;
     }
 
     // Returns a map between human readable refresh rate and number of seconds the device spent in
@@ -78,11 +77,11 @@
         std::unordered_map<std::string, int64_t> totalTime;
         // Multiple configs may map to the same name, e.g. "60fps". Add the
         // times for such configs together.
-        for (const auto& [config, time] : mConfigModesTotalTime) {
-            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(config).name] = 0;
+        for (const auto& [configId, time] : mConfigModesTotalTime) {
+            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(configId).name] = 0;
         }
-        for (const auto& [config, time] : mConfigModesTotalTime) {
-            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(config).name] += time;
+        for (const auto& [configId, time] : mConfigModesTotalTime) {
+            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(configId).name] += time;
         }
         totalTime["ScreenOff"] = mScreenOffTime;
         return totalTime;
@@ -139,14 +138,14 @@
     // Aggregate refresh rate statistics for telemetry.
     TimeStats& mTimeStats;
 
-    int mCurrentConfigMode;
+    HwcConfigIndexType mCurrentConfigMode;
     int32_t mCurrentPowerMode;
 
-    std::unordered_map<int /* config */, int64_t /* duration in ms */> mConfigModesTotalTime;
+    std::unordered_map<HwcConfigIndexType /* configId */, int64_t /* duration in ms */>
+            mConfigModesTotalTime;
     int64_t mScreenOffTime = 0;
 
     nsecs_t mPreviousRecordedTime = systemTime();
 };
 
-} // namespace scheduler
-} // namespace android
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 55fd603..71a6a2f 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -59,11 +59,13 @@
 namespace android {
 
 Scheduler::Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function,
-                     const scheduler::RefreshRateConfigs& refreshRateConfig)
+                     const scheduler::RefreshRateConfigs& refreshRateConfig,
+                     ISchedulerCallback& schedulerCallback)
       : mPrimaryDispSync(new impl::DispSync("SchedulerDispSync",
                                             sysprop::running_without_sync_framework(true))),
         mEventControlThread(new impl::EventControlThread(std::move(function))),
         mSupportKernelTimer(sysprop::support_kernel_idle_timer(false)),
+        mSchedulerCallback(schedulerCallback),
         mRefreshRateConfigs(refreshRateConfig) {
     using namespace sysprop;
 
@@ -104,10 +106,12 @@
 
 Scheduler::Scheduler(std::unique_ptr<DispSync> primaryDispSync,
                      std::unique_ptr<EventControlThread> eventControlThread,
-                     const scheduler::RefreshRateConfigs& configs)
+                     const scheduler::RefreshRateConfigs& configs,
+                     ISchedulerCallback& schedulerCallback)
       : mPrimaryDispSync(std::move(primaryDispSync)),
         mEventControlThread(std::move(eventControlThread)),
         mSupportKernelTimer(false),
+        mSchedulerCallback(schedulerCallback),
         mRefreshRateConfigs(configs) {}
 
 Scheduler::~Scheduler() {
@@ -182,7 +186,7 @@
 }
 
 void Scheduler::onConfigChanged(ConnectionHandle handle, PhysicalDisplayId displayId,
-                                int32_t configId) {
+                                HwcConfigIndexType configId) {
     RETURN_IF_INVALID_HANDLE(handle);
     mConnections[handle].thread->onConfigChanged(displayId, configId);
 }
@@ -280,8 +284,7 @@
     const nsecs_t last = mLastResyncTime.exchange(now);
 
     if (now - last > kIgnoreDelay) {
-        resyncToHardwareVsync(false,
-                              mRefreshRateConfigs.getCurrentRefreshRate().second.vsyncPeriod);
+        resyncToHardwareVsync(false, mRefreshRateConfigs.getCurrentRefreshRate().vsyncPeriod);
     }
 }
 
@@ -332,53 +335,44 @@
 void Scheduler::registerLayer(Layer* layer) {
     if (!mLayerHistory) return;
 
-    const auto type = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
-            ? RefreshRateType::DEFAULT
-            : RefreshRateType::PERFORMANCE;
-
-    const auto lowFps = mRefreshRateConfigs.getRefreshRateFromType(RefreshRateType::DEFAULT).fps;
-    const auto highFps = mRefreshRateConfigs.getRefreshRateFromType(type).fps;
+    const auto lowFps = mRefreshRateConfigs.getMinRefreshRate().fps;
+    const auto highFps = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
+            ? lowFps
+            : mRefreshRateConfigs.getMaxRefreshRate().fps;
 
     mLayerHistory->registerLayer(layer, lowFps, highFps);
 }
 
-void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime, bool isHDR) {
+void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime) {
     if (mLayerHistory) {
-        mLayerHistory->record(layer, presentTime, isHDR, systemTime());
+        mLayerHistory->record(layer, presentTime, systemTime());
     }
 }
 
 void Scheduler::chooseRefreshRateForContent() {
     if (!mLayerHistory) return;
 
-    auto [refreshRate, isHDR] = mLayerHistory->summarize(systemTime());
+    auto [refreshRate] = mLayerHistory->summarize(systemTime());
     const uint32_t refreshRateRound = std::round(refreshRate);
-    RefreshRateType newRefreshRateType;
+    HwcConfigIndexType newConfigId;
     {
         std::lock_guard<std::mutex> lock(mFeatureStateLock);
-        if (mFeatures.contentRefreshRate == refreshRateRound && mFeatures.isHDRContent == isHDR) {
+        if (mFeatures.contentRefreshRate == refreshRateRound) {
             return;
         }
         mFeatures.contentRefreshRate = refreshRateRound;
         ATRACE_INT("ContentFPS", refreshRateRound);
 
-        mFeatures.isHDRContent = isHDR;
-        ATRACE_INT("ContentHDR", isHDR);
-
         mFeatures.contentDetection =
                 refreshRateRound > 0 ? ContentDetectionState::On : ContentDetectionState::Off;
-        newRefreshRateType = calculateRefreshRateType();
-        if (mFeatures.refreshRateType == newRefreshRateType) {
+        newConfigId = calculateRefreshRateType();
+        if (mFeatures.configId == newConfigId) {
             return;
         }
-        mFeatures.refreshRateType = newRefreshRateType;
-    }
-    changeRefreshRate(newRefreshRateType, ConfigEvent::Changed);
-}
-
-void Scheduler::setChangeRefreshRateCallback(ChangeRefreshRateCallback&& callback) {
-    std::lock_guard<std::mutex> lock(mCallbackLock);
-    mChangeRefreshRateCallback = std::move(callback);
+        mFeatures.configId = newConfigId;
+    };
+    auto newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
+    mSchedulerCallback.changeRefreshRate(newRefreshRate, ConfigEvent::Changed);
 }
 
 void Scheduler::resetIdleTimer() {
@@ -423,13 +417,16 @@
 void Scheduler::kernelIdleTimerCallback(TimerState state) {
     ATRACE_INT("ExpiredKernelIdleTimer", static_cast<int>(state));
 
+    // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
+    // magic number
     const auto refreshRate = mRefreshRateConfigs.getCurrentRefreshRate();
-    if (state == TimerState::Reset && refreshRate.first == RefreshRateType::PERFORMANCE) {
+    constexpr float FPS_THRESHOLD_FOR_KERNEL_TIMER = 65.0f;
+    if (state == TimerState::Reset && refreshRate.fps > FPS_THRESHOLD_FOR_KERNEL_TIMER) {
         // If we're not in performance mode then the kernel timer shouldn't do
         // anything, as the refresh rate during DPU power collapse will be the
         // same.
-        resyncToHardwareVsync(true /* makeAvailable */, refreshRate.second.vsyncPeriod);
-    } else if (state == TimerState::Expired && refreshRate.first != RefreshRateType::PERFORMANCE) {
+        resyncToHardwareVsync(true /* makeAvailable */, refreshRate.vsyncPeriod);
+    } else if (state == TimerState::Expired && refreshRate.fps <= FPS_THRESHOLD_FOR_KERNEL_TIMER) {
         // Disable HW VSYNC if the timer expired, as we don't need it enabled if
         // we're not pushing frames, and if we're in PERFORMANCE mode then we'll
         // need to update the DispSync model anyway.
@@ -471,96 +468,93 @@
 template <class T>
 void Scheduler::handleTimerStateChanged(T* currentState, T newState, bool eventOnContentDetection) {
     ConfigEvent event = ConfigEvent::None;
-    RefreshRateType newRefreshRateType;
+    HwcConfigIndexType newConfigId;
     {
         std::lock_guard<std::mutex> lock(mFeatureStateLock);
         if (*currentState == newState) {
             return;
         }
         *currentState = newState;
-        newRefreshRateType = calculateRefreshRateType();
-        if (mFeatures.refreshRateType == newRefreshRateType) {
+        newConfigId = calculateRefreshRateType();
+        if (mFeatures.configId == newConfigId) {
             return;
         }
-        mFeatures.refreshRateType = newRefreshRateType;
+        mFeatures.configId = newConfigId;
         if (eventOnContentDetection && mFeatures.contentDetection == ContentDetectionState::On) {
             event = ConfigEvent::Changed;
         }
     }
-    changeRefreshRate(newRefreshRateType, event);
+    const RefreshRate& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
+    mSchedulerCallback.changeRefreshRate(newRefreshRate, event);
 }
 
-Scheduler::RefreshRateType Scheduler::calculateRefreshRateType() {
+HwcConfigIndexType Scheduler::calculateRefreshRateType() {
     if (!mRefreshRateConfigs.refreshRateSwitchingSupported()) {
-        return RefreshRateType::DEFAULT;
-    }
-
-    // HDR content is not supported on PERFORMANCE mode
-    if (mForceHDRContentToDefaultRefreshRate && mFeatures.isHDRContent) {
-        return RefreshRateType::DEFAULT;
+        return mRefreshRateConfigs.getCurrentRefreshRate().configId;
     }
 
     // If Display Power is not in normal operation we want to be in performance mode.
     // When coming back to normal mode, a grace period is given with DisplayPowerTimer
     if (!mFeatures.isDisplayPowerStateNormal || mFeatures.displayPowerTimer == TimerState::Reset) {
-        return RefreshRateType::PERFORMANCE;
+        return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
     }
 
     // As long as touch is active we want to be in performance mode
     if (mFeatures.touch == TouchState::Active) {
-        return RefreshRateType::PERFORMANCE;
+        return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
     }
 
     // If timer has expired as it means there is no new content on the screen
     if (mFeatures.idleTimer == TimerState::Expired) {
-        return RefreshRateType::DEFAULT;
+        return mRefreshRateConfigs.getMinRefreshRateByPolicy().configId;
     }
 
     // If content detection is off we choose performance as we don't know the content fps
     if (mFeatures.contentDetection == ContentDetectionState::Off) {
-        return RefreshRateType::PERFORMANCE;
+        return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
     }
 
     // Content detection is on, find the appropriate refresh rate with minimal error
-    // TODO(b/139751853): Scan allowed refresh rates only (SurfaceFlinger::mAllowedDisplayConfigs)
-    const float rate = static_cast<float>(mFeatures.contentRefreshRate);
-    auto iter = min_element(mRefreshRateConfigs.getRefreshRateMap().cbegin(),
-                            mRefreshRateConfigs.getRefreshRateMap().cend(),
-                            [rate](const auto& lhs, const auto& rhs) -> bool {
-                                return std::abs(lhs.second.fps - rate) <
-                                        std::abs(rhs.second.fps - rate);
-                            });
-    RefreshRateType currRefreshRateType = iter->first;
+    return mRefreshRateConfigs
+            .getRefreshRateForContent(static_cast<float>(mFeatures.contentRefreshRate))
+            .configId;
+}
 
-    // Some content aligns better on higher refresh rate. For example for 45fps we should choose
-    // 90Hz config. However we should still prefer a lower refresh rate if the content doesn't
-    // align well with both
-    constexpr float MARGIN = 0.05f;
-    float ratio = mRefreshRateConfigs.getRefreshRateFromType(currRefreshRateType).fps / rate;
-    if (std::abs(std::round(ratio) - ratio) > MARGIN) {
-        while (iter != mRefreshRateConfigs.getRefreshRateMap().cend()) {
-            ratio = iter->second.fps / rate;
+std::optional<HwcConfigIndexType> Scheduler::getPreferredConfigId() {
+    std::lock_guard<std::mutex> lock(mFeatureStateLock);
+    return mFeatures.configId;
+}
 
-            if (std::abs(std::round(ratio) - ratio) <= MARGIN) {
-                currRefreshRateType = iter->first;
-                break;
+void Scheduler::onNewVsyncPeriodChangeTimeline(const HWC2::VsyncPeriodChangeTimeline& timeline) {
+    if (timeline.refreshRequired) {
+        mSchedulerCallback.repaintEverythingForHWC();
+    }
+
+    std::lock_guard<std::mutex> lock(mVsyncTimelineLock);
+    mLastVsyncPeriodChangeTimeline = std::make_optional(timeline);
+
+    const auto maxAppliedTime = systemTime() + MAX_VSYNC_APPLIED_TIME.count();
+    if (timeline.newVsyncAppliedTimeNanos > maxAppliedTime) {
+        mLastVsyncPeriodChangeTimeline->newVsyncAppliedTimeNanos = maxAppliedTime;
+    }
+}
+
+void Scheduler::onDisplayRefreshed(nsecs_t timestamp) {
+    bool callRepaint = false;
+    {
+        std::lock_guard<std::mutex> lock(mVsyncTimelineLock);
+        if (mLastVsyncPeriodChangeTimeline && mLastVsyncPeriodChangeTimeline->refreshRequired) {
+            if (mLastVsyncPeriodChangeTimeline->refreshTimeNanos < timestamp) {
+                mLastVsyncPeriodChangeTimeline->refreshRequired = false;
+            } else {
+                // We need to send another refresh as refreshTimeNanos is still in the future
+                callRepaint = true;
             }
-            ++iter;
         }
     }
 
-    return currRefreshRateType;
-}
-
-Scheduler::RefreshRateType Scheduler::getPreferredRefreshRateType() {
-    std::lock_guard<std::mutex> lock(mFeatureStateLock);
-    return mFeatures.refreshRateType;
-}
-
-void Scheduler::changeRefreshRate(RefreshRateType refreshRateType, ConfigEvent configEvent) {
-    std::lock_guard<std::mutex> lock(mCallbackLock);
-    if (mChangeRefreshRateCallback) {
-        mChangeRefreshRateCallback(refreshRateType, configEvent);
+    if (callRepaint) {
+        mSchedulerCallback.repaintEverythingForHWC();
     }
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 346896c..15277ce 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -34,23 +34,31 @@
 
 namespace android {
 
+using namespace std::chrono_literals;
+
 class DispSync;
 class FenceTime;
 class InjectVSyncSource;
 struct DisplayStateInfo;
 
+class ISchedulerCallback {
+public:
+    virtual ~ISchedulerCallback() = default;
+    virtual void changeRefreshRate(const scheduler::RefreshRateConfigs::RefreshRate&,
+                                   scheduler::RefreshRateConfigEvent) = 0;
+    virtual void repaintEverythingForHWC() = 0;
+};
+
 class Scheduler {
 public:
-    using RefreshRateType = scheduler::RefreshRateConfigs::RefreshRateType;
+    using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
     using ConfigEvent = scheduler::RefreshRateConfigEvent;
 
-    using ChangeRefreshRateCallback = std::function<void(RefreshRateType, ConfigEvent)>;
-
     // Indicates whether to start the transaction early, or at vsync time.
     enum class TransactionStart { EARLY, NORMAL };
 
     Scheduler(impl::EventControlThread::SetVSyncEnabledFunction,
-              const scheduler::RefreshRateConfigs&);
+              const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback);
 
     virtual ~Scheduler();
 
@@ -67,7 +75,7 @@
     sp<EventThreadConnection> getEventConnection(ConnectionHandle);
 
     void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected);
-    void onConfigChanged(ConnectionHandle, PhysicalDisplayId, int32_t configId);
+    void onConfigChanged(ConnectionHandle, PhysicalDisplayId, HwcConfigIndexType configId);
 
     void onScreenAcquired(ConnectionHandle);
     void onScreenReleased(ConnectionHandle);
@@ -103,14 +111,11 @@
 
     // Layers are registered on creation, and unregistered when the weak reference expires.
     void registerLayer(Layer*);
-    void recordLayerHistory(Layer*, nsecs_t presentTime, bool isHDR);
+    void recordLayerHistory(Layer*, nsecs_t presentTime);
 
     // Detects content using layer history, and selects a matching refresh rate.
     void chooseRefreshRateForContent();
 
-    // Called by Scheduler to change refresh rate.
-    void setChangeRefreshRateCallback(ChangeRefreshRateCallback&&);
-
     bool isIdleTimerEnabled() const { return mIdleTimer.has_value(); }
     void resetIdleTimer();
 
@@ -122,8 +127,14 @@
     void dump(std::string&) const;
     void dump(ConnectionHandle, std::string&) const;
 
-    // Get the appropriate refresh type for current conditions.
-    RefreshRateType getPreferredRefreshRateType();
+    // Get the appropriate refresh for current conditions.
+    std::optional<HwcConfigIndexType> getPreferredConfigId();
+
+    // Notifies the scheduler about a refresh rate timeline change.
+    void onNewVsyncPeriodChangeTimeline(const HWC2::VsyncPeriodChangeTimeline& timeline);
+
+    // Notifies the scheduler when the display was refreshed
+    void onDisplayRefreshed(nsecs_t timestamp);
 
 private:
     friend class TestableScheduler;
@@ -136,7 +147,7 @@
 
     // Used by tests to inject mocks.
     Scheduler(std::unique_ptr<DispSync>, std::unique_ptr<EventControlThread>,
-              const scheduler::RefreshRateConfigs&);
+              const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback);
 
     std::unique_ptr<VSyncSource> makePrimaryDispSyncSource(const char* name, nsecs_t phaseOffsetNs,
                                                            nsecs_t offsetThresholdForNextVsync);
@@ -158,9 +169,7 @@
 
     void setVsyncPeriod(nsecs_t period);
 
-    RefreshRateType calculateRefreshRateType() REQUIRES(mFeatureStateLock);
-    // Acquires a lock and calls the ChangeRefreshRateCallback with given parameters.
-    void changeRefreshRate(RefreshRateType, ConfigEvent);
+    HwcConfigIndexType calculateRefreshRateType() REQUIRES(mFeatureStateLock);
 
     // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
     struct Connection {
@@ -197,8 +206,7 @@
     // Timer used to monitor display power mode.
     std::optional<scheduler::OneShotTimer> mDisplayPowerTimer;
 
-    std::mutex mCallbackLock;
-    ChangeRefreshRateCallback mChangeRefreshRateCallback GUARDED_BY(mCallbackLock);
+    ISchedulerCallback& mSchedulerCallback;
 
     // In order to make sure that the features don't override themselves, we need a state machine
     // to keep track which feature requested the config change.
@@ -210,17 +218,18 @@
         TouchState touch = TouchState::Inactive;
         TimerState displayPowerTimer = TimerState::Expired;
 
-        RefreshRateType refreshRateType = RefreshRateType::DEFAULT;
+        std::optional<HwcConfigIndexType> configId;
         uint32_t contentRefreshRate = 0;
 
-        bool isHDRContent = false;
         bool isDisplayPowerStateNormal = true;
     } mFeatures GUARDED_BY(mFeatureStateLock);
 
     const scheduler::RefreshRateConfigs& mRefreshRateConfigs;
 
-    // Global config to force HDR content to work on DEFAULT refreshRate
-    static constexpr bool mForceHDRContentToDefaultRefreshRate = false;
+    std::mutex mVsyncTimelineLock;
+    std::optional<HWC2::VsyncPeriodChangeTimeline> mLastVsyncPeriodChangeTimeline
+            GUARDED_BY(mVsyncTimelineLock);
+    static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/StrongTyping.h b/services/surfaceflinger/Scheduler/StrongTyping.h
index 02db022..e8ca0ba 100644
--- a/services/surfaceflinger/Scheduler/StrongTyping.h
+++ b/services/surfaceflinger/Scheduler/StrongTyping.h
@@ -51,13 +51,22 @@
     inline bool operator>(T const& other) const { return !(*this < other || *this == other); }
 };
 
+template <typename T>
+struct Hash : Ability<T, Hash> {
+    [[nodiscard]] std::size_t hash() const {
+        return std::hash<typename std::remove_const<
+                typename std::remove_reference<decltype(this->base().value())>::type>::type>{}(
+                this->base().value());
+    }
+};
+
 template <typename T, typename W, template <typename> class... Ability>
 struct StrongTyping : Ability<StrongTyping<T, W, Ability...>>... {
     StrongTyping() : mValue(0) {}
     explicit StrongTyping(T const& value) : mValue(value) {}
     StrongTyping(StrongTyping const&) = default;
     StrongTyping& operator=(StrongTyping const&) = default;
-    inline operator T() const { return mValue; }
+    explicit inline operator T() const { return mValue; }
     T const& value() const { return mValue; }
     T& value() { return mValue; }
 
@@ -65,3 +74,12 @@
     T mValue;
 };
 } // namespace android
+
+namespace std {
+template <typename T, typename W, template <typename> class... Ability>
+struct hash<android::StrongTyping<T, W, Ability...>> {
+    std::size_t operator()(android::StrongTyping<T, W, Ability...> const& k) const {
+        return k.hash();
+    }
+};
+} // namespace std
diff --git a/services/surfaceflinger/Scheduler/TimeKeeper.h b/services/surfaceflinger/Scheduler/TimeKeeper.h
index 699cd50..38f0708 100644
--- a/services/surfaceflinger/Scheduler/TimeKeeper.h
+++ b/services/surfaceflinger/Scheduler/TimeKeeper.h
@@ -21,10 +21,24 @@
 
 namespace android::scheduler {
 
+class Clock {
+public:
+    virtual ~Clock();
+    /*
+     * Returns the SYSTEM_TIME_MONOTONIC, used by testing infra to stub time.
+     */
+    virtual nsecs_t now() const = 0;
+
+protected:
+    Clock() = default;
+    Clock(Clock const&) = delete;
+    Clock& operator=(Clock const&) = delete;
+};
+
 /*
  * TimeKeeper is the interface for a single-shot timer primitive.
  */
-class TimeKeeper {
+class TimeKeeper : public Clock {
 public:
     virtual ~TimeKeeper();
 
@@ -39,11 +53,6 @@
      */
     virtual void alarmCancel() = 0;
 
-    /*
-     * Returns the SYSTEM_TIME_MONOTONIC, used by testing infra to stub time.
-     */
-    virtual nsecs_t now() const = 0;
-
 protected:
     TimeKeeper(TimeKeeper const&) = delete;
     TimeKeeper& operator=(TimeKeeper const&) = delete;
diff --git a/services/surfaceflinger/Scheduler/Timer.cpp b/services/surfaceflinger/Scheduler/Timer.cpp
index 2394ed2..fb4f315 100644
--- a/services/surfaceflinger/Scheduler/Timer.cpp
+++ b/services/surfaceflinger/Scheduler/Timer.cpp
@@ -85,7 +85,7 @@
     };
 
     if (timerfd_settime(mTimerFd, 0, &new_timer, &old_timer)) {
-        ALOGW("Failed to set timerfd");
+        ALOGW("Failed to set timerfd %s (%i)", strerror(errno), errno);
     }
 }
 
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h
index 4a4bef8..e001080 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatch.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h
@@ -34,7 +34,7 @@
  */
 class VSyncDispatch {
 public:
-    using CallbackToken = StrongTyping<size_t, class CallbackTokenTag, Compare>;
+    using CallbackToken = StrongTyping<size_t, class CallbackTokenTag, Compare, Hash>;
 
     virtual ~VSyncDispatch();
 
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 7922484..a79fe98 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -47,22 +47,26 @@
     return {mArmedInfo->mActualWakeupTime};
 }
 
-nsecs_t VSyncDispatchTimerQueueEntry::schedule(nsecs_t workDuration, nsecs_t earliestVsync,
-                                               VSyncTracker& tracker, nsecs_t now) {
+ScheduleResult VSyncDispatchTimerQueueEntry::schedule(nsecs_t workDuration, nsecs_t earliestVsync,
+                                                      VSyncTracker& tracker, nsecs_t now) {
+    auto const nextVsyncTime =
+            tracker.nextAnticipatedVSyncTimeFrom(std::max(earliestVsync, now + workDuration));
+    if (mLastDispatchTime >= nextVsyncTime) { // already dispatched a callback for this vsync
+        return ScheduleResult::CannotSchedule;
+    }
+
+    auto const nextWakeupTime = nextVsyncTime - workDuration;
+    auto result = mArmedInfo ? ScheduleResult::ReScheduled : ScheduleResult::Scheduled;
     mWorkDuration = workDuration;
     mEarliestVsync = earliestVsync;
-    arm(tracker, now);
-    return mArmedInfo->mActualWakeupTime;
+    mArmedInfo = {nextWakeupTime, nextVsyncTime};
+    return result;
 }
 
 void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) {
     if (!mArmedInfo) {
         return;
     }
-    arm(tracker, now);
-}
-
-void VSyncDispatchTimerQueueEntry::arm(VSyncTracker& tracker, nsecs_t now) {
     auto const nextVsyncTime =
             tracker.nextAnticipatedVSyncTimeFrom(std::max(mEarliestVsync, now + mWorkDuration));
     mArmedInfo = {nextVsyncTime - mWorkDuration, nextVsyncTime};
@@ -214,16 +218,13 @@
             return result;
         }
         auto& callback = it->second;
-        result = callback->wakeupTime() ? ScheduleResult::ReScheduled : ScheduleResult::Scheduled;
-
         auto const now = mTimeKeeper->now();
-        auto const wakeupTime = callback->schedule(workDuration, earliestVsync, mTracker, now);
-
-        if (wakeupTime < now - mTimerSlack || callback->lastExecutedVsyncTarget() > wakeupTime) {
-            return ScheduleResult::CannotSchedule;
+        result = callback->schedule(workDuration, earliestVsync, mTracker, now);
+        if (result == ScheduleResult::CannotSchedule) {
+            return result;
         }
 
-        if (wakeupTime < mIntendedWakeupTime - mTimerSlack) {
+        if (callback->wakeupTime() < mIntendedWakeupTime - mTimerSlack) {
             rearmTimerSkippingUpdateFor(now, it);
         }
     }
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index f058099..fc78da3 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -44,8 +44,8 @@
     std::optional<nsecs_t> lastExecutedVsyncTarget() const;
 
     // This moves the state from disarmed->armed and will calculate the wakeupTime.
-    nsecs_t schedule(nsecs_t workDuration, nsecs_t earliestVsync, VSyncTracker& tracker,
-                     nsecs_t now);
+    ScheduleResult schedule(nsecs_t workDuration, nsecs_t earliestVsync, VSyncTracker& tracker,
+                            nsecs_t now);
     // This will update armed entries with the latest vsync information. Entry remains armed.
     void update(VSyncTracker& tracker, nsecs_t now);
 
@@ -67,7 +67,6 @@
     void ensureNotRunning();
 
 private:
-    void arm(VSyncTracker& tracker, nsecs_t now);
     std::string const mName;
     std::function<void(nsecs_t)> const mCallback;
 
@@ -106,7 +105,8 @@
     VSyncDispatchTimerQueue(VSyncDispatchTimerQueue const&) = delete;
     VSyncDispatchTimerQueue& operator=(VSyncDispatchTimerQueue const&) = delete;
 
-    using CallbackMap = std::unordered_map<size_t, std::shared_ptr<VSyncDispatchTimerQueueEntry>>;
+    using CallbackMap =
+            std::unordered_map<CallbackToken, std::shared_ptr<VSyncDispatchTimerQueueEntry>>;
 
     void timerCallback();
     void setTimer(nsecs_t, nsecs_t) REQUIRES(mMutex);
diff --git a/services/surfaceflinger/Scheduler/VSyncModulator.cpp b/services/surfaceflinger/Scheduler/VSyncModulator.cpp
index 27fd76c..8de35b1 100644
--- a/services/surfaceflinger/Scheduler/VSyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncModulator.cpp
@@ -134,18 +134,13 @@
         return;
     }
 
-    const bool isDefault = mOffsets.fpsMode == RefreshRateType::DEFAULT;
-    const bool isPerformance = mOffsets.fpsMode == RefreshRateType::PERFORMANCE;
     const bool isEarly = &offsets == &mOffsetsConfig.early;
     const bool isEarlyGl = &offsets == &mOffsetsConfig.earlyGl;
     const bool isLate = &offsets == &mOffsetsConfig.late;
 
-    ATRACE_INT("Vsync-EarlyOffsetsOn", isDefault && isEarly);
-    ATRACE_INT("Vsync-EarlyGLOffsetsOn", isDefault && isEarlyGl);
-    ATRACE_INT("Vsync-LateOffsetsOn", isDefault && isLate);
-    ATRACE_INT("Vsync-HighFpsEarlyOffsetsOn", isPerformance && isEarly);
-    ATRACE_INT("Vsync-HighFpsEarlyGLOffsetsOn", isPerformance && isEarlyGl);
-    ATRACE_INT("Vsync-HighFpsLateOffsetsOn", isPerformance && isLate);
+    ATRACE_INT("Vsync-EarlyOffsetsOn", isEarly);
+    ATRACE_INT("Vsync-EarlyGLOffsetsOn", isEarlyGl);
+    ATRACE_INT("Vsync-LateOffsetsOn", isLate);
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncModulator.h b/services/surfaceflinger/Scheduler/VSyncModulator.h
index 727cef2..63c0feb 100644
--- a/services/surfaceflinger/Scheduler/VSyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VSyncModulator.h
@@ -37,13 +37,10 @@
     // switch in and out of gl composition.
     static constexpr int MIN_EARLY_GL_FRAME_COUNT_TRANSACTION = 2;
 
-    using RefreshRateType = RefreshRateConfigs::RefreshRateType;
-
 public:
     // Wrapper for a collection of surfaceflinger/app offsets for a particular
     // configuration.
     struct Offsets {
-        RefreshRateType fpsMode;
         nsecs_t sf;
         nsecs_t app;
     };
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 643c5d2..3b99a58 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -23,6 +23,7 @@
 #include <utils/Trace.h>
 #include <algorithm>
 #include <chrono>
+#include <sstream>
 #include "SchedulerUtils.h"
 
 namespace android::scheduler {
@@ -54,6 +55,11 @@
     return percent < kOutlierTolerancePercent || percent > (kMaxPercent - kOutlierTolerancePercent);
 }
 
+nsecs_t VSyncPredictor::currentPeriod() const {
+    std::lock_guard<std::mutex> lk(mMutex);
+    return std::get<0>(mRateMap.find(mIdealPeriod)->second);
+}
+
 void VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
     std::lock_guard<std::mutex> lk(mMutex);
 
@@ -148,12 +154,24 @@
     }
 
     auto const oldest = *std::min_element(timestamps.begin(), timestamps.end());
-    auto const ordinalRequest = (timePoint - oldest + slope) / slope;
+
+    // See b/145667109, the ordinal calculation must take into account the intercept.
+    auto const zeroPoint = oldest + intercept;
+    auto const ordinalRequest = (timePoint - zeroPoint + slope) / slope;
     auto const prediction = (ordinalRequest * slope) + intercept + oldest;
 
-    ALOGV("prediction made from: %" PRId64 " prediction: %" PRId64 " (+%" PRId64 ") slope: %" PRId64
-          " intercept: %" PRId64,
-          timePoint, prediction, prediction - timePoint, slope, intercept);
+    auto const printer = [&, slope = slope, intercept = intercept] {
+        std::stringstream str;
+        str << "prediction made from: " << timePoint << "prediction: " << prediction << " (+"
+            << prediction - timePoint << ") slope: " << slope << " intercept: " << intercept
+            << "oldestTS: " << oldest << " ordinal: " << ordinalRequest;
+        return str.str();
+    };
+
+    ALOGV("%s", printer().c_str());
+    LOG_ALWAYS_FATAL_IF(prediction < timePoint, "VSyncPredictor: model miscalculation: %s",
+                        printer().c_str());
+
     return prediction;
 }
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 1590f49..4210b3c 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -39,6 +39,7 @@
 
     void addVsyncTimestamp(nsecs_t timestamp) final;
     nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final;
+    nsecs_t currentPeriod() const final;
 
     /*
      * Inform the model that the period is anticipated to change to a new value.
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
new file mode 100644
index 0000000..f2a7791
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "VSyncReactor.h"
+#include "TimeKeeper.h"
+#include "VSyncDispatch.h"
+#include "VSyncTracker.h"
+
+namespace android::scheduler {
+
+Clock::~Clock() = default;
+
+VSyncReactor::VSyncReactor(std::unique_ptr<Clock> clock, std::unique_ptr<VSyncDispatch> dispatch,
+                           std::unique_ptr<VSyncTracker> tracker, size_t pendingFenceLimit)
+      : mClock(std::move(clock)),
+        mDispatch(std::move(dispatch)),
+        mTracker(std::move(tracker)),
+        mPendingLimit(pendingFenceLimit) {}
+
+bool VSyncReactor::addPresentFence(const std::shared_ptr<FenceTime>& fence) {
+    if (!fence) {
+        return false;
+    }
+
+    nsecs_t const signalTime = fence->getCachedSignalTime();
+    if (signalTime == Fence::SIGNAL_TIME_INVALID) {
+        return true;
+    }
+
+    std::lock_guard<std::mutex> lk(mMutex);
+    if (mIgnorePresentFences) {
+        return true;
+    }
+
+    for (auto it = mUnfiredFences.begin(); it != mUnfiredFences.end();) {
+        auto const time = (*it)->getCachedSignalTime();
+        if (time == Fence::SIGNAL_TIME_PENDING) {
+            it++;
+        } else if (time == Fence::SIGNAL_TIME_INVALID) {
+            it = mUnfiredFences.erase(it);
+        } else {
+            mTracker->addVsyncTimestamp(time);
+            it = mUnfiredFences.erase(it);
+        }
+    }
+
+    if (signalTime == Fence::SIGNAL_TIME_PENDING) {
+        if (mPendingLimit == mUnfiredFences.size()) {
+            mUnfiredFences.erase(mUnfiredFences.begin());
+        }
+        mUnfiredFences.push_back(fence);
+    } else {
+        mTracker->addVsyncTimestamp(signalTime);
+    }
+
+    return false; // TODO(b/144707443): add policy for turning on HWVsync.
+}
+
+void VSyncReactor::setIgnorePresentFences(bool ignoration) {
+    std::lock_guard<std::mutex> lk(mMutex);
+    mIgnorePresentFences = ignoration;
+    if (mIgnorePresentFences == true) {
+        mUnfiredFences.clear();
+    }
+}
+
+nsecs_t VSyncReactor::computeNextRefresh(int periodOffset) const {
+    auto const now = mClock->now();
+    auto const currentPeriod = periodOffset ? mTracker->currentPeriod() : 0;
+    return mTracker->nextAnticipatedVSyncTimeFrom(now + periodOffset * currentPeriod);
+}
+
+nsecs_t VSyncReactor::expectedPresentTime() {
+    return mTracker->nextAnticipatedVSyncTimeFrom(mClock->now());
+}
+
+void VSyncReactor::setPeriod(nsecs_t period) {
+    mTracker->setPeriod(period);
+    {
+        std::lock_guard<std::mutex> lk(mMutex);
+        mPeriodChangeInProgress = true;
+    }
+}
+
+nsecs_t VSyncReactor::getPeriod() {
+    return mTracker->currentPeriod();
+}
+
+void VSyncReactor::beginResync() {}
+
+void VSyncReactor::endResync() {}
+
+bool VSyncReactor::addResyncSample(nsecs_t timestamp, bool* periodFlushed) {
+    assert(periodFlushed);
+    mTracker->addVsyncTimestamp(timestamp);
+    {
+        std::lock_guard<std::mutex> lk(mMutex);
+        *periodFlushed = mPeriodChangeInProgress;
+        mPeriodChangeInProgress = false;
+    }
+    return false;
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.h b/services/surfaceflinger/Scheduler/VSyncReactor.h
new file mode 100644
index 0000000..786ee98
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/thread_annotations.h>
+#include <ui/FenceTime.h>
+#include <memory>
+#include <mutex>
+#include <vector>
+
+namespace android::scheduler {
+
+class Clock;
+class VSyncDispatch;
+class VSyncTracker;
+
+// TODO (b/145217110): consider renaming.
+class VSyncReactor /* TODO (b/140201379): : public android::DispSync */ {
+public:
+    VSyncReactor(std::unique_ptr<Clock> clock, std::unique_ptr<VSyncDispatch> dispatch,
+                 std::unique_ptr<VSyncTracker> tracker, size_t pendingFenceLimit);
+
+    bool addPresentFence(const std::shared_ptr<FenceTime>& fence);
+    void setIgnorePresentFences(bool ignoration);
+
+    nsecs_t computeNextRefresh(int periodOffset) const;
+    nsecs_t expectedPresentTime();
+
+    void setPeriod(nsecs_t period);
+    nsecs_t getPeriod();
+
+    // TODO: (b/145626181) remove begin,endResync functions from DispSync i/f when possible.
+    void beginResync();
+    bool addResyncSample(nsecs_t timestamp, bool* periodFlushed);
+    void endResync();
+
+private:
+    std::unique_ptr<Clock> const mClock;
+    std::unique_ptr<VSyncDispatch> const mDispatch;
+    std::unique_ptr<VSyncTracker> const mTracker;
+    size_t const mPendingLimit;
+
+    std::mutex mMutex;
+    bool mIgnorePresentFences GUARDED_BY(mMutex) = false;
+    std::vector<std::shared_ptr<FenceTime>> mUnfiredFences GUARDED_BY(mMutex);
+    bool mPeriodChangeInProgress GUARDED_BY(mMutex) = false;
+};
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 97b9620..6be63fe 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -47,6 +47,20 @@
      */
     virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const = 0;
 
+    /*
+     * The current period of the vsync signal.
+     *
+     * \return  The current period of the vsync signal
+     */
+    virtual nsecs_t currentPeriod() const = 0;
+
+    /*
+     * Inform the tracker that the period is changing and the tracker needs to recalibrate itself.
+     *
+     * \param [in] period   The period that the system is changing into.
+     */
+    virtual void setPeriod(nsecs_t period) = 0;
+
 protected:
     VSyncTracker(VSyncTracker const&) = delete;
     VSyncTracker& operator=(VSyncTracker const&) = delete;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 14a2ab1..9d5081c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -542,14 +542,8 @@
 
         if (mRefreshRateConfigs->refreshRateSwitchingSupported()) {
             // set the refresh rate according to the policy
-            const auto& performanceRefreshRate =
-                    mRefreshRateConfigs->getRefreshRateFromType(RefreshRateType::PERFORMANCE);
-
-            if (isDisplayConfigAllowed(performanceRefreshRate.configId)) {
-                setRefreshRateTo(RefreshRateType::PERFORMANCE, Scheduler::ConfigEvent::None);
-            } else {
-                setRefreshRateTo(RefreshRateType::DEFAULT, Scheduler::ConfigEvent::None);
-            }
+            const auto& performanceRefreshRate = mRefreshRateConfigs->getMaxRefreshRateByPolicy();
+            changeRefreshRateLocked(performanceRefreshRate, Scheduler::ConfigEvent::None);
         }
     }));
 }
@@ -606,6 +600,7 @@
                         ? renderengine::RenderEngine::ContextPriority::HIGH
                         : renderengine::RenderEngine::ContextPriority::MEDIUM)
                 .build()));
+    mCompositionEngine->setTimeStats(mTimeStats);
 
     LOG_ALWAYS_FATAL_IF(mVrFlingerRequestsDisplay,
             "Starting with vr flinger active is not currently supported.");
@@ -821,9 +816,8 @@
         info.xdpi = xdpi;
         info.ydpi = ydpi;
         info.fps = 1e9 / hwConfig->getVsyncPeriod();
-        const auto refreshRateType =
-                mRefreshRateConfigs->getRefreshRateTypeFromHwcConfigId(hwConfig->getId());
-        const auto offset = mPhaseOffsets->getOffsetsForRefreshRate(refreshRateType);
+
+        const auto offset = mPhaseOffsets->getOffsetsForRefreshRate(info.fps);
         info.appVsyncOffset = offset.late.app;
 
         // This is how far in advance a buffer must be queued for
@@ -873,17 +867,17 @@
     if (display->isPrimary()) {
         std::lock_guard<std::mutex> lock(mActiveConfigLock);
         if (mDesiredActiveConfigChanged) {
-            return mDesiredActiveConfig.configId;
-        } else {
-            return display->getActiveConfig();
+            return mDesiredActiveConfig.configId.value();
         }
-    } else {
-        return display->getActiveConfig();
     }
+
+    return display->getActiveConfig().value();
 }
 
 void SurfaceFlinger::setDesiredActiveConfig(const ActiveConfigInfo& info) {
     ATRACE_CALL();
+    auto refreshRate = mRefreshRateConfigs->getRefreshRateFromConfigId(info.configId);
+    ALOGV("setDesiredActiveConfig(%s)", refreshRate.name.c_str());
 
     // Don't check against the current mode yet. Worst case we set the desired
     // config twice. However event generation config might have changed so we need to update it
@@ -902,13 +896,14 @@
         // As we called to set period, we will call to onRefreshRateChangeCompleted once
         // DispSync model is locked.
         mVSyncModulator->onRefreshRateChangeInitiated();
-        mPhaseOffsets->setRefreshRateType(info.type);
+
+        mPhaseOffsets->setRefreshRateFps(refreshRate.fps);
         mVSyncModulator->setPhaseOffsets(mPhaseOffsets->getCurrentOffsets());
     }
     mDesiredActiveConfigChanged = true;
 
     if (mRefreshRateOverlay) {
-        mRefreshRateOverlay->changeRefreshRate(mDesiredActiveConfig.type);
+        mRefreshRateOverlay->changeRefreshRate(refreshRate);
     }
 }
 
@@ -930,14 +925,15 @@
     }
 
     std::lock_guard<std::mutex> lock(mActiveConfigLock);
-    mRefreshRateConfigs->setCurrentConfig(mUpcomingActiveConfig.configId);
+    mRefreshRateConfigs->setCurrentConfigId(mUpcomingActiveConfig.configId);
     mRefreshRateStats->setConfigMode(mUpcomingActiveConfig.configId);
-
     display->setActiveConfig(mUpcomingActiveConfig.configId);
 
-    mPhaseOffsets->setRefreshRateType(mUpcomingActiveConfig.type);
+    auto refreshRate =
+            mRefreshRateConfigs->getRefreshRateFromConfigId(mUpcomingActiveConfig.configId);
+    mPhaseOffsets->setRefreshRateFps(refreshRate.fps);
     mVSyncModulator->setPhaseOffsets(mPhaseOffsets->getCurrentOffsets());
-    ATRACE_INT("ActiveConfigMode", mUpcomingActiveConfig.configId);
+    ATRACE_INT("ActiveConfigFPS", refreshRate.fps);
 
     if (mUpcomingActiveConfig.event != Scheduler::ConfigEvent::None) {
         mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value,
@@ -951,12 +947,15 @@
     mDesiredActiveConfigChanged = false;
 
     mScheduler->resyncToHardwareVsync(true, getVsyncPeriod());
-    mPhaseOffsets->setRefreshRateType(mUpcomingActiveConfig.type);
+    auto refreshRate =
+            mRefreshRateConfigs->getRefreshRateFromConfigId(mDesiredActiveConfig.configId);
+    mPhaseOffsets->setRefreshRateFps(refreshRate.fps);
     mVSyncModulator->setPhaseOffsets(mPhaseOffsets->getCurrentOffsets());
 }
 
 bool SurfaceFlinger::performSetActiveConfig() {
     ATRACE_CALL();
+    ALOGV("performSetActiveConfig");
     if (mCheckPendingFence) {
         if (previousFrameMissed()) {
             // fence has not signaled yet. wait for the next invalidate
@@ -980,6 +979,10 @@
         desiredActiveConfig = mDesiredActiveConfig;
     }
 
+    auto refreshRate =
+            mRefreshRateConfigs->getRefreshRateFromConfigId(desiredActiveConfig.configId);
+    ALOGV("performSetActiveConfig changing active config to %d(%s)", refreshRate.configId.value(),
+          refreshRate.name.c_str());
     const auto display = getDefaultDisplayDeviceLocked();
     if (!display || display->getActiveConfig() == desiredActiveConfig.configId) {
         // display is not valid or we are already in the requested mode
@@ -1000,12 +1003,26 @@
     const auto displayId = display->getId();
     LOG_ALWAYS_FATAL_IF(!displayId);
 
-    ATRACE_INT("ActiveConfigModeHWC", mUpcomingActiveConfig.configId);
-    getHwComposer().setActiveConfig(*displayId, mUpcomingActiveConfig.configId);
+    ATRACE_INT("ActiveConfigFPS_HWC", refreshRate.fps);
 
-    // we need to submit an empty frame to HWC to start the process
+    // TODO(b/142753666) use constrains
+    HWC2::VsyncPeriodChangeConstraints constraints;
+    constraints.desiredTimeNanos = systemTime();
+    constraints.seamlessRequired = false;
+
+    HWC2::VsyncPeriodChangeTimeline outTimeline;
+    auto status =
+            getHwComposer().setActiveConfigWithConstraints(*displayId,
+                                                           mUpcomingActiveConfig.configId.value(),
+                                                           constraints, &outTimeline);
+    if (status != NO_ERROR) {
+        LOG_ALWAYS_FATAL("setActiveConfigWithConstraints failed: %d", status);
+        return false;
+    }
+
+    mScheduler->onNewVsyncPeriodChangeTimeline(outTimeline);
+    // Scheduler will submit an empty frame to HWC if needed.
     mCheckPendingFence = true;
-    mEventQueue->invalidate();
     return false;
 }
 
@@ -1358,14 +1375,16 @@
         return 0;
     }
 
-    const auto config = getHwComposer().getActiveConfig(*displayId);
-    return config ? config->getVsyncPeriod() : 0;
+    return getHwComposer().getDisplayVsyncPeriod(*displayId);
 }
 
 void SurfaceFlinger::onVsyncReceived(int32_t sequenceId, hwc2_display_t hwcDisplayId,
-                                     int64_t timestamp) {
+                                     int64_t timestamp,
+                                     std::optional<hwc2_vsync_period_t> /*vsyncPeriod*/) {
     ATRACE_NAME("SF onVsync");
 
+    // TODO(b/140201379): use vsyncPeriod in the new DispSync
+
     Mutex::Autolock lock(mStateLock);
     // Ignore any vsyncs from a previous hardware composer.
     if (sequenceId != getBE().mComposerSequenceId) {
@@ -1393,11 +1412,12 @@
     *compositorTiming = getBE().mCompositorTiming;
 }
 
-bool SurfaceFlinger::isDisplayConfigAllowed(int32_t configId) const {
+bool SurfaceFlinger::isDisplayConfigAllowed(HwcConfigIndexType configId) const {
     return mAllowedDisplayConfigs.empty() || mAllowedDisplayConfigs.count(configId);
 }
 
-void SurfaceFlinger::setRefreshRateTo(RefreshRateType refreshRate, Scheduler::ConfigEvent event) {
+void SurfaceFlinger::changeRefreshRateLocked(const RefreshRate& refreshRate,
+                                             Scheduler::ConfigEvent event) {
     const auto display = getDefaultDisplayDeviceLocked();
     if (!display || mBootStage != BootStage::FINISHED) {
         return;
@@ -1405,15 +1425,13 @@
     ATRACE_CALL();
 
     // Don't do any updating if the current fps is the same as the new one.
-    const auto& refreshRateConfig = mRefreshRateConfigs->getRefreshRateFromType(refreshRate);
-    const int desiredConfigId = refreshRateConfig.configId;
-
-    if (!isDisplayConfigAllowed(desiredConfigId)) {
-        ALOGV("Skipping config %d as it is not part of allowed configs", desiredConfigId);
+    if (!isDisplayConfigAllowed(refreshRate.configId)) {
+        ALOGV("Skipping config %d as it is not part of allowed configs",
+              refreshRate.configId.value());
         return;
     }
 
-    setDesiredActiveConfig({refreshRate, desiredConfigId, event});
+    setDesiredActiveConfig({refreshRate.configId, event});
 }
 
 void SurfaceFlinger::onHotplugReceived(int32_t sequenceId, hwc2_display_t hwcDisplayId,
@@ -1442,6 +1460,16 @@
     setTransactionFlags(eDisplayTransactionNeeded);
 }
 
+void SurfaceFlinger::onVsyncPeriodTimingChangedReceived(
+        int32_t sequenceId, hwc2_display_t /*display*/,
+        const hwc_vsync_period_change_timeline_t& updatedTimeline) {
+    Mutex::Autolock lock(mStateLock);
+    if (sequenceId != getBE().mComposerSequenceId) {
+        return;
+    }
+    mScheduler->onNewVsyncPeriodChangeTimeline(updatedTimeline);
+}
+
 void SurfaceFlinger::onRefreshReceived(int sequenceId, hwc2_display_t /*hwcDisplayId*/) {
     Mutex::Autolock lock(mStateLock);
     if (sequenceId != getBE().mComposerSequenceId) {
@@ -1601,6 +1629,7 @@
     ATRACE_CALL();
     switch (what) {
         case MessageQueue::INVALIDATE: {
+            const nsecs_t frameStart = systemTime();
             // calculate the expected present time once and use the cached
             // value throughout this frame to make sure all layers are
             // seeing this same value.
@@ -1665,6 +1694,13 @@
                 // Signal a refresh if a transaction modified the window state,
                 // a new buffer was latched, or if HWC has requested a full
                 // repaint
+                if (mFrameStartTime <= 0) {
+                    // We should only use the time of the first invalidate
+                    // message that signals a refresh as the beginning of the
+                    // frame. Otherwise the real frame time will be
+                    // underestimated.
+                    mFrameStartTime = frameStart;
+                }
                 signalRefresh();
             }
             break;
@@ -1715,12 +1751,7 @@
     refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
     for (sp<Layer> layer : mLayersWithQueuedFrames) {
         auto compositionLayer = layer->getCompositionLayer();
-        if (compositionLayer) {
-            refreshArgs.layersWithQueuedFrames.push_back(compositionLayer.get());
-            mFrameTracer->traceTimestamp(layer->getSequence(), layer->getCurrentBufferId(),
-                                         layer->getCurrentFrameNumber(), systemTime(),
-                                         FrameTracer::FrameEvent::HWC_COMPOSITION_QUEUED);
-        }
+        if (compositionLayer) refreshArgs.layersWithQueuedFrames.push_back(compositionLayer.get());
     }
 
     refreshArgs.repaintEverything = mRepaintEverything.exchange(false);
@@ -1747,7 +1778,16 @@
 
     mGeometryInvalid = false;
 
+    // Store the present time just before calling to the composition engine so we could notify
+    // the scheduler.
+    const auto presentTime = systemTime();
+
     mCompositionEngine->present(refreshArgs);
+    mTimeStats->recordFrameDuration(mFrameStartTime, systemTime());
+    // Reset the frame start time now that we've recorded this frame.
+    mFrameStartTime = 0;
+
+    mScheduler->onDisplayRefreshed(presentTime);
 
     postFrame();
     postComposition();
@@ -1989,6 +2029,7 @@
     // Even though ATRACE_INT64 already checks if tracing is enabled, it doesn't prevent the
     // side-effect of getTotalSize(), so we check that again here
     if (ATRACE_ENABLED()) {
+        // getTotalSize returns the total number of buffers that were allocated by SurfaceFlinger
         ATRACE_INT64("Total Buffer Size", GraphicBufferAllocator::get().getTotalSize());
     }
 }
@@ -2165,7 +2206,8 @@
                                                     Dataspace::UNKNOWN});
     if (!state.isVirtual()) {
         LOG_ALWAYS_FATAL_IF(!displayId);
-        display->setActiveConfig(getHwComposer().getActiveConfigIndex(*displayId));
+        auto activeConfigId = HwcConfigIndexType(getHwComposer().getActiveConfigIndex(*displayId));
+        display->setActiveConfig(activeConfigId);
     }
 
     display->setLayerStack(state.layerStack);
@@ -2505,6 +2547,12 @@
     mCompositionEngine->updateCursorAsync(refreshArgs);
 }
 
+void SurfaceFlinger::changeRefreshRate(const RefreshRate& refreshRate,
+                                       Scheduler::ConfigEvent event) {
+    Mutex::Autolock lock(mStateLock);
+    changeRefreshRateLocked(refreshRate, event);
+}
+
 void SurfaceFlinger::initScheduler(DisplayId primaryDisplayId) {
     if (mScheduler) {
         // In practice it's not allowed to hotplug in/out the primary display once it's been
@@ -2513,7 +2561,7 @@
         return;
     }
 
-    int currentConfig = getHwComposer().getActiveConfigIndex(primaryDisplayId);
+    auto currentConfig = HwcConfigIndexType(getHwComposer().getActiveConfigIndex(primaryDisplayId));
     mRefreshRateConfigs =
             std::make_unique<scheduler::RefreshRateConfigs>(refresh_rate_switching(false),
                                                             getHwComposer().getConfigs(
@@ -2527,7 +2575,7 @@
     // start the EventThread
     mScheduler =
             getFactory().createScheduler([this](bool enabled) { setPrimaryVsyncEnabled(enabled); },
-                                         *mRefreshRateConfigs);
+                                         *mRefreshRateConfigs, *this);
     mAppConnectionHandle =
             mScheduler->createConnection("app", mPhaseOffsets->getCurrentAppOffset(),
                                          mPhaseOffsets->getOffsetThresholdForNextVsync(),
@@ -2546,12 +2594,6 @@
     mRegionSamplingThread =
             new RegionSamplingThread(*this, *mScheduler,
                                      RegionSamplingThread::EnvironmentTimingTunables());
-
-    mScheduler->setChangeRefreshRateCallback(
-            [this](RefreshRateType type, Scheduler::ConfigEvent event) {
-                Mutex::Autolock lock(mStateLock);
-                setRefreshRateTo(type, event);
-            });
 }
 
 void SurfaceFlinger::commitTransaction()
@@ -3114,7 +3156,13 @@
         listenerCallbacks.insert(listener);
     }
 
-    sp<Layer> layer(fromHandle(s.surface));
+    sp<Layer> layer = nullptr;
+    if (s.surface) {
+        layer = fromHandle(s.surface);
+    } else {
+        // The client may provide us a null handle. Treat it as if the layer was removed.
+        ALOGW("Attempt to set client state with a null layer handle");
+    }
     if (layer == nullptr) {
         for (auto& [listener, callbackIds] : s.listeners) {
             mTransactionCompletedThread.registerUnpresentedCallbackHandle(
@@ -3717,9 +3765,6 @@
     }
 
     if (currentMode == HWC_POWER_MODE_OFF) {
-        // Turn on the display
-        // TODO: @vhau temp fix only!  See b/141111965
-        mTransactionCompletedThread.clearAllPending();
         getHwComposer().setPowerMode(*displayId, mode);
         if (display->isPrimary() && mode != HWC_POWER_MODE_DOZE_SUSPEND) {
             setVsyncEnabledInHWC(*displayId, mHWCVsyncPendingState);
@@ -3951,10 +3996,17 @@
                   dispSyncPresentTimeOffset, getVsyncPeriod());
 
     StringAppendF(&result, "Allowed Display Configs: ");
-    for (int32_t configId : mAllowedDisplayConfigs) {
+    for (auto configId : mAllowedDisplayConfigs) {
         StringAppendF(&result, "%" PRIu32 " Hz, ",
-                      mRefreshRateConfigs->getRefreshRateFromConfigId(configId).fps);
+                      static_cast<int32_t>(
+                              mRefreshRateConfigs->getRefreshRateFromConfigId(configId).fps));
     }
+    StringAppendF(&result,
+                  "DesiredDisplayConfigSpecs: default config ID: %" PRIu32
+                  ", min: %.2f Hz, max: %.2f Hz",
+                  mDesiredDisplayConfigSpecs.defaultModeId,
+                  mDesiredDisplayConfigSpecs.minRefreshRate,
+                  mDesiredDisplayConfigSpecs.maxRefreshRate);
     StringAppendF(&result, "(config override by backdoor: %s)\n\n",
                   mDebugDisplayConfigSetByBackdoor ? "yes" : "no");
 
@@ -4286,8 +4338,8 @@
                       "  refresh-rate              : %f fps\n"
                       "  x-dpi                     : %f\n"
                       "  y-dpi                     : %f\n",
-                      1e9 / activeConfig->getVsyncPeriod(), activeConfig->getDpiX(),
-                      activeConfig->getDpiY());
+                      1e9 / getHwComposer().getDisplayVsyncPeriod(*displayId),
+                      activeConfig->getDpiX(), activeConfig->getDpiY());
     }
 
     StringAppendF(&result, "  transaction time: %f us\n", inTransactionDuration / 1000.0);
@@ -4384,13 +4436,16 @@
         case SET_ACTIVE_CONFIG:
         case SET_ALLOWED_DISPLAY_CONFIGS:
         case GET_ALLOWED_DISPLAY_CONFIGS:
+        case SET_DESIRED_DISPLAY_CONFIG_SPECS:
+        case GET_DESIRED_DISPLAY_CONFIG_SPECS:
         case SET_ACTIVE_COLOR_MODE:
         case INJECT_VSYNC:
         case SET_POWER_MODE:
         case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES:
         case SET_DISPLAY_CONTENT_SAMPLING_ENABLED:
         case GET_DISPLAYED_CONTENT_SAMPLE:
-        case NOTIFY_POWER_HINT: {
+        case NOTIFY_POWER_HINT:
+        case SET_GLOBAL_SHADOW_SETTINGS: {
             if (!callingThreadHasUnscopedSurfaceFlingerAccess()) {
                 IPCThreadState* ipc = IPCThreadState::self();
                 ALOGE("Permission Denial: can't access SurfaceFlinger pid=%d, uid=%d",
@@ -4793,13 +4848,9 @@
                 n = data.readInt32();
                 if (n && !mRefreshRateOverlay &&
                     mRefreshRateConfigs->refreshRateSwitchingSupported()) {
-                    RefreshRateType type;
-                    {
-                        std::lock_guard<std::mutex> lock(mActiveConfigLock);
-                        type = mDesiredActiveConfig.type;
-                    }
                     mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(*this);
-                    mRefreshRateOverlay->changeRefreshRate(type);
+                    auto current = mRefreshRateConfigs->getCurrentRefreshRate();
+                    mRefreshRateOverlay->changeRefreshRate(current);
                 } else if (!n) {
                     mRefreshRateOverlay.reset();
                 }
@@ -4903,8 +4954,12 @@
     }
     // Couldn't find display by displayId. Try to get display by layerStack since virtual displays
     // may not have a displayId.
+    return getDisplayByLayerStack(displayOrLayerStack);
+}
+
+const sp<DisplayDevice> SurfaceFlinger::getDisplayByLayerStack(uint64_t layerStack) {
     for (const auto& [token, display] : mDisplays) {
-        if (display->getLayerStack() == displayOrLayerStack) {
+        if (display->getLayerStack() == layerStack) {
             return display;
         }
     }
@@ -4960,8 +5015,8 @@
     public:
         LayerRenderArea(SurfaceFlinger* flinger, const sp<Layer>& layer, const Rect crop,
                         int32_t reqWidth, int32_t reqHeight, Dataspace reqDataSpace,
-                        bool childrenOnly)
-              : RenderArea(reqWidth, reqHeight, CaptureFill::CLEAR, reqDataSpace),
+                        bool childrenOnly, const Rect& displayViewport)
+              : RenderArea(reqWidth, reqHeight, CaptureFill::CLEAR, reqDataSpace, displayViewport),
                 mLayer(layer),
                 mCrop(crop),
                 mNeedsFiltering(false),
@@ -5045,7 +5100,7 @@
     sp<Layer> parent;
     Rect crop(sourceCrop);
     std::unordered_set<sp<Layer>, ISurfaceComposer::SpHash<Layer>> excludeLayers;
-
+    Rect displayViewport;
     {
         Mutex::Autolock _l(mStateLock);
 
@@ -5062,14 +5117,21 @@
             return PERMISSION_DENIED;
         }
 
+        Rect parentSourceBounds = parent->getCroppedBufferSize(parent->getCurrentState());
         if (sourceCrop.width() <= 0) {
             crop.left = 0;
-            crop.right = parent->getBufferSize(parent->getCurrentState()).getWidth();
+            crop.right = parentSourceBounds.getWidth();
         }
 
         if (sourceCrop.height() <= 0) {
             crop.top = 0;
-            crop.bottom = parent->getBufferSize(parent->getCurrentState()).getHeight();
+            crop.bottom = parentSourceBounds.getHeight();
+        }
+
+        if (crop.isEmpty() || frameScale <= 0.0f) {
+            // Error out if the layer has no source bounds (i.e. they are boundless) and a source
+            // crop was not specified, or an invalid frame scale was provided.
+            return BAD_VALUE;
         }
         reqWidth = crop.width() * frameScale;
         reqHeight = crop.height() * frameScale;
@@ -5083,6 +5145,13 @@
                 return NAME_NOT_FOUND;
             }
         }
+
+        auto display = getDisplayByLayerStack(parent->getLayerStack());
+        if (!display) {
+            return BAD_VALUE;
+        }
+
+        displayViewport = display->getViewport();
     } // mStateLock
 
     // really small crop or frameScale
@@ -5093,7 +5162,8 @@
         reqHeight = 1;
     }
 
-    LayerRenderArea renderArea(this, parent, crop, reqWidth, reqHeight, reqDataspace, childrenOnly);
+    LayerRenderArea renderArea(this, parent, crop, reqWidth, reqHeight, reqDataspace, childrenOnly,
+                               displayViewport);
     auto traverseLayers = [parent, childrenOnly,
                            &excludeLayers](const LayerVector::Visitor& visitor) {
         parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) {
@@ -5219,6 +5289,7 @@
     const auto rotation = renderArea.getRotationFlags();
     const auto transform = renderArea.getTransform();
     const auto sourceCrop = renderArea.getSourceCrop();
+    const auto& displayViewport = renderArea.getDisplayViewport();
 
     renderengine::DisplaySettings clientCompositionDisplay;
     std::vector<renderengine::LayerSettings> clientCompositionLayers;
@@ -5303,6 +5374,12 @@
         };
         auto result = layer->prepareClientComposition(targetSettings);
         if (result) {
+            std::optional<renderengine::LayerSettings> shadowLayer =
+                    layer->prepareShadowClientComposition(*result, displayViewport,
+                                                          clientCompositionDisplay.outputDataspace);
+            if (shadowLayer) {
+                clientCompositionLayers.push_back(*shadowLayer);
+            }
             clientCompositionLayers.push_back(*result);
         }
     });
@@ -5383,6 +5460,27 @@
 void SurfaceFlinger::setAllowedDisplayConfigsInternal(const sp<DisplayDevice>& display,
                                                       const std::vector<int32_t>& allowedConfigs) {
     if (!display->isPrimary()) {
+        // TODO(b/144711714): For non-primary displays we should be able to set an active config
+        // as well. For now, just call directly to setActiveConfigWithConstraints but ideally
+        // it should go thru setDesiredActiveConfig, similar to primary display.
+        ALOGV("setAllowedDisplayConfigsInternal for non-primary display");
+        const auto displayId = display->getId();
+        LOG_ALWAYS_FATAL_IF(!displayId);
+
+        HWC2::VsyncPeriodChangeConstraints constraints;
+        constraints.desiredTimeNanos = systemTime();
+        constraints.seamlessRequired = false;
+
+        HWC2::VsyncPeriodChangeTimeline timeline = {0, 0, 0};
+        getHwComposer().setActiveConfigWithConstraints(*displayId, allowedConfigs[0], constraints,
+                                                       &timeline);
+        if (timeline.refreshRequired) {
+            repaintEverythingForHWC();
+        }
+
+        auto configId = HwcConfigIndexType(allowedConfigs[0]);
+        display->setActiveConfig(configId);
+        mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value, configId);
         return;
     }
 
@@ -5401,28 +5499,48 @@
     mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value,
                                 display->getActiveConfig());
 
-    if (mRefreshRateConfigs->refreshRateSwitchingSupported()) {
-        const auto& type = mScheduler->getPreferredRefreshRateType();
-        const auto& config = mRefreshRateConfigs->getRefreshRateFromType(type);
-        if (isDisplayConfigAllowed(config.configId)) {
-            ALOGV("switching to Scheduler preferred config %d", config.configId);
-            setDesiredActiveConfig({type, config.configId, Scheduler::ConfigEvent::Changed});
-        } else {
-            // Set the highest allowed config by iterating backwards on available refresh rates
-            const auto& refreshRates = mRefreshRateConfigs->getRefreshRateMap();
-            for (auto iter = refreshRates.crbegin(); iter != refreshRates.crend(); ++iter) {
-                if (isDisplayConfigAllowed(iter->second.configId)) {
-                    ALOGV("switching to allowed config %d", iter->second.configId);
-                    setDesiredActiveConfig(
-                            {iter->first, iter->second.configId, Scheduler::ConfigEvent::Changed});
-                    break;
-                }
-            }
+    // Prepare the parameters needed for RefreshRateConfigs::setPolicy. This will change to just
+    // passthrough once DisplayManager provide these parameters directly.
+    const auto refreshRate =
+            mRefreshRateConfigs->getRefreshRateFromConfigId(HwcConfigIndexType(allowedConfigs[0]));
+    const auto defaultModeId = refreshRate.configId;
+    auto minRefreshRateFps = refreshRate.fps;
+    auto maxRefreshRateFps = minRefreshRateFps;
+
+    for (auto config : allowedConfigs) {
+        const auto configRefreshRate =
+                mRefreshRateConfigs->getRefreshRateFromConfigId(HwcConfigIndexType(config));
+        if (configRefreshRate.fps < minRefreshRateFps) {
+            minRefreshRateFps = configRefreshRate.fps;
+        } else if (configRefreshRate.fps > maxRefreshRateFps) {
+            maxRefreshRateFps = configRefreshRate.fps;
         }
-    } else if (!allowedConfigs.empty()) {
-        ALOGV("switching to config %d", allowedConfigs[0]);
-        setDesiredActiveConfig(
-                {RefreshRateType::DEFAULT, allowedConfigs[0], Scheduler::ConfigEvent::Changed});
+    }
+    mRefreshRateConfigs->setPolicy(defaultModeId, minRefreshRateFps, maxRefreshRateFps);
+
+    if (mRefreshRateConfigs->refreshRateSwitchingSupported()) {
+        auto configId = mScheduler->getPreferredConfigId();
+        auto preferredRefreshRate = configId
+                ? mRefreshRateConfigs->getRefreshRateFromConfigId(*configId)
+                : mRefreshRateConfigs->getMinRefreshRateByPolicy();
+        ALOGV("trying to switch to Scheduler preferred config %d (%s)",
+              preferredRefreshRate.configId.value(), preferredRefreshRate.name.c_str());
+        if (isDisplayConfigAllowed(preferredRefreshRate.configId)) {
+            ALOGV("switching to Scheduler preferred config %d",
+                  preferredRefreshRate.configId.value());
+            setDesiredActiveConfig(
+                    {preferredRefreshRate.configId, Scheduler::ConfigEvent::Changed});
+        } else {
+            // Set the highest allowed config
+            setDesiredActiveConfig({mRefreshRateConfigs->getMaxRefreshRateByPolicy().configId,
+                                    Scheduler::ConfigEvent::Changed});
+        }
+    } else {
+        if (!allowedConfigs.empty()) {
+            ALOGV("switching to config %d", allowedConfigs[0]);
+            auto configId = HwcConfigIndexType(allowedConfigs[0]);
+            setDesiredActiveConfig({configId, Scheduler::ConfigEvent::Changed});
+        }
     }
 }
 
@@ -5471,7 +5589,70 @@
     }
 
     if (display->isPrimary()) {
-        outAllowedConfigs->assign(mAllowedDisplayConfigs.begin(), mAllowedDisplayConfigs.end());
+        outAllowedConfigs->reserve(mAllowedDisplayConfigs.size());
+        for (auto configId : mAllowedDisplayConfigs) {
+            outAllowedConfigs->push_back(configId.value());
+        }
+    }
+
+    return NO_ERROR;
+}
+
+status_t SurfaceFlinger::setDesiredDisplayConfigSpecs(const sp<IBinder>& displayToken,
+                                                      int32_t defaultModeId, float minRefreshRate,
+                                                      float maxRefreshRate) {
+    ATRACE_CALL();
+
+    if (!displayToken) {
+        return BAD_VALUE;
+    }
+
+    postMessageSync(new LambdaMessage([&]() {
+        const auto display = getDisplayDeviceLocked(displayToken);
+        if (!display) {
+            ALOGE("Attempt to set desired display configs for invalid display token %p",
+                  displayToken.get());
+        } else if (display->isVirtual()) {
+            ALOGW("Attempt to set desired display configs for virtual display");
+        } else {
+            // TODO(b/142507213): Plug through to HWC once the interface is ready.
+            Mutex::Autolock lock(mStateLock);
+            const DesiredDisplayConfigSpecs desiredDisplayConfigSpecs = {defaultModeId,
+                                                                         minRefreshRate,
+                                                                         maxRefreshRate};
+            if (desiredDisplayConfigSpecs == mDesiredDisplayConfigSpecs) {
+                return;
+            }
+            ALOGV("Updating desired display configs");
+            ALOGD("desiredDisplayConfigSpecs: defaultId: %d min: %.f max: %.f decisions: ",
+                  desiredDisplayConfigSpecs.defaultModeId, desiredDisplayConfigSpecs.minRefreshRate,
+                  desiredDisplayConfigSpecs.maxRefreshRate);
+            mDesiredDisplayConfigSpecs = desiredDisplayConfigSpecs;
+        }
+    }));
+    return NO_ERROR;
+}
+
+status_t SurfaceFlinger::getDesiredDisplayConfigSpecs(const sp<IBinder>& displayToken,
+                                                      int32_t* outDefaultModeId,
+                                                      float* outMinRefreshRate,
+                                                      float* outMaxRefreshRate) {
+    ATRACE_CALL();
+
+    if (!displayToken || !outDefaultModeId || !outMinRefreshRate || !outMaxRefreshRate) {
+        return BAD_VALUE;
+    }
+
+    Mutex::Autolock lock(mStateLock);
+    const auto display = getDisplayDeviceLocked(displayToken);
+    if (!display) {
+        return NAME_NOT_FOUND;
+    }
+
+    if (display->isPrimary()) {
+        *outDefaultModeId = mDesiredDisplayConfigSpecs.defaultModeId;
+        *outMinRefreshRate = mDesiredDisplayConfigSpecs.minRefreshRate;
+        *outMaxRefreshRate = mDesiredDisplayConfigSpecs.maxRefreshRate;
     }
 
     return NO_ERROR;
@@ -5482,7 +5663,10 @@
 }
 
 sp<Layer> SurfaceFlinger::fromHandle(const sp<IBinder>& handle) {
-    BBinder *b = handle->localBinder();
+    BBinder* b = nullptr;
+    if (handle) {
+        b = handle->localBinder();
+    }
     if (b == nullptr) {
         return nullptr;
     }
@@ -5500,6 +5684,20 @@
 
 void SurfaceFlinger::onLayerDestroyed(Layer* layer) {
     mNumLayers--;
+    removeFromOffscreenLayers(layer);
+}
+
+// WARNING: ONLY CALL THIS FROM LAYER DTOR
+// Here we add children in the current state to offscreen layers and remove the
+// layer itself from the offscreen layer list.  Since
+// this is the dtor, it is safe to access the current state.  This keeps us
+// from dangling children layers such that they are not reachable from the
+// Drawing state nor the offscreen layer list
+// See b/141111965
+void SurfaceFlinger::removeFromOffscreenLayers(Layer* layer) {
+    for (auto& child : layer->getCurrentChildren()) {
+        mOffscreenLayers.emplace(child.get());
+    }
     mOffscreenLayers.erase(layer);
 }
 
@@ -5507,6 +5705,22 @@
     getRenderEngine().unbindExternalTextureBuffer(clientCacheId.id);
 }
 
+status_t SurfaceFlinger::setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor,
+                                                 float lightPosY, float lightPosZ,
+                                                 float lightRadius) {
+    Mutex::Autolock _l(mStateLock);
+    mCurrentState.globalShadowSettings.ambientColor = vec4(ambientColor);
+    mCurrentState.globalShadowSettings.spotColor = vec4(spotColor);
+    mCurrentState.globalShadowSettings.lightPos.y = lightPosY;
+    mCurrentState.globalShadowSettings.lightPos.z = lightPosZ;
+    mCurrentState.globalShadowSettings.lightRadius = lightRadius;
+
+    // these values are overridden when calculating the shadow settings for a layer.
+    mCurrentState.globalShadowSettings.lightPos.x = 0.f;
+    mCurrentState.globalShadowSettings.length = 0.f;
+    return NO_ERROR;
+}
+
 } // namespace android
 
 #if defined(__gl_h_)
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 50b3ae4..53b8908 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -37,6 +37,7 @@
 #include <input/ISetInputWindowsListener.h>
 #include <layerproto/LayerProtoHeader.h>
 #include <math/mat4.h>
+#include <renderengine/LayerSettings.h>
 #include <serviceutils/PriorityDumper.h>
 #include <system/graphics.h>
 #include <ui/FenceTime.h>
@@ -174,7 +175,8 @@
                        public PriorityDumper,
                        public ClientCache::ErasedRecipient,
                        private IBinder::DeathRecipient,
-                       private HWC2::ComposerCallback {
+                       private HWC2::ComposerCallback,
+                       private ISchedulerCallback {
 public:
     SurfaceFlingerBE& getBE() { return mBE; }
     const SurfaceFlingerBE& getBE() const { return mBE; }
@@ -271,9 +273,6 @@
     // force full composition on all displays
     void repaintEverything();
 
-    // force full composition on all displays without resetting the scheduler idle timer.
-    void repaintEverythingForHWC();
-
     surfaceflinger::Factory& getFactory() { return mFactory; }
 
     // The CompositionEngine encapsulates all composition related interfaces and actions.
@@ -313,6 +312,8 @@
     void onLayerFirstRef(Layer*);
     void onLayerDestroyed(Layer*);
 
+    void removeFromOffscreenLayers(Layer* layer);
+
     TransactionCompletedThread& getTransactionCompletedThread() {
         return mTransactionCompletedThread;
     }
@@ -365,6 +366,8 @@
             if (colorMatrixChanged) {
                 colorMatrix = other.colorMatrix;
             }
+            globalShadowSettings = other.globalShadowSettings;
+
             return *this;
         }
 
@@ -375,10 +378,23 @@
         bool colorMatrixChanged = true;
         mat4 colorMatrix;
 
+        renderengine::ShadowSettings globalShadowSettings;
+
         void traverseInZOrder(const LayerVector::Visitor& visitor) const;
         void traverseInReverseZOrder(const LayerVector::Visitor& visitor) const;
     };
 
+    struct DesiredDisplayConfigSpecs {
+        int32_t defaultModeId;
+        float minRefreshRate;
+        float maxRefreshRate;
+
+        bool operator==(const DesiredDisplayConfigSpecs& other) const {
+            return defaultModeId == other.defaultModeId && minRefreshRate == other.minRefreshRate &&
+                    maxRefreshRate == other.maxRefreshRate;
+        }
+    };
+
     /* ------------------------------------------------------------------------
      * IBinder interface
      */
@@ -466,11 +482,17 @@
                                       const std::vector<int32_t>& allowedConfigs) override;
     status_t getAllowedDisplayConfigs(const sp<IBinder>& displayToken,
                                       std::vector<int32_t>* outAllowedConfigs) override;
+    status_t setDesiredDisplayConfigSpecs(const sp<IBinder>& displayToken, int32_t displayModeId,
+                                          float minRefreshRate, float maxRefreshRate) override;
+    status_t getDesiredDisplayConfigSpecs(const sp<IBinder>& displayToken,
+                                          int32_t* outDefaultModeId, float* outMinRefreshRate,
+                                          float* outMaxRefreshRate) override;
     status_t getDisplayBrightnessSupport(const sp<IBinder>& displayToken,
                                          bool* outSupport) const override;
     status_t setDisplayBrightness(const sp<IBinder>& displayToken, float brightness) const override;
     status_t notifyPowerHint(int32_t hintId) override;
-
+    status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor,
+                                     float lightPosY, float lightPosZ, float lightRadius) override;
     /* ------------------------------------------------------------------------
      * DeathRecipient interface
      */
@@ -484,13 +506,22 @@
     /* ------------------------------------------------------------------------
      * HWC2::ComposerCallback / HWComposer::EventHandler interface
      */
-    void onVsyncReceived(int32_t sequenceId, hwc2_display_t hwcDisplayId,
-                         int64_t timestamp) override;
+    void onVsyncReceived(int32_t sequenceId, hwc2_display_t hwcDisplayId, int64_t timestamp,
+                         std::optional<hwc2_vsync_period_t> vsyncPeriod) override;
     void onHotplugReceived(int32_t sequenceId, hwc2_display_t hwcDisplayId,
                            HWC2::Connection connection) override;
     void onRefreshReceived(int32_t sequenceId, hwc2_display_t hwcDisplayId) override;
+    void onVsyncPeriodTimingChangedReceived(
+            int32_t sequenceId, hwc2_display_t display,
+            const hwc_vsync_period_change_timeline_t& updatedTimeline) override;
 
     /* ------------------------------------------------------------------------
+     * ISchedulerCallback
+     */
+    void changeRefreshRate(const Scheduler::RefreshRate&, Scheduler::ConfigEvent) override;
+    // force full composition on all displays without resetting the scheduler idle timer.
+    void repaintEverythingForHWC() override;
+    /* ------------------------------------------------------------------------
      * Message handling
      */
     void waitForEvent();
@@ -500,15 +531,14 @@
     void signalLayerUpdate();
     void signalRefresh();
 
-    using RefreshRateType = scheduler::RefreshRateConfigs::RefreshRateType;
+    using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
 
     struct ActiveConfigInfo {
-        RefreshRateType type = RefreshRateType::DEFAULT;
-        int configId = 0;
+        HwcConfigIndexType configId;
         Scheduler::ConfigEvent event = Scheduler::ConfigEvent::None;
 
         bool operator!=(const ActiveConfigInfo& other) const {
-            return type != other.type || configId != other.configId || event != other.event;
+            return configId != other.configId || event != other.event;
         }
     };
 
@@ -661,6 +691,7 @@
                                  const sp<GraphicBuffer>& buffer, bool useIdentityTransform,
                                  bool& outCapturedSecureLayers);
     const sp<DisplayDevice> getDisplayByIdOrLayerStack(uint64_t displayOrLayerStack);
+    const sp<DisplayDevice> getDisplayByLayerStack(uint64_t layerStack);
     status_t captureScreenImplLocked(const RenderArea& renderArea,
                                      TraverseLayersFunction traverseLayers,
                                      ANativeWindowBuffer* buffer, bool useIdentityTransform,
@@ -783,9 +814,10 @@
 
     // Sets the refresh rate by switching active configs, if they are available for
     // the desired refresh rate.
-    void setRefreshRateTo(RefreshRateType, Scheduler::ConfigEvent event) REQUIRES(mStateLock);
+    void changeRefreshRateLocked(const RefreshRate&, Scheduler::ConfigEvent event)
+            REQUIRES(mStateLock);
 
-    bool isDisplayConfigAllowed(int32_t configId) const REQUIRES(mStateLock);
+    bool isDisplayConfigAllowed(HwcConfigIndexType configId) const REQUIRES(mStateLock);
 
     bool previousFrameMissed(int graceTimeMs = 0);
 
@@ -1109,8 +1141,9 @@
     std::atomic<nsecs_t> mExpectedPresentTime = 0;
 
     // All configs are allowed if the set is empty.
-    using DisplayConfigs = std::set<int32_t>;
+    using DisplayConfigs = std::set<HwcConfigIndexType>;
     DisplayConfigs mAllowedDisplayConfigs GUARDED_BY(mStateLock);
+    DesiredDisplayConfigSpecs mDesiredDisplayConfigSpecs GUARDED_BY(mStateLock);
 
     std::mutex mActiveConfigLock;
     // This bit is set once we start setting the config. We read from this bit during the
@@ -1148,6 +1181,9 @@
     bool mPendingSyncInputWindows GUARDED_BY(mStateLock);
     Hwc2::impl::PowerAdvisor mPowerAdvisor;
 
+    // This should only be accessed on the main thread.
+    nsecs_t mFrameStartTime = 0;
+
     std::unique_ptr<RefreshRateOverlay> mRefreshRateOverlay;
 
     // Flag used to set override allowed display configs from backdoor
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index 4f439da..bd4cdba 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -64,8 +64,9 @@
 }
 
 std::unique_ptr<Scheduler> DefaultFactory::createScheduler(
-        SetVSyncEnabled setVSyncEnabled, const scheduler::RefreshRateConfigs& configs) {
-    return std::make_unique<Scheduler>(std::move(setVSyncEnabled), configs);
+        SetVSyncEnabled setVSyncEnabled, const scheduler::RefreshRateConfigs& configs,
+        ISchedulerCallback& schedulerCallback) {
+    return std::make_unique<Scheduler>(std::move(setVSyncEnabled), configs, schedulerCallback);
 }
 
 std::unique_ptr<SurfaceInterceptor> DefaultFactory::createSurfaceInterceptor(
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index 42bb177..1a24448 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -32,7 +32,8 @@
     std::unique_ptr<MessageQueue> createMessageQueue() override;
     std::unique_ptr<scheduler::PhaseOffsets> createPhaseOffsets() override;
     std::unique_ptr<Scheduler> createScheduler(SetVSyncEnabled,
-                                               const scheduler::RefreshRateConfigs&) override;
+                                               const scheduler::RefreshRateConfigs&,
+                                               ISchedulerCallback&) override;
     std::unique_ptr<SurfaceInterceptor> createSurfaceInterceptor(SurfaceFlinger*) override;
     sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override;
     sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs&&) override;
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index 20784d2..0db941d 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -40,6 +40,7 @@
 class HWComposer;
 class IGraphicBufferConsumer;
 class IGraphicBufferProducer;
+class ISchedulerCallback;
 class Layer;
 class MessageQueue;
 class Scheduler;
@@ -75,7 +76,8 @@
     virtual std::unique_ptr<MessageQueue> createMessageQueue() = 0;
     virtual std::unique_ptr<scheduler::PhaseOffsets> createPhaseOffsets() = 0;
     virtual std::unique_ptr<Scheduler> createScheduler(SetVSyncEnabled,
-                                                       const scheduler::RefreshRateConfigs&) = 0;
+                                                       const scheduler::RefreshRateConfigs&,
+                                                       ISchedulerCallback&) = 0;
     virtual std::unique_ptr<SurfaceInterceptor> createSurfaceInterceptor(SurfaceFlinger*) = 0;
 
     virtual sp<StartPropertySetThread> createStartPropertySetThread(
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 611afce..1895777 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -20,14 +20,13 @@
 #include "TimeStats.h"
 
 #include <android-base/stringprintf.h>
-
 #include <log/log.h>
-
 #include <utils/String8.h>
 #include <utils/Timers.h>
 #include <utils/Trace.h>
 
 #include <algorithm>
+#include <chrono>
 
 namespace android {
 
@@ -113,6 +112,46 @@
     mTimeStats.clientCompositionFrames++;
 }
 
+static int32_t msBetween(nsecs_t start, nsecs_t end) {
+    int64_t delta = std::chrono::duration_cast<std::chrono::milliseconds>(
+                            std::chrono::nanoseconds(end - start))
+                            .count();
+    delta = std::clamp(delta, int64_t(INT32_MIN), int64_t(INT32_MAX));
+    return static_cast<int32_t>(delta);
+}
+
+void TimeStats::recordFrameDuration(nsecs_t startTime, nsecs_t endTime) {
+    if (!mEnabled.load()) return;
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mPowerTime.powerMode == HWC_POWER_MODE_NORMAL) {
+        mTimeStats.frameDuration.insert(msBetween(startTime, endTime));
+    }
+}
+
+void TimeStats::recordRenderEngineDuration(nsecs_t startTime, nsecs_t endTime) {
+    if (!mEnabled.load()) return;
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mGlobalRecord.renderEngineDurations.size() == MAX_NUM_TIME_RECORDS) {
+        ALOGE("RenderEngineTimes are already at its maximum size[%zu]", MAX_NUM_TIME_RECORDS);
+        mGlobalRecord.renderEngineDurations.pop_front();
+    }
+    mGlobalRecord.renderEngineDurations.push_back({startTime, endTime});
+}
+
+void TimeStats::recordRenderEngineDuration(nsecs_t startTime,
+                                           const std::shared_ptr<FenceTime>& endTime) {
+    if (!mEnabled.load()) return;
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mGlobalRecord.renderEngineDurations.size() == MAX_NUM_TIME_RECORDS) {
+        ALOGE("RenderEngineTimes are already at its maximum size[%zu]", MAX_NUM_TIME_RECORDS);
+        mGlobalRecord.renderEngineDurations.pop_front();
+    }
+    mGlobalRecord.renderEngineDurations.push_back({startTime, endTime});
+}
+
 bool TimeStats::recordReadyLocked(int32_t layerId, TimeRecord* timeRecord) {
     if (!timeRecord->ready) {
         ALOGV("[%d]-[%" PRIu64 "]-presentFence is still not received", layerId,
@@ -149,12 +188,6 @@
     return true;
 }
 
-static int32_t msBetween(nsecs_t start, nsecs_t end) {
-    int64_t delta = (end - start) / 1000000;
-    delta = std::clamp(delta, int64_t(INT32_MIN), int64_t(INT32_MAX));
-    return static_cast<int32_t>(delta);
-}
-
 void TimeStats::flushAvailableRecordsToStatsLocked(int32_t layerId) {
     ATRACE_CALL();
 
@@ -491,6 +524,31 @@
         mGlobalRecord.prevPresentTime = curPresentTime;
         mGlobalRecord.presentFences.pop_front();
     }
+    while (!mGlobalRecord.renderEngineDurations.empty()) {
+        const auto duration = mGlobalRecord.renderEngineDurations.front();
+        const auto& endTime = duration.endTime;
+
+        nsecs_t endNs = -1;
+
+        if (auto val = std::get_if<nsecs_t>(&endTime)) {
+            endNs = *val;
+        } else {
+            endNs = std::get<std::shared_ptr<FenceTime>>(endTime)->getSignalTime();
+        }
+
+        if (endNs == Fence::SIGNAL_TIME_PENDING) break;
+
+        if (endNs < 0) {
+            ALOGE("RenderEngineTiming is invalid!");
+            mGlobalRecord.renderEngineDurations.pop_front();
+            continue;
+        }
+
+        const int32_t renderEngineMs = msBetween(duration.startTime, endNs);
+        mTimeStats.renderEngineTiming.insert(renderEngineMs);
+
+        mGlobalRecord.renderEngineDurations.pop_front();
+    }
 }
 
 void TimeStats::setPresentFenceGlobal(const std::shared_ptr<FenceTime>& presentFence) {
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index 6e71f5a..65e5cf4 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -27,6 +27,7 @@
 #include <mutex>
 #include <optional>
 #include <unordered_map>
+#include <variant>
 
 using namespace android::surfaceflinger;
 
@@ -44,6 +45,20 @@
     virtual void incrementMissedFrames() = 0;
     virtual void incrementClientCompositionFrames() = 0;
 
+    // Records the start and end times for a frame.
+    // The start time is the same as the beginning of a SurfaceFlinger
+    // invalidate message.
+    // The end time corresponds to when SurfaceFlinger finishes submitting the
+    // request to HWC to present a frame.
+    virtual void recordFrameDuration(nsecs_t startTime, nsecs_t endTime) = 0;
+    // Records the start time and end times for when RenderEngine begins work.
+    // The start time corresponds to the beginning of RenderEngine::drawLayers.
+    // The end time corresponds to when RenderEngine finishes rendering.
+    virtual void recordRenderEngineDuration(nsecs_t startTime, nsecs_t endTime) = 0;
+    // Same as above, but passes in a fence representing the end time.
+    virtual void recordRenderEngineDuration(nsecs_t startTime,
+                                            const std::shared_ptr<FenceTime>& readyFence) = 0;
+
     virtual void setPostTime(int32_t layerId, uint64_t frameNumber, const std::string& layerName,
                              nsecs_t postTime) = 0;
     virtual void setLatchTime(int32_t layerId, uint64_t frameNumber, nsecs_t latchTime) = 0;
@@ -51,6 +66,8 @@
     virtual void setAcquireTime(int32_t layerId, uint64_t frameNumber, nsecs_t acquireTime) = 0;
     virtual void setAcquireFence(int32_t layerId, uint64_t frameNumber,
                                  const std::shared_ptr<FenceTime>& acquireFence) = 0;
+    // SetPresent{Time, Fence} are not expected to be called in the critical
+    // rendering path, as they flush prior fences if those fences have fired.
     virtual void setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime) = 0;
     virtual void setPresentFence(int32_t layerId, uint64_t frameNumber,
                                  const std::shared_ptr<FenceTime>& presentFence) = 0;
@@ -100,9 +117,15 @@
         nsecs_t prevTime = 0;
     };
 
+    struct RenderEngineDuration {
+        nsecs_t startTime;
+        std::variant<nsecs_t, std::shared_ptr<FenceTime>> endTime;
+    };
+
     struct GlobalRecord {
         nsecs_t prevPresentTime = 0;
         std::deque<std::shared_ptr<FenceTime>> presentFences;
+        std::deque<RenderEngineDuration> renderEngineDurations;
     };
 
 public:
@@ -116,6 +139,11 @@
     void incrementMissedFrames() override;
     void incrementClientCompositionFrames() override;
 
+    void recordFrameDuration(nsecs_t startTime, nsecs_t endTime) override;
+    void recordRenderEngineDuration(nsecs_t startTime, nsecs_t endTime) override;
+    void recordRenderEngineDuration(nsecs_t startTime,
+                                    const std::shared_ptr<FenceTime>& readyFence) override;
+
     void setPostTime(int32_t layerId, uint64_t frameNumber, const std::string& layerName,
                      nsecs_t postTime) override;
     void setLatchTime(int32_t layerId, uint64_t frameNumber, nsecs_t latchTime) override;
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
index 16d2da0..7e43880 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -111,6 +111,10 @@
     StringAppendF(&result, "totalP2PTime = %" PRId64 " ms\n", presentToPresent.totalTime());
     StringAppendF(&result, "presentToPresent histogram is as below:\n");
     result.append(presentToPresent.toString());
+    StringAppendF(&result, "frameDuration histogram is as below:\n");
+    result.append(frameDuration.toString());
+    StringAppendF(&result, "renderEngineTiming histogram is as below:\n");
+    result.append(renderEngineTiming.toString());
     const auto dumpStats = generateDumpStats(maxLayers);
     for (const auto& ele : dumpStats) {
         result.append(ele->toString());
@@ -158,6 +162,16 @@
         histProto->set_time_millis(histEle.first);
         histProto->set_frame_count(histEle.second);
     }
+    for (const auto& histEle : frameDuration.hist) {
+        SFTimeStatsHistogramBucketProto* histProto = globalProto.add_frame_duration();
+        histProto->set_time_millis(histEle.first);
+        histProto->set_frame_count(histEle.second);
+    }
+    for (const auto& histEle : renderEngineTiming.hist) {
+        SFTimeStatsHistogramBucketProto* histProto = globalProto.add_render_engine_timing();
+        histProto->set_time_millis(histEle.first);
+        histProto->set_frame_count(histEle.second);
+    }
     const auto dumpStats = generateDumpStats(maxLayers);
     for (const auto& ele : dumpStats) {
         SFTimeStatsLayerProto* layerProto = globalProto.add_stats();
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
index f2ac7ff..bd97ecc 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -61,6 +61,8 @@
         int32_t clientCompositionFrames = 0;
         int64_t displayOnTime = 0;
         Histogram presentToPresent;
+        Histogram frameDuration;
+        Histogram renderEngineTiming;
         std::unordered_map<std::string, TimeStatsLayer> stats;
         std::unordered_map<uint32_t, nsecs_t> refreshRateStats;
 
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto b/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto
index 0dacbeb..5fd4a39 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto
+++ b/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto
@@ -25,7 +25,7 @@
 // changes to these messages, and keep google3 side proto messages in sync if
 // the end to end pipeline needs to be updated.
 
-// Next tag: 10
+// Next tag: 12
 message SFTimeStatsGlobalProto {
   // The stats start time in UTC as seconds since January 1, 1970
   optional int64 stats_start = 1;
@@ -43,6 +43,10 @@
   repeated SFTimeStatsDisplayConfigBucketProto display_config_stats = 9;
   // Present to present histogram.
   repeated SFTimeStatsHistogramBucketProto present_to_present = 8;
+  // Frame CPU duration histogram.
+  repeated SFTimeStatsHistogramBucketProto frame_duration = 10;
+  // Frame GPU duration histogram.
+  repeated SFTimeStatsHistogramBucketProto render_engine_timing = 11;
   // Stats per layer. Apps could have multiple layers.
   repeated SFTimeStatsLayerProto stats = 6;
 }
diff --git a/services/surfaceflinger/TransactionCompletedThread.cpp b/services/surfaceflinger/TransactionCompletedThread.cpp
index 1e07191..4c1baaf 100644
--- a/services/surfaceflinger/TransactionCompletedThread.cpp
+++ b/services/surfaceflinger/TransactionCompletedThread.cpp
@@ -189,15 +189,6 @@
     return NO_ERROR;
 }
 
-void TransactionCompletedThread::clearAllPending() {
-    std::lock_guard lock(mMutex);
-    if (!mRunning) {
-        return;
-    }
-    mPendingTransactions.clear();
-    mConditionVariable.notify_all();
-}
-
 status_t TransactionCompletedThread::registerUnpresentedCallbackHandle(
         const sp<CallbackHandle>& handle) {
     std::lock_guard lock(mMutex);
diff --git a/services/surfaceflinger/TransactionCompletedThread.h b/services/surfaceflinger/TransactionCompletedThread.h
index cd95bfb..12ea8fe 100644
--- a/services/surfaceflinger/TransactionCompletedThread.h
+++ b/services/surfaceflinger/TransactionCompletedThread.h
@@ -70,8 +70,6 @@
     // Notifies the TransactionCompletedThread that a pending CallbackHandle has been presented.
     status_t finalizePendingCallbackHandles(const std::deque<sp<CallbackHandle>>& handles);
 
-    void clearAllPending();
-
     // Adds the Transaction CallbackHandle from a layer that does not need to be relatched and
     // presented this frame.
     status_t registerUnpresentedCallbackHandle(const sp<CallbackHandle>& handle);
diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
index 51b20cb..049c872 100644
--- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
+++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
@@ -260,7 +260,7 @@
 prop {
     api_name: "color_space_agnostic_dataspace"
     type: Long
-    scope: System
+    scope: Public
     access: Readonly
     prop_name: "ro.surface_flinger.color_space_agnostic_dataspace"
 }
@@ -339,7 +339,7 @@
 prop {
     api_name: "set_display_power_timer_ms"
     type: Integer
-    scope: System
+    scope: Public
     access: Readonly
     prop_name: "ro.surface_flinger.set_display_power_timer_ms"
 }
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index d021fc2..ca7e18d 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -18,9 +18,11 @@
     test_suites: ["device-tests"],
     srcs: [
         "BufferGenerator.cpp",
+        "CommonTypes_test.cpp",
         "Credentials_test.cpp",
         "DereferenceSurfaceControl_test.cpp",
         "DisplayActiveConfig_test.cpp",
+        "DisplayConfigs_test.cpp",
         "InvalidHandles_test.cpp",
         "LayerCallback_test.cpp",
         "LayerRenderTypeTransaction_test.cpp",
@@ -41,6 +43,8 @@
         "libtrace_proto",
     ],
     shared_libs: [
+        "android.hardware.graphics.common@1.2",
+        "android.hardware.graphics.composer@2.1",
         "libandroid",
         "libbinder",
         "libcutils",
@@ -53,6 +57,7 @@
         "libtimestats_proto",
         "libui",
         "libutils",
+        "vintf-graphics-common-ndk_platform",
     ]
 }
 
diff --git a/services/surfaceflinger/tests/CommonTypes_test.cpp b/services/surfaceflinger/tests/CommonTypes_test.cpp
new file mode 100644
index 0000000..a3e16f9
--- /dev/null
+++ b/services/surfaceflinger/tests/CommonTypes_test.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <aidl/android/hardware/graphics/common/BlendMode.h>
+#include <aidl/android/hardware/graphics/common/Dataspace.h>
+
+#include <android/hardware/graphics/common/1.2/types.h>
+#include <android/hardware/graphics/composer/2.1/IComposerClient.h>
+
+using AidlBlendMode = aidl::android::hardware::graphics::common::BlendMode;
+using AidlDataspace = aidl::android::hardware::graphics::common::Dataspace;
+
+using HidlBlendMode = android::hardware::graphics::composer::V2_1::IComposerClient::BlendMode;
+using HidlDataspace = android::hardware::graphics::common::V1_2::Dataspace;
+
+static_assert(static_cast<uint32_t>(AidlBlendMode::INVALID) ==
+              static_cast<uint32_t>(HidlBlendMode::INVALID));
+static_assert(static_cast<uint32_t>(AidlBlendMode::NONE) ==
+              static_cast<uint32_t>(HidlBlendMode::NONE));
+static_assert(static_cast<uint32_t>(AidlBlendMode::PREMULTIPLIED) ==
+              static_cast<uint32_t>(HidlBlendMode::PREMULTIPLIED));
+static_assert(static_cast<uint32_t>(AidlBlendMode::COVERAGE) ==
+              static_cast<uint32_t>(HidlBlendMode::COVERAGE));
+
+static_assert(static_cast<uint32_t>(AidlDataspace::UNKNOWN) ==
+              static_cast<uint32_t>(HidlDataspace::UNKNOWN));
+static_assert(static_cast<uint32_t>(AidlDataspace::ARBITRARY) ==
+              static_cast<uint32_t>(HidlDataspace::ARBITRARY));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_SHIFT) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_SHIFT));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_MASK) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_MASK));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_UNSPECIFIED) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_UNSPECIFIED));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT709) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_BT709));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT601_625) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_BT601_625));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT601_625_UNADJUSTED) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_BT601_625_UNADJUSTED));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT601_525) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_BT601_525));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT601_525_UNADJUSTED) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_BT601_525_UNADJUSTED));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT2020) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_BT2020));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT2020_CONSTANT_LUMINANCE) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_BT2020_CONSTANT_LUMINANCE));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT470M) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_BT470M));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_FILM) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_FILM));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_DCI_P3) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_DCI_P3));
+static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_ADOBE_RGB) ==
+              static_cast<uint32_t>(HidlDataspace::STANDARD_ADOBE_RGB));
+static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_SHIFT) ==
+              static_cast<uint32_t>(HidlDataspace::TRANSFER_SHIFT));
+static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_MASK) ==
+              static_cast<uint32_t>(HidlDataspace::TRANSFER_MASK));
+static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_UNSPECIFIED) ==
+              static_cast<uint32_t>(HidlDataspace::TRANSFER_UNSPECIFIED));
+static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_LINEAR) ==
+              static_cast<uint32_t>(HidlDataspace::TRANSFER_LINEAR));
+static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_SRGB) ==
+              static_cast<uint32_t>(HidlDataspace::TRANSFER_SRGB));
+static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_SMPTE_170M) ==
+              static_cast<uint32_t>(HidlDataspace::TRANSFER_SMPTE_170M));
+static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_GAMMA2_2) ==
+              static_cast<uint32_t>(HidlDataspace::TRANSFER_GAMMA2_2));
+static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_GAMMA2_6) ==
+              static_cast<uint32_t>(HidlDataspace::TRANSFER_GAMMA2_6));
+static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_GAMMA2_8) ==
+              static_cast<uint32_t>(HidlDataspace::TRANSFER_GAMMA2_8));
+static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_ST2084) ==
+              static_cast<uint32_t>(HidlDataspace::TRANSFER_ST2084));
+static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_HLG) ==
+              static_cast<uint32_t>(HidlDataspace::TRANSFER_HLG));
+static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_SHIFT) ==
+              static_cast<uint32_t>(HidlDataspace::RANGE_SHIFT));
+static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_MASK) ==
+              static_cast<uint32_t>(HidlDataspace::RANGE_MASK));
+static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_UNSPECIFIED) ==
+              static_cast<uint32_t>(HidlDataspace::RANGE_UNSPECIFIED));
+static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_FULL) ==
+              static_cast<uint32_t>(HidlDataspace::RANGE_FULL));
+static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_LIMITED) ==
+              static_cast<uint32_t>(HidlDataspace::RANGE_LIMITED));
+static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_EXTENDED) ==
+              static_cast<uint32_t>(HidlDataspace::RANGE_EXTENDED));
+static_assert(static_cast<uint32_t>(AidlDataspace::V0_SRGB_LINEAR) ==
+              static_cast<uint32_t>(HidlDataspace::V0_SRGB_LINEAR));
+static_assert(static_cast<uint32_t>(AidlDataspace::V0_SCRGB_LINEAR) ==
+              static_cast<uint32_t>(HidlDataspace::V0_SCRGB_LINEAR));
+static_assert(static_cast<uint32_t>(AidlDataspace::V0_SRGB) ==
+              static_cast<uint32_t>(HidlDataspace::V0_SRGB));
+static_assert(static_cast<uint32_t>(AidlDataspace::V0_SCRGB) ==
+              static_cast<uint32_t>(HidlDataspace::V0_SCRGB));
+static_assert(static_cast<uint32_t>(AidlDataspace::V0_JFIF) ==
+              static_cast<uint32_t>(HidlDataspace::V0_JFIF));
+static_assert(static_cast<uint32_t>(AidlDataspace::V0_BT601_625) ==
+              static_cast<uint32_t>(HidlDataspace::V0_BT601_625));
+static_assert(static_cast<uint32_t>(AidlDataspace::V0_BT601_525) ==
+              static_cast<uint32_t>(HidlDataspace::V0_BT601_525));
+static_assert(static_cast<uint32_t>(AidlDataspace::V0_BT709) ==
+              static_cast<uint32_t>(HidlDataspace::V0_BT709));
+static_assert(static_cast<uint32_t>(AidlDataspace::DCI_P3_LINEAR) ==
+              static_cast<uint32_t>(HidlDataspace::DCI_P3_LINEAR));
+static_assert(static_cast<uint32_t>(AidlDataspace::DCI_P3) ==
+              static_cast<uint32_t>(HidlDataspace::DCI_P3));
+static_assert(static_cast<uint32_t>(AidlDataspace::DISPLAY_P3_LINEAR) ==
+              static_cast<uint32_t>(HidlDataspace::DISPLAY_P3_LINEAR));
+static_assert(static_cast<uint32_t>(AidlDataspace::DISPLAY_P3) ==
+              static_cast<uint32_t>(HidlDataspace::DISPLAY_P3));
+static_assert(static_cast<uint32_t>(AidlDataspace::ADOBE_RGB) ==
+              static_cast<uint32_t>(HidlDataspace::ADOBE_RGB));
+static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_LINEAR) ==
+              static_cast<uint32_t>(HidlDataspace::BT2020_LINEAR));
+static_assert(static_cast<uint32_t>(AidlDataspace::BT2020) ==
+              static_cast<uint32_t>(HidlDataspace::BT2020));
+static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_PQ) ==
+              static_cast<uint32_t>(HidlDataspace::BT2020_PQ));
+static_assert(static_cast<uint32_t>(AidlDataspace::DEPTH) ==
+              static_cast<uint32_t>(HidlDataspace::DEPTH));
+static_assert(static_cast<uint32_t>(AidlDataspace::SENSOR) ==
+              static_cast<uint32_t>(HidlDataspace::SENSOR));
+static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_ITU) ==
+              static_cast<uint32_t>(HidlDataspace::BT2020_ITU));
+static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_ITU_PQ) ==
+              static_cast<uint32_t>(HidlDataspace::BT2020_ITU_PQ));
+static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_ITU_HLG) ==
+              static_cast<uint32_t>(HidlDataspace::BT2020_ITU_HLG));
+static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_HLG) ==
+              static_cast<uint32_t>(HidlDataspace::BT2020_HLG));
+static_assert(static_cast<uint32_t>(AidlDataspace::DISPLAY_BT2020) ==
+              static_cast<uint32_t>(HidlDataspace::DISPLAY_BT2020));
+static_assert(static_cast<uint32_t>(AidlDataspace::DYNAMIC_DEPTH) ==
+              static_cast<uint32_t>(HidlDataspace::DYNAMIC_DEPTH));
+static_assert(static_cast<uint32_t>(AidlDataspace::JPEG_APP_SEGMENTS) ==
+              static_cast<uint32_t>(HidlDataspace::JPEG_APP_SEGMENTS));
+static_assert(static_cast<uint32_t>(AidlDataspace::HEIF) ==
+              static_cast<uint32_t>(HidlDataspace::HEIF));
+
+// Below are the dataspaces that have been deprecated for sometime. They are required to behave
+// the same as their V0_* counterparts. We redefined them in AIDL to be the same as the
+// their V0_* counterparts.
+static_assert(static_cast<uint32_t>(AidlDataspace::SRGB_LINEAR) ==
+              static_cast<uint32_t>(AidlDataspace::V0_SRGB_LINEAR));
+static_assert(static_cast<uint32_t>(AidlDataspace::SRGB) ==
+              static_cast<uint32_t>(AidlDataspace::V0_SRGB));
+static_assert(static_cast<uint32_t>(AidlDataspace::JFIF) ==
+              static_cast<uint32_t>(AidlDataspace::V0_JFIF));
+static_assert(static_cast<uint32_t>(AidlDataspace::BT601_625) ==
+              static_cast<uint32_t>(AidlDataspace::V0_BT601_625));
+static_assert(static_cast<uint32_t>(AidlDataspace::BT601_525) ==
+              static_cast<uint32_t>(AidlDataspace::V0_BT601_525));
+static_assert(static_cast<uint32_t>(AidlDataspace::BT709) ==
+              static_cast<uint32_t>(AidlDataspace::V0_BT709));
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index b667a74..b1bb7fd 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -274,7 +274,7 @@
         sp<GraphicBuffer> outBuffer;
         return ScreenshotClient::captureLayers(mBGSurfaceControl->getHandle(),
                                                ui::Dataspace::V0_SRGB, ui::PixelFormat::RGBA_8888,
-                                               Rect(), FRAME_SCALE, &outBuffer);
+                                               Rect(0, 0, 1, 1), FRAME_SCALE, &outBuffer);
     };
     ASSERT_NO_FATAL_FAILURE(checkWithPrivileges<status_t>(condition, NO_ERROR, PERMISSION_DENIED));
 }
diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
new file mode 100644
index 0000000..420fb29
--- /dev/null
+++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <thread>
+#include "LayerTransactionTest.h"
+namespace android {
+
+using android::hardware::graphics::common::V1_1::BufferUsage;
+
+::testing::Environment* const binderEnv =
+        ::testing::AddGlobalTestEnvironment(new BinderEnvironment());
+
+/**
+ * Test class for setting display configs and passing around refresh rate ranges.
+ */
+class RefreshRateRangeTest : public ::testing::Test {
+protected:
+    void SetUp() override { mDisplayToken = SurfaceComposerClient::getInternalDisplayToken(); }
+
+    sp<IBinder> mDisplayToken;
+    int32_t defaultConfigId;
+    float minRefreshRate;
+    float maxRefreshRate;
+};
+
+TEST_F(RefreshRateRangeTest, simpleSetAndGet) {
+    status_t res = SurfaceComposerClient::setDesiredDisplayConfigSpecs(mDisplayToken, 1, 45, 75);
+    EXPECT_EQ(res, NO_ERROR);
+
+    res = SurfaceComposerClient::getDesiredDisplayConfigSpecs(mDisplayToken, &defaultConfigId,
+                                                              &minRefreshRate, &maxRefreshRate);
+    EXPECT_EQ(res, NO_ERROR);
+    EXPECT_EQ(defaultConfigId, 1);
+    EXPECT_EQ(minRefreshRate, 45);
+    EXPECT_EQ(maxRefreshRate, 75);
+}
+
+TEST_F(RefreshRateRangeTest, complexSetAndGet) {
+    status_t res = SurfaceComposerClient::setDesiredDisplayConfigSpecs(mDisplayToken, 1, 45, 75);
+    EXPECT_EQ(res, NO_ERROR);
+
+    res = SurfaceComposerClient::getDesiredDisplayConfigSpecs(mDisplayToken, &defaultConfigId,
+                                                              &minRefreshRate, &maxRefreshRate);
+    EXPECT_EQ(res, NO_ERROR);
+    EXPECT_EQ(defaultConfigId, 1);
+    EXPECT_EQ(minRefreshRate, 45);
+    EXPECT_EQ(maxRefreshRate, 75);
+
+    // Second call overrides the first one.
+    res = SurfaceComposerClient::setDesiredDisplayConfigSpecs(mDisplayToken, 10, 145, 875);
+    EXPECT_EQ(res, NO_ERROR);
+    res = SurfaceComposerClient::getDesiredDisplayConfigSpecs(mDisplayToken, &defaultConfigId,
+                                                              &minRefreshRate, &maxRefreshRate);
+    EXPECT_EQ(res, NO_ERROR);
+    EXPECT_EQ(defaultConfigId, 10);
+    EXPECT_EQ(minRefreshRate, 145);
+    EXPECT_EQ(maxRefreshRate, 875);
+}
+} // namespace android
diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
index 999e82d..627de7a 100644
--- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
@@ -72,8 +72,8 @@
     ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", 32, 32));
     ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(layer, Color::RED, 32, 32));
 
-    // GLES requires only 4 bits of subpixel precision during rasterization
-    // XXX GLES composition does not match HWC composition due to precision
+    // GPU composition requires only 4 bits of subpixel precision during rasterization
+    // XXX GPU composition does not match HWC composition due to precision
     // loss (b/69315223)
     const float epsilon = 1.0f / 16.0f;
     Transaction().setPosition(layer, 0.5f - epsilon, 0.5f - epsilon).apply();
diff --git a/services/surfaceflinger/tests/LayerUpdate_test.cpp b/services/surfaceflinger/tests/LayerUpdate_test.cpp
index 73f563d..0ad122b 100644
--- a/services/surfaceflinger/tests/LayerUpdate_test.cpp
+++ b/services/surfaceflinger/tests/LayerUpdate_test.cpp
@@ -1443,6 +1443,61 @@
     mCapture->expectColor(Rect(0, 0, 9, 9), {100, 100, 100, 255});
 }
 
+TEST_F(ScreenCaptureTest, CaptureBoundlessLayerWithSourceCrop) {
+    sp<SurfaceControl> child = createColorLayer("Child layer", Color::RED, mFGSurfaceControl.get());
+    SurfaceComposerClient::Transaction().show(child).apply(true);
+
+    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    sp<GraphicBuffer> outBuffer;
+    Rect sourceCrop(0, 0, 10, 10);
+    ASSERT_EQ(NO_ERROR, sf->captureLayers(child->getHandle(), &outBuffer, sourceCrop));
+    ScreenCapture sc(outBuffer);
+
+    sc.expectColor(Rect(0, 0, 9, 9), Color::RED);
+}
+
+TEST_F(ScreenCaptureTest, CaptureBoundedLayerWithoutSourceCrop) {
+    sp<SurfaceControl> child = createColorLayer("Child layer", Color::RED, mFGSurfaceControl.get());
+    Rect layerCrop(0, 0, 10, 10);
+    SurfaceComposerClient::Transaction().setCrop_legacy(child, layerCrop).show(child).apply(true);
+
+    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    sp<GraphicBuffer> outBuffer;
+    Rect sourceCrop = Rect();
+    ASSERT_EQ(NO_ERROR, sf->captureLayers(child->getHandle(), &outBuffer, sourceCrop));
+    ScreenCapture sc(outBuffer);
+
+    sc.expectColor(Rect(0, 0, 9, 9), Color::RED);
+}
+
+TEST_F(ScreenCaptureTest, CaptureBoundlessLayerWithoutSourceCropFails) {
+    sp<SurfaceControl> child = createColorLayer("Child layer", Color::RED, mFGSurfaceControl.get());
+    SurfaceComposerClient::Transaction().show(child).apply(true);
+
+    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    sp<GraphicBuffer> outBuffer;
+    Rect sourceCrop = Rect();
+
+    ASSERT_EQ(BAD_VALUE, sf->captureLayers(child->getHandle(), &outBuffer, sourceCrop));
+}
+
+TEST_F(ScreenCaptureTest, CaptureBufferLayerWithoutBufferFails) {
+    sp<SurfaceControl> child = createSurface(mClient, "Child surface", 10, 10,
+                                             PIXEL_FORMAT_RGBA_8888, 0, mFGSurfaceControl.get());
+    SurfaceComposerClient::Transaction().show(child).apply(true);
+
+    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    sp<GraphicBuffer> outBuffer;
+    Rect sourceCrop = Rect();
+    ASSERT_EQ(BAD_VALUE, sf->captureLayers(child->getHandle(), &outBuffer, sourceCrop));
+
+    TransactionUtils::fillSurfaceRGBA8(child, Color::RED);
+    SurfaceComposerClient::Transaction().apply(true);
+    ASSERT_EQ(NO_ERROR, sf->captureLayers(child->getHandle(), &outBuffer, sourceCrop));
+    ScreenCapture sc(outBuffer);
+    sc.expectColor(Rect(0, 0, 9, 9), Color::RED);
+}
+
 // In the following tests we verify successful skipping of a parent layer,
 // so we use the same verification logic and only change how we mutate
 // the parent layer to verify that various properties are ignored.
diff --git a/services/surfaceflinger/tests/fakehwc/Android.bp b/services/surfaceflinger/tests/fakehwc/Android.bp
index 9d74761..31837a9 100644
--- a/services/surfaceflinger/tests/fakehwc/Android.bp
+++ b/services/surfaceflinger/tests/fakehwc/Android.bp
@@ -10,6 +10,9 @@
     ],
     shared_libs: [
         "android.hardware.graphics.composer@2.1",
+        "android.hardware.graphics.composer@2.2",
+        "android.hardware.graphics.composer@2.3",
+        "android.hardware.graphics.composer@2.4",
         "android.hardware.graphics.composer@2.1-resources",
         "android.hardware.graphics.mapper@2.0",
         "android.hardware.graphics.mapper@3.0",
@@ -39,8 +42,8 @@
         "libtrace_proto",
     ],
     header_libs: [
-        "android.hardware.graphics.composer@2.1-command-buffer",
-        "android.hardware.graphics.composer@2.1-hal",
+        "android.hardware.graphics.composer@2.4-command-buffer",
+        "android.hardware.graphics.composer@2.4-hal",
         "libsurfaceflinger_headers",
     ],
 }
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp
index eeb6efe..6d79615 100644
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp
+++ b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp
@@ -146,6 +146,7 @@
 
 FakeComposerClient::FakeComposerClient()
       : mEventCallback(nullptr),
+        mEventCallback_2_4(nullptr),
         mCurrentConfig(NULL_DISPLAY_CONFIG),
         mVsyncEnabled(false),
         mLayers(),
@@ -165,6 +166,9 @@
 
 void FakeComposerClient::registerEventCallback(EventCallback* callback) {
     ALOGV("registerEventCallback");
+    LOG_FATAL_IF(mEventCallback_2_4 != nullptr,
+                 "already registered using registerEventCallback_2_4");
+
     mEventCallback = callback;
     if (mEventCallback) {
         mEventCallback->onHotplug(PRIMARY_DISPLAY, IComposerCallback::Connection::CONNECTED);
@@ -179,12 +183,16 @@
 void FakeComposerClient::hotplugDisplay(Display display, IComposerCallback::Connection state) {
     if (mEventCallback) {
         mEventCallback->onHotplug(display, state);
+    } else if (mEventCallback_2_4) {
+        mEventCallback_2_4->onHotplug(display, state);
     }
 }
 
 void FakeComposerClient::refreshDisplay(Display display) {
     if (mEventCallback) {
         mEventCallback->onRefresh(display);
+    } else if (mEventCallback_2_4) {
+        mEventCallback_2_4->onRefresh(display);
     }
 }
 
@@ -193,33 +201,37 @@
     return 1;
 }
 
-Error FakeComposerClient::createVirtualDisplay(uint32_t /*width*/, uint32_t /*height*/,
-                                               PixelFormat* /*format*/, Display* /*outDisplay*/) {
+V2_1::Error FakeComposerClient::createVirtualDisplay(uint32_t /*width*/, uint32_t /*height*/,
+                                                     V1_0::PixelFormat* /*format*/,
+                                                     Display* /*outDisplay*/) {
     ALOGV("createVirtualDisplay");
-    return Error::NONE;
+    return V2_1::Error::NONE;
 }
 
-Error FakeComposerClient::destroyVirtualDisplay(Display /*display*/) {
+V2_1::Error FakeComposerClient::destroyVirtualDisplay(Display /*display*/) {
     ALOGV("destroyVirtualDisplay");
-    return Error::NONE;
+    return V2_1::Error::NONE;
 }
 
-Error FakeComposerClient::createLayer(Display /*display*/, Layer* outLayer) {
+V2_1::Error FakeComposerClient::createLayer(Display /*display*/, Layer* outLayer) {
     ALOGV("createLayer");
     *outLayer = mLayers.size();
     auto newLayer = std::make_unique<LayerImpl>();
     mLayers.push_back(std::move(newLayer));
-    return Error::NONE;
+    return V2_1::Error::NONE;
 }
 
-Error FakeComposerClient::destroyLayer(Display /*display*/, Layer layer) {
+V2_1::Error FakeComposerClient::destroyLayer(Display /*display*/, Layer layer) {
     ALOGV("destroyLayer");
     mLayers[layer]->mValid = false;
-    return Error::NONE;
+    return V2_1::Error::NONE;
 }
 
-Error FakeComposerClient::getActiveConfig(Display /*display*/, Config* outConfig) {
+V2_1::Error FakeComposerClient::getActiveConfig(Display display, Config* outConfig) {
     ALOGV("getActiveConfig");
+    if (mMockHal) {
+        return mMockHal->getActiveConfig(display, outConfig);
+    }
 
     // TODO Assert outConfig != nullptr
 
@@ -227,30 +239,480 @@
     // IComposerClient::getActiveConfig, but returning BAD_CONFIG
     // seems to not fit SurfaceFlinger plans. See version 2 below.
     // if (mCurrentConfig == NULL_DISPLAY_CONFIG) {
-    //     return Error::BAD_CONFIG;
+    //     return V2_1::Error::BAD_CONFIG;
     // }
     //*outConfig = mCurrentConfig;
     *outConfig = 1; // Very special config for you my friend
-    return Error::NONE;
+    return V2_1::Error::NONE;
 }
 
-Error FakeComposerClient::getClientTargetSupport(Display /*display*/, uint32_t /*width*/,
-                                                 uint32_t /*height*/, PixelFormat /*format*/,
-                                                 Dataspace /*dataspace*/) {
+V2_1::Error FakeComposerClient::getClientTargetSupport(Display /*display*/, uint32_t /*width*/,
+                                                       uint32_t /*height*/,
+                                                       V1_0::PixelFormat /*format*/,
+                                                       V1_0::Dataspace /*dataspace*/) {
     ALOGV("getClientTargetSupport");
-    return Error::NONE;
+    return V2_1::Error::NONE;
 }
 
-Error FakeComposerClient::getColorModes(Display /*display*/, hidl_vec<ColorMode>* /*outModes*/) {
+V2_1::Error FakeComposerClient::getColorModes(Display /*display*/,
+                                              hidl_vec<V1_0::ColorMode>* /*outModes*/) {
     ALOGV("getColorModes");
-    return Error::NONE;
+    return V2_1::Error::NONE;
 }
 
-Error FakeComposerClient::getDisplayAttribute(Display display, Config config,
-                                              IComposerClient::Attribute attribute,
-                                              int32_t* outValue) {
+V2_1::Error FakeComposerClient::getDisplayAttribute(Display display, Config config,
+                                                    V2_1::IComposerClient::Attribute attribute,
+                                                    int32_t* outValue) {
+    auto tmpError =
+            getDisplayAttribute_2_4(display, config,
+                                    static_cast<IComposerClient::Attribute>(attribute), outValue);
+    return static_cast<V2_1::Error>(tmpError);
+}
+
+V2_1::Error FakeComposerClient::getDisplayConfigs(Display display, hidl_vec<Config>* outConfigs) {
+    ALOGV("getDisplayConfigs");
+    if (mMockHal) {
+        return mMockHal->getDisplayConfigs(display, outConfigs);
+    }
+
+    // TODO assert display == 1, outConfigs != nullptr
+
+    outConfigs->resize(1);
+    (*outConfigs)[0] = 1;
+
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::getDisplayName(Display /*display*/, hidl_string* /*outName*/) {
+    ALOGV("getDisplayName");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::getDisplayType(Display /*display*/,
+                                               IComposerClient::DisplayType* outType) {
+    ALOGV("getDisplayType");
+    // TODO: This setting nothing on the output had no effect on initial trials. Is first display
+    // assumed to be physical?
+    *outType = static_cast<IComposerClient::DisplayType>(HWC2_DISPLAY_TYPE_PHYSICAL);
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::getDozeSupport(Display /*display*/, bool* /*outSupport*/) {
+    ALOGV("getDozeSupport");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::getHdrCapabilities(Display /*display*/,
+                                                   hidl_vec<V1_0::Hdr>* /*outTypes*/,
+                                                   float* /*outMaxLuminance*/,
+                                                   float* /*outMaxAverageLuminance*/,
+                                                   float* /*outMinLuminance*/) {
+    ALOGV("getHdrCapabilities");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setActiveConfig(Display display, Config config) {
+    ALOGV("setActiveConfig");
+    if (mMockHal) {
+        return mMockHal->setActiveConfig(display, config);
+    }
+    mCurrentConfig = config;
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setColorMode(Display /*display*/, V1_0::ColorMode /*mode*/) {
+    ALOGV("setColorMode");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setPowerMode(Display /*display*/,
+                                             V2_1::IComposerClient::PowerMode /*mode*/) {
+    ALOGV("setPowerMode");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setVsyncEnabled(Display /*display*/,
+                                                IComposerClient::Vsync enabled) {
+    mVsyncEnabled = (enabled == IComposerClient::Vsync::ENABLE);
+    ALOGV("setVsyncEnabled(%s)", mVsyncEnabled ? "ENABLE" : "DISABLE");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setColorTransform(Display /*display*/, const float* /*matrix*/,
+                                                  int32_t /*hint*/) {
+    ALOGV("setColorTransform");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setClientTarget(Display /*display*/, buffer_handle_t /*target*/,
+                                                int32_t /*acquireFence*/, int32_t /*dataspace*/,
+                                                const std::vector<hwc_rect_t>& /*damage*/) {
+    ALOGV("setClientTarget");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setOutputBuffer(Display /*display*/, buffer_handle_t /*buffer*/,
+                                                int32_t /*releaseFence*/) {
+    ALOGV("setOutputBuffer");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::validateDisplay(
+        Display /*display*/, std::vector<Layer>* /*outChangedLayers*/,
+        std::vector<IComposerClient::Composition>* /*outCompositionTypes*/,
+        uint32_t* /*outDisplayRequestMask*/, std::vector<Layer>* /*outRequestedLayers*/,
+        std::vector<uint32_t>* /*outRequestMasks*/) {
+    ALOGV("validateDisplay");
+    // TODO: Assume touching nothing means All Korrekt!
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::acceptDisplayChanges(Display /*display*/) {
+    ALOGV("acceptDisplayChanges");
+    // Didn't ask for changes because software is omnipotent.
+    return V2_1::Error::NONE;
+}
+
+bool layerZOrdering(const std::unique_ptr<FrameRect>& a, const std::unique_ptr<FrameRect>& b) {
+    return a->z <= b->z;
+}
+
+V2_1::Error FakeComposerClient::presentDisplay(Display /*display*/, int32_t* /*outPresentFence*/,
+                                               std::vector<Layer>* /*outLayers*/,
+                                               std::vector<int32_t>* /*outReleaseFences*/) {
+    ALOGV("presentDisplay");
+    // TODO Leaving layers and their fences out for now. Doing so
+    // means that we've already processed everything. Important to
+    // test that the fences are respected, though. (How?)
+
+    std::unique_ptr<Frame> newFrame(new Frame);
+    for (uint64_t layer = 0; layer < mLayers.size(); layer++) {
+        const LayerImpl& layerImpl = *mLayers[layer];
+
+        if (!layerImpl.mValid) continue;
+
+        auto rect = std::make_unique<FrameRect>(layer, layerImpl.mRenderState, layerImpl.mZ);
+        newFrame->rectangles.push_back(std::move(rect));
+    }
+    std::sort(newFrame->rectangles.begin(), newFrame->rectangles.end(), layerZOrdering);
+    {
+        Mutex::Autolock _l(mStateMutex);
+        mFrames.push_back(std::move(newFrame));
+        mFramesAvailable.broadcast();
+    }
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerCursorPosition(Display /*display*/, Layer /*layer*/,
+                                                       int32_t /*x*/, int32_t /*y*/) {
+    ALOGV("setLayerCursorPosition");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerBuffer(Display /*display*/, Layer layer,
+                                               buffer_handle_t buffer, int32_t acquireFence) {
+    ALOGV("setLayerBuffer");
+    LayerImpl& l = getLayerImpl(layer);
+    if (buffer != l.mRenderState.mBuffer) {
+        l.mRenderState.mSwapCount++; // TODO: Is setting to same value a swap or not?
+    }
+    l.mRenderState.mBuffer = buffer;
+    l.mRenderState.mAcquireFence = acquireFence;
+
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerSurfaceDamage(Display /*display*/, Layer /*layer*/,
+                                                      const std::vector<hwc_rect_t>& /*damage*/) {
+    ALOGV("setLayerSurfaceDamage");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerBlendMode(Display /*display*/, Layer layer, int32_t mode) {
+    ALOGV("setLayerBlendMode");
+    getLayerImpl(layer).mRenderState.mBlendMode = static_cast<hwc2_blend_mode_t>(mode);
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerColor(Display /*display*/, Layer layer,
+                                              IComposerClient::Color color) {
+    ALOGV("setLayerColor");
+    getLayerImpl(layer).mRenderState.mLayerColor.r = color.r;
+    getLayerImpl(layer).mRenderState.mLayerColor.g = color.g;
+    getLayerImpl(layer).mRenderState.mLayerColor.b = color.b;
+    getLayerImpl(layer).mRenderState.mLayerColor.a = color.a;
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerCompositionType(Display /*display*/, Layer /*layer*/,
+                                                        int32_t /*type*/) {
+    ALOGV("setLayerCompositionType");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerDataspace(Display /*display*/, Layer /*layer*/,
+                                                  int32_t /*dataspace*/) {
+    ALOGV("setLayerDataspace");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerDisplayFrame(Display /*display*/, Layer layer,
+                                                     const hwc_rect_t& frame) {
+    ALOGV("setLayerDisplayFrame (%d, %d, %d, %d)", frame.left, frame.top, frame.right,
+          frame.bottom);
+    getLayerImpl(layer).mRenderState.mDisplayFrame = frame;
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerPlaneAlpha(Display /*display*/, Layer layer, float alpha) {
+    ALOGV("setLayerPlaneAlpha");
+    getLayerImpl(layer).mRenderState.mPlaneAlpha = alpha;
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerSidebandStream(Display /*display*/, Layer /*layer*/,
+                                                       buffer_handle_t /*stream*/) {
+    ALOGV("setLayerSidebandStream");
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerSourceCrop(Display /*display*/, Layer layer,
+                                                   const hwc_frect_t& crop) {
+    ALOGV("setLayerSourceCrop");
+    getLayerImpl(layer).mRenderState.mSourceCrop = crop;
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerTransform(Display /*display*/, Layer layer,
+                                                  int32_t transform) {
+    ALOGV("setLayerTransform");
+    getLayerImpl(layer).mRenderState.mTransform = static_cast<hwc_transform_t>(transform);
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerVisibleRegion(Display /*display*/, Layer layer,
+                                                      const std::vector<hwc_rect_t>& visible) {
+    ALOGV("setLayerVisibleRegion");
+    getLayerImpl(layer).mRenderState.mVisibleRegion = visible;
+    return V2_1::Error::NONE;
+}
+
+V2_1::Error FakeComposerClient::setLayerZOrder(Display /*display*/, Layer layer, uint32_t z) {
+    ALOGV("setLayerZOrder");
+    getLayerImpl(layer).mZ = z;
+    return V2_1::Error::NONE;
+}
+
+// Composer 2.2
+V2_1::Error FakeComposerClient::getPerFrameMetadataKeys(
+        Display /*display*/, std::vector<V2_2::IComposerClient::PerFrameMetadataKey>* /*outKeys*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::setLayerPerFrameMetadata(
+        Display /*display*/, Layer /*layer*/,
+        const std::vector<V2_2::IComposerClient::PerFrameMetadata>& /*metadata*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getReadbackBufferAttributes(
+        Display /*display*/, graphics::common::V1_1::PixelFormat* /*outFormat*/,
+        graphics::common::V1_1::Dataspace* /*outDataspace*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::setReadbackBuffer(Display /*display*/,
+                                                  const native_handle_t* /*bufferHandle*/,
+                                                  android::base::unique_fd /*fenceFd*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getReadbackBufferFence(Display /*display*/,
+                                                       android::base::unique_fd* /*outFenceFd*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::createVirtualDisplay_2_2(
+        uint32_t /*width*/, uint32_t /*height*/, graphics::common::V1_1::PixelFormat* /*format*/,
+        Display* /*outDisplay*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+V2_1::Error FakeComposerClient::getClientTargetSupport_2_2(
+        Display /*display*/, uint32_t /*width*/, uint32_t /*height*/,
+        graphics::common::V1_1::PixelFormat /*format*/,
+        graphics::common::V1_1::Dataspace /*dataspace*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::setPowerMode_2_2(Display /*display*/,
+                                                 V2_2::IComposerClient::PowerMode /*mode*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::setLayerFloatColor(Display /*display*/, Layer /*layer*/,
+                                                   V2_2::IComposerClient::FloatColor /*color*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getColorModes_2_2(
+        Display /*display*/, hidl_vec<graphics::common::V1_1::ColorMode>* /*outModes*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getRenderIntents(
+        Display /*display*/, graphics::common::V1_1::ColorMode /*mode*/,
+        std::vector<graphics::common::V1_1::RenderIntent>* /*outIntents*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::setColorMode_2_2(Display /*display*/,
+                                                 graphics::common::V1_1::ColorMode /*mode*/,
+                                                 graphics::common::V1_1::RenderIntent /*intent*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+std::array<float, 16> FakeComposerClient::getDataspaceSaturationMatrix(
+        graphics::common::V1_1::Dataspace /*dataspace*/) {
+    return {};
+}
+
+// Composer 2.3
+V2_1::Error FakeComposerClient::getPerFrameMetadataKeys_2_3(
+        Display /*display*/, std::vector<V2_3::IComposerClient::PerFrameMetadataKey>* /*outKeys*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::setColorMode_2_3(Display /*display*/,
+                                                 graphics::common::V1_2::ColorMode /*mode*/,
+                                                 graphics::common::V1_1::RenderIntent /*intent*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getRenderIntents_2_3(
+        Display /*display*/, graphics::common::V1_2::ColorMode /*mode*/,
+        std::vector<graphics::common::V1_1::RenderIntent>* /*outIntents*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getColorModes_2_3(
+        Display /*display*/, hidl_vec<graphics::common::V1_2::ColorMode>* /*outModes*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getClientTargetSupport_2_3(
+        Display /*display*/, uint32_t /*width*/, uint32_t /*height*/,
+        graphics::common::V1_2::PixelFormat /*format*/,
+        graphics::common::V1_2::Dataspace /*dataspace*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getReadbackBufferAttributes_2_3(
+        Display /*display*/, graphics::common::V1_2::PixelFormat* /*outFormat*/,
+        graphics::common::V1_2::Dataspace* /*outDataspace*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getHdrCapabilities_2_3(
+        Display /*display*/, hidl_vec<graphics::common::V1_2::Hdr>* /*outTypes*/,
+        float* /*outMaxLuminance*/, float* /*outMaxAverageLuminance*/, float* /*outMinLuminance*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::setLayerPerFrameMetadata_2_3(
+        Display /*display*/, Layer /*layer*/,
+        const std::vector<V2_3::IComposerClient::PerFrameMetadata>& /*metadata*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getDisplayIdentificationData(Display /*display*/,
+                                                             uint8_t* /*outPort*/,
+                                                             std::vector<uint8_t>* /*outData*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::setLayerColorTransform(Display /*display*/, Layer /*layer*/,
+                                                       const float* /*matrix*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getDisplayedContentSamplingAttributes(
+        uint64_t /*display*/, graphics::common::V1_2::PixelFormat& /*format*/,
+        graphics::common::V1_2::Dataspace& /*dataspace*/,
+        hidl_bitfield<V2_3::IComposerClient::FormatColorComponent>& /*componentMask*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::setDisplayedContentSamplingEnabled(
+        uint64_t /*display*/, V2_3::IComposerClient::DisplayedContentSampling /*enable*/,
+        hidl_bitfield<V2_3::IComposerClient::FormatColorComponent> /*componentMask*/,
+        uint64_t /*maxFrames*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getDisplayedContentSample(
+        uint64_t /*display*/, uint64_t /*maxFrames*/, uint64_t /*timestamp*/,
+        uint64_t& /*frameCount*/, hidl_vec<uint64_t>& /*sampleComponent0*/,
+        hidl_vec<uint64_t>& /*sampleComponent1*/, hidl_vec<uint64_t>& /*sampleComponent2*/,
+        hidl_vec<uint64_t>& /*sampleComponent3*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getDisplayCapabilities(
+        Display /*display*/,
+        std::vector<V2_3::IComposerClient::DisplayCapability>* /*outCapabilities*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::setLayerPerFrameMetadataBlobs(
+        Display /*display*/, Layer /*layer*/,
+        std::vector<V2_3::IComposerClient::PerFrameMetadataBlob>& /*blobs*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::getDisplayBrightnessSupport(Display /*display*/,
+                                                            bool* /*outSupport*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+V2_1::Error FakeComposerClient::setDisplayBrightness(Display /*display*/, float /*brightness*/) {
+    return V2_1::Error::UNSUPPORTED;
+}
+
+// Composer 2.4
+void FakeComposerClient::registerEventCallback_2_4(EventCallback_2_4* callback) {
+    ALOGV("registerEventCallback_2_4");
+    LOG_FATAL_IF(mEventCallback != nullptr, "already registered using registerEventCallback");
+
+    mEventCallback_2_4 = callback;
+    if (mEventCallback_2_4) {
+        mEventCallback_2_4->onHotplug(PRIMARY_DISPLAY, IComposerCallback::Connection::CONNECTED);
+    }
+}
+
+void FakeComposerClient::unregisterEventCallback_2_4() {
+    ALOGV("unregisterEventCallback_2_4");
+    mEventCallback_2_4 = nullptr;
+}
+
+V2_4::Error FakeComposerClient::getDisplayCapabilities_2_4(
+        Display /*display*/,
+        std::vector<V2_4::IComposerClient::DisplayCapability>* /*outCapabilities*/) {
+    return V2_4::Error::UNSUPPORTED;
+}
+
+V2_4::Error FakeComposerClient::getDisplayConnectionType(
+        Display /*display*/, V2_4::IComposerClient::DisplayConnectionType* /*outType*/) {
+    return V2_4::Error::UNSUPPORTED;
+}
+
+V2_4::Error FakeComposerClient::getDisplayAttribute_2_4(Display display, Config config,
+                                                        IComposerClient::Attribute attribute,
+                                                        int32_t* outValue) {
     ALOGV("getDisplayAttribute (%d, %d, %d, %p)", static_cast<int>(display),
           static_cast<int>(config), static_cast<int>(attribute), outValue);
+    if (mMockHal) {
+        return mMockHal->getDisplayAttribute_2_4(display, config, attribute, outValue);
+    }
 
     // TODO: SOOO much fun to be had with these alone
     switch (attribute) {
@@ -276,233 +738,32 @@
     return Error::NONE;
 }
 
-Error FakeComposerClient::getDisplayConfigs(Display /*display*/, hidl_vec<Config>* outConfigs) {
-    ALOGV("getDisplayConfigs");
-    // TODO assert display == 1, outConfigs != nullptr
-
-    outConfigs->resize(1);
-    (*outConfigs)[0] = 1;
-
-    return Error::NONE;
-}
-
-Error FakeComposerClient::getDisplayName(Display /*display*/, hidl_string* /*outName*/) {
-    ALOGV("getDisplayName");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::getDisplayType(Display /*display*/,
-                                         IComposerClient::DisplayType* outType) {
-    ALOGV("getDisplayType");
-    // TODO: This setting nothing on the output had no effect on initial trials. Is first display
-    // assumed to be physical?
-    *outType = static_cast<IComposerClient::DisplayType>(HWC2_DISPLAY_TYPE_PHYSICAL);
-    return Error::NONE;
-}
-
-Error FakeComposerClient::getDozeSupport(Display /*display*/, bool* /*outSupport*/) {
-    ALOGV("getDozeSupport");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::getHdrCapabilities(Display /*display*/, hidl_vec<Hdr>* /*outTypes*/,
-                                             float* /*outMaxLuminance*/,
-                                             float* /*outMaxAverageLuminance*/,
-                                             float* /*outMinLuminance*/) {
-    ALOGV("getHdrCapabilities");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setActiveConfig(Display /*display*/, Config config) {
-    ALOGV("setActiveConfig");
-    mCurrentConfig = config;
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setColorMode(Display /*display*/, ColorMode /*mode*/) {
-    ALOGV("setColorMode");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setPowerMode(Display /*display*/, IComposerClient::PowerMode /*mode*/) {
-    ALOGV("setPowerMode");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setVsyncEnabled(Display /*display*/, IComposerClient::Vsync enabled) {
-    mVsyncEnabled = (enabled == IComposerClient::Vsync::ENABLE);
-    ALOGV("setVsyncEnabled(%s)", mVsyncEnabled ? "ENABLE" : "DISABLE");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setColorTransform(Display /*display*/, const float* /*matrix*/,
-                                            int32_t /*hint*/) {
-    ALOGV("setColorTransform");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setClientTarget(Display /*display*/, buffer_handle_t /*target*/,
-                                          int32_t /*acquireFence*/, int32_t /*dataspace*/,
-                                          const std::vector<hwc_rect_t>& /*damage*/) {
-    ALOGV("setClientTarget");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setOutputBuffer(Display /*display*/, buffer_handle_t /*buffer*/,
-                                          int32_t /*releaseFence*/) {
-    ALOGV("setOutputBuffer");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::validateDisplay(
-        Display /*display*/, std::vector<Layer>* /*outChangedLayers*/,
-        std::vector<IComposerClient::Composition>* /*outCompositionTypes*/,
-        uint32_t* /*outDisplayRequestMask*/, std::vector<Layer>* /*outRequestedLayers*/,
-        std::vector<uint32_t>* /*outRequestMasks*/) {
-    ALOGV("validateDisplay");
-    // TODO: Assume touching nothing means All Korrekt!
-    return Error::NONE;
-}
-
-Error FakeComposerClient::acceptDisplayChanges(Display /*display*/) {
-    ALOGV("acceptDisplayChanges");
-    // Didn't ask for changes because software is omnipotent.
-    return Error::NONE;
-}
-
-bool layerZOrdering(const std::unique_ptr<FrameRect>& a, const std::unique_ptr<FrameRect>& b) {
-    return a->z <= b->z;
-}
-
-Error FakeComposerClient::presentDisplay(Display /*display*/, int32_t* /*outPresentFence*/,
-                                         std::vector<Layer>* /*outLayers*/,
-                                         std::vector<int32_t>* /*outReleaseFences*/) {
-    ALOGV("presentDisplay");
-    // TODO Leaving layers and their fences out for now. Doing so
-    // means that we've already processed everything. Important to
-    // test that the fences are respected, though. (How?)
-
-    std::unique_ptr<Frame> newFrame(new Frame);
-    for (uint64_t layer = 0; layer < mLayers.size(); layer++) {
-        const LayerImpl& layerImpl = *mLayers[layer];
-
-        if (!layerImpl.mValid) continue;
-
-        auto rect = std::make_unique<FrameRect>(layer, layerImpl.mRenderState, layerImpl.mZ);
-        newFrame->rectangles.push_back(std::move(rect));
+V2_4::Error FakeComposerClient::getDisplayVsyncPeriod(Display display,
+                                                      V2_4::VsyncPeriodNanos* outVsyncPeriod) {
+    ALOGV("getDisplayVsyncPeriod");
+    if (mMockHal) {
+        return mMockHal->getDisplayVsyncPeriod(display, outVsyncPeriod);
     }
-    std::sort(newFrame->rectangles.begin(), newFrame->rectangles.end(), layerZOrdering);
-    {
-        Mutex::Autolock _l(mStateMutex);
-        mFrames.push_back(std::move(newFrame));
-        mFramesAvailable.broadcast();
+
+    return V2_4::Error::UNSUPPORTED;
+}
+
+V2_4::Error FakeComposerClient::setActiveConfigWithConstraints(
+        Display display, Config config,
+        const V2_4::IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
+        VsyncPeriodChangeTimeline* timeline) {
+    ALOGV("setActiveConfigWithConstraints");
+    if (mMockHal) {
+        return mMockHal->setActiveConfigWithConstraints(display, config,
+                                                        vsyncPeriodChangeConstraints, timeline);
     }
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerCursorPosition(Display /*display*/, Layer /*layer*/,
-                                                 int32_t /*x*/, int32_t /*y*/) {
-    ALOGV("setLayerCursorPosition");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerBuffer(Display /*display*/, Layer layer, buffer_handle_t buffer,
-                                         int32_t acquireFence) {
-    ALOGV("setLayerBuffer");
-    LayerImpl& l = getLayerImpl(layer);
-    if (buffer != l.mRenderState.mBuffer) {
-        l.mRenderState.mSwapCount++; // TODO: Is setting to same value a swap or not?
-    }
-    l.mRenderState.mBuffer = buffer;
-    l.mRenderState.mAcquireFence = acquireFence;
-
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerSurfaceDamage(Display /*display*/, Layer /*layer*/,
-                                                const std::vector<hwc_rect_t>& /*damage*/) {
-    ALOGV("setLayerSurfaceDamage");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerBlendMode(Display /*display*/, Layer layer, int32_t mode) {
-    ALOGV("setLayerBlendMode");
-    getLayerImpl(layer).mRenderState.mBlendMode = static_cast<hwc2_blend_mode_t>(mode);
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerColor(Display /*display*/, Layer layer,
-                                        IComposerClient::Color color) {
-    ALOGV("setLayerColor");
-    getLayerImpl(layer).mRenderState.mLayerColor.r = color.r;
-    getLayerImpl(layer).mRenderState.mLayerColor.g = color.g;
-    getLayerImpl(layer).mRenderState.mLayerColor.b = color.b;
-    getLayerImpl(layer).mRenderState.mLayerColor.a = color.a;
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerCompositionType(Display /*display*/, Layer /*layer*/,
-                                                  int32_t /*type*/) {
-    ALOGV("setLayerCompositionType");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerDataspace(Display /*display*/, Layer /*layer*/,
-                                            int32_t /*dataspace*/) {
-    ALOGV("setLayerDataspace");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerDisplayFrame(Display /*display*/, Layer layer,
-                                               const hwc_rect_t& frame) {
-    ALOGV("setLayerDisplayFrame (%d, %d, %d, %d)", frame.left, frame.top, frame.right,
-          frame.bottom);
-    getLayerImpl(layer).mRenderState.mDisplayFrame = frame;
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerPlaneAlpha(Display /*display*/, Layer layer, float alpha) {
-    ALOGV("setLayerPlaneAlpha");
-    getLayerImpl(layer).mRenderState.mPlaneAlpha = alpha;
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerSidebandStream(Display /*display*/, Layer /*layer*/,
-                                                 buffer_handle_t /*stream*/) {
-    ALOGV("setLayerSidebandStream");
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerSourceCrop(Display /*display*/, Layer layer,
-                                             const hwc_frect_t& crop) {
-    ALOGV("setLayerSourceCrop");
-    getLayerImpl(layer).mRenderState.mSourceCrop = crop;
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerTransform(Display /*display*/, Layer layer, int32_t transform) {
-    ALOGV("setLayerTransform");
-    getLayerImpl(layer).mRenderState.mTransform = static_cast<hwc_transform_t>(transform);
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerVisibleRegion(Display /*display*/, Layer layer,
-                                                const std::vector<hwc_rect_t>& visible) {
-    ALOGV("setLayerVisibleRegion");
-    getLayerImpl(layer).mRenderState.mVisibleRegion = visible;
-    return Error::NONE;
-}
-
-Error FakeComposerClient::setLayerZOrder(Display /*display*/, Layer layer, uint32_t z) {
-    ALOGV("setLayerZOrder");
-    getLayerImpl(layer).mZ = z;
-    return Error::NONE;
+    return V2_4::Error::UNSUPPORTED;
 }
 
 //////////////////////////////////////////////////////////////////
 
 void FakeComposerClient::requestVSync(uint64_t vsyncTime) {
-    if (mEventCallback) {
+    if (mEventCallback || mEventCallback_2_4) {
         uint64_t timestamp = vsyncTime;
         ALOGV("Vsync");
         if (timestamp == 0) {
@@ -512,8 +773,10 @@
         }
         if (mSurfaceComposer != nullptr) {
             mSurfaceComposer->injectVSync(timestamp);
-        } else {
+        } else if (mEventCallback) {
             mEventCallback->onVsync(PRIMARY_DISPLAY, timestamp);
+        } else {
+            mEventCallback_2_4->onVsync_2_4(PRIMARY_DISPLAY, timestamp, 16'666'666);
         }
     }
 }
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h
index d115d79..2a08b9b 100644
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h
+++ b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h
@@ -19,10 +19,15 @@
 #define HWC2_USE_CPP11
 #define HWC2_INCLUDE_STRINGIFICATION
 #include <composer-hal/2.1/ComposerClient.h>
+#include <composer-hal/2.2/ComposerClient.h>
+#include <composer-hal/2.3/ComposerClient.h>
+#include <composer-hal/2.4/ComposerClient.h>
 #undef HWC2_USE_CPP11
 #undef HWC2_INCLUDE_STRINGIFICATION
 #include "RenderState.h"
 
+#include "MockComposerHal.h"
+
 // Needed for display type/ID enums
 #include <hardware/hwcomposer_defs.h>
 
@@ -30,8 +35,10 @@
 
 #include <chrono>
 
-using namespace android::hardware::graphics::composer::V2_1;
-using namespace android::hardware::graphics::composer::V2_1::hal;
+using namespace android::hardware::graphics::common;
+using namespace android::hardware::graphics::composer;
+using namespace android::hardware::graphics::composer::V2_4;
+using namespace android::hardware::graphics::composer::V2_4::hal;
 using namespace android::hardware;
 using namespace std::chrono_literals;
 
@@ -46,7 +53,6 @@
 } // namespace android
 
 namespace sftest {
-
 // NOTE: The ID's need to be exactly these. VR composer and parts of
 // the SurfaceFlinger assume the display IDs to have these values
 // despite the enum being documented as a display type.
@@ -59,6 +65,8 @@
     FakeComposerClient();
     virtual ~FakeComposerClient();
 
+    void setMockHal(MockComposerHal* mockHal) { mMockHal = mockHal; }
+
     bool hasCapability(hwc2_capability_t capability) override;
 
     std::string dumpDebugInfo() override;
@@ -66,59 +74,178 @@
     void unregisterEventCallback() override;
 
     uint32_t getMaxVirtualDisplayCount() override;
-    Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format,
-                               Display* outDisplay) override;
-    Error destroyVirtualDisplay(Display display) override;
-    Error createLayer(Display display, Layer* outLayer) override;
-    Error destroyLayer(Display display, Layer layer) override;
+    V2_1::Error createVirtualDisplay(uint32_t width, uint32_t height, V1_0::PixelFormat* format,
+                                     Display* outDisplay) override;
+    V2_1::Error destroyVirtualDisplay(Display display) override;
+    V2_1::Error createLayer(Display display, Layer* outLayer) override;
+    V2_1::Error destroyLayer(Display display, Layer layer) override;
 
-    Error getActiveConfig(Display display, Config* outConfig) override;
-    Error getClientTargetSupport(Display display, uint32_t width, uint32_t height,
-                                 PixelFormat format, Dataspace dataspace) override;
-    Error getColorModes(Display display, hidl_vec<ColorMode>* outModes) override;
-    Error getDisplayAttribute(Display display, Config config, IComposerClient::Attribute attribute,
-                              int32_t* outValue) override;
-    Error getDisplayConfigs(Display display, hidl_vec<Config>* outConfigs) override;
-    Error getDisplayName(Display display, hidl_string* outName) override;
-    Error getDisplayType(Display display, IComposerClient::DisplayType* outType) override;
-    Error getDozeSupport(Display display, bool* outSupport) override;
-    Error getHdrCapabilities(Display display, hidl_vec<Hdr>* outTypes, float* outMaxLuminance,
-                             float* outMaxAverageLuminance, float* outMinLuminance) override;
+    V2_1::Error getActiveConfig(Display display, Config* outConfig) override;
+    V2_1::Error getClientTargetSupport(Display display, uint32_t width, uint32_t height,
+                                       V1_0::PixelFormat format,
+                                       V1_0::Dataspace dataspace) override;
+    V2_1::Error getColorModes(Display display, hidl_vec<V1_0::ColorMode>* outModes) override;
+    V2_1::Error getDisplayAttribute(Display display, Config config,
+                                    V2_1::IComposerClient::Attribute attribute,
+                                    int32_t* outValue) override;
+    V2_1::Error getDisplayConfigs(Display display, hidl_vec<Config>* outConfigs) override;
+    V2_1::Error getDisplayName(Display display, hidl_string* outName) override;
+    V2_1::Error getDisplayType(Display display, IComposerClient::DisplayType* outType) override;
+    V2_1::Error getDozeSupport(Display display, bool* outSupport) override;
+    V2_1::Error getHdrCapabilities(Display display, hidl_vec<V1_0::Hdr>* outTypes,
+                                   float* outMaxLuminance, float* outMaxAverageLuminance,
+                                   float* outMinLuminance) override;
 
-    Error setActiveConfig(Display display, Config config) override;
-    Error setColorMode(Display display, ColorMode mode) override;
-    Error setPowerMode(Display display, IComposerClient::PowerMode mode) override;
-    Error setVsyncEnabled(Display display, IComposerClient::Vsync enabled) override;
+    V2_1::Error setActiveConfig(Display display, Config config) override;
+    V2_1::Error setColorMode(Display display, V1_0::ColorMode mode) override;
+    V2_1::Error setPowerMode(Display display, V2_1::IComposerClient::PowerMode mode) override;
+    V2_1::Error setVsyncEnabled(Display display, IComposerClient::Vsync enabled) override;
 
-    Error setColorTransform(Display display, const float* matrix, int32_t hint) override;
-    Error setClientTarget(Display display, buffer_handle_t target, int32_t acquireFence,
-                          int32_t dataspace, const std::vector<hwc_rect_t>& damage) override;
-    Error setOutputBuffer(Display display, buffer_handle_t buffer, int32_t releaseFence) override;
-    Error validateDisplay(Display display, std::vector<Layer>* outChangedLayers,
-                          std::vector<IComposerClient::Composition>* outCompositionTypes,
-                          uint32_t* outDisplayRequestMask, std::vector<Layer>* outRequestedLayers,
-                          std::vector<uint32_t>* outRequestMasks) override;
-    Error acceptDisplayChanges(Display display) override;
-    Error presentDisplay(Display display, int32_t* outPresentFence, std::vector<Layer>* outLayers,
-                         std::vector<int32_t>* outReleaseFences) override;
+    V2_1::Error setColorTransform(Display display, const float* matrix, int32_t hint) override;
+    V2_1::Error setClientTarget(Display display, buffer_handle_t target, int32_t acquireFence,
+                                int32_t dataspace, const std::vector<hwc_rect_t>& damage) override;
+    V2_1::Error setOutputBuffer(Display display, buffer_handle_t buffer,
+                                int32_t releaseFence) override;
+    V2_1::Error validateDisplay(Display display, std::vector<Layer>* outChangedLayers,
+                                std::vector<IComposerClient::Composition>* outCompositionTypes,
+                                uint32_t* outDisplayRequestMask,
+                                std::vector<Layer>* outRequestedLayers,
+                                std::vector<uint32_t>* outRequestMasks) override;
+    V2_1::Error acceptDisplayChanges(Display display) override;
+    V2_1::Error presentDisplay(Display display, int32_t* outPresentFence,
+                               std::vector<Layer>* outLayers,
+                               std::vector<int32_t>* outReleaseFences) override;
 
-    Error setLayerCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override;
-    Error setLayerBuffer(Display display, Layer layer, buffer_handle_t buffer,
-                         int32_t acquireFence) override;
-    Error setLayerSurfaceDamage(Display display, Layer layer,
-                                const std::vector<hwc_rect_t>& damage) override;
-    Error setLayerBlendMode(Display display, Layer layer, int32_t mode) override;
-    Error setLayerColor(Display display, Layer layer, IComposerClient::Color color) override;
-    Error setLayerCompositionType(Display display, Layer layer, int32_t type) override;
-    Error setLayerDataspace(Display display, Layer layer, int32_t dataspace) override;
-    Error setLayerDisplayFrame(Display display, Layer layer, const hwc_rect_t& frame) override;
-    Error setLayerPlaneAlpha(Display display, Layer layer, float alpha) override;
-    Error setLayerSidebandStream(Display display, Layer layer, buffer_handle_t stream) override;
-    Error setLayerSourceCrop(Display display, Layer layer, const hwc_frect_t& crop) override;
-    Error setLayerTransform(Display display, Layer layer, int32_t transform) override;
-    Error setLayerVisibleRegion(Display display, Layer layer,
-                                const std::vector<hwc_rect_t>& visible) override;
-    Error setLayerZOrder(Display display, Layer layer, uint32_t z) override;
+    V2_1::Error setLayerCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override;
+    V2_1::Error setLayerBuffer(Display display, Layer layer, buffer_handle_t buffer,
+                               int32_t acquireFence) override;
+    V2_1::Error setLayerSurfaceDamage(Display display, Layer layer,
+                                      const std::vector<hwc_rect_t>& damage) override;
+    V2_1::Error setLayerBlendMode(Display display, Layer layer, int32_t mode) override;
+    V2_1::Error setLayerColor(Display display, Layer layer, IComposerClient::Color color) override;
+    V2_1::Error setLayerCompositionType(Display display, Layer layer, int32_t type) override;
+    V2_1::Error setLayerDataspace(Display display, Layer layer, int32_t dataspace) override;
+    V2_1::Error setLayerDisplayFrame(Display display, Layer layer,
+                                     const hwc_rect_t& frame) override;
+    V2_1::Error setLayerPlaneAlpha(Display display, Layer layer, float alpha) override;
+    V2_1::Error setLayerSidebandStream(Display display, Layer layer,
+                                       buffer_handle_t stream) override;
+    V2_1::Error setLayerSourceCrop(Display display, Layer layer, const hwc_frect_t& crop) override;
+    V2_1::Error setLayerTransform(Display display, Layer layer, int32_t transform) override;
+    V2_1::Error setLayerVisibleRegion(Display display, Layer layer,
+                                      const std::vector<hwc_rect_t>& visible) override;
+    V2_1::Error setLayerZOrder(Display display, Layer layer, uint32_t z) override;
+
+    // Composer 2.2
+    V2_1::Error getPerFrameMetadataKeys(
+            Display display,
+            std::vector<V2_2::IComposerClient::PerFrameMetadataKey>* outKeys) override;
+    V2_1::Error setLayerPerFrameMetadata(
+            Display display, Layer layer,
+            const std::vector<V2_2::IComposerClient::PerFrameMetadata>& metadata) override;
+
+    V2_1::Error getReadbackBufferAttributes(
+            Display display, graphics::common::V1_1::PixelFormat* outFormat,
+            graphics::common::V1_1::Dataspace* outDataspace) override;
+    V2_1::Error setReadbackBuffer(Display display, const native_handle_t* bufferHandle,
+                                  android::base::unique_fd fenceFd) override;
+    V2_1::Error getReadbackBufferFence(Display display,
+                                       android::base::unique_fd* outFenceFd) override;
+    V2_1::Error createVirtualDisplay_2_2(uint32_t width, uint32_t height,
+                                         graphics::common::V1_1::PixelFormat* format,
+                                         Display* outDisplay) override;
+    V2_1::Error getClientTargetSupport_2_2(Display display, uint32_t width, uint32_t height,
+                                           graphics::common::V1_1::PixelFormat format,
+                                           graphics::common::V1_1::Dataspace dataspace) override;
+    V2_1::Error setPowerMode_2_2(Display display, V2_2::IComposerClient::PowerMode mode) override;
+
+    V2_1::Error setLayerFloatColor(Display display, Layer layer,
+                                   V2_2::IComposerClient::FloatColor color) override;
+
+    V2_1::Error getColorModes_2_2(Display display,
+                                  hidl_vec<graphics::common::V1_1::ColorMode>* outModes) override;
+    V2_1::Error getRenderIntents(
+            Display display, graphics::common::V1_1::ColorMode mode,
+            std::vector<graphics::common::V1_1::RenderIntent>* outIntents) override;
+    V2_1::Error setColorMode_2_2(Display display, graphics::common::V1_1::ColorMode mode,
+                                 graphics::common::V1_1::RenderIntent intent) override;
+
+    std::array<float, 16> getDataspaceSaturationMatrix(
+            graphics::common::V1_1::Dataspace dataspace) override;
+
+    // Composer 2.3
+    V2_1::Error getPerFrameMetadataKeys_2_3(
+            Display display,
+            std::vector<V2_3::IComposerClient::PerFrameMetadataKey>* outKeys) override;
+
+    V2_1::Error setColorMode_2_3(Display display, graphics::common::V1_2::ColorMode mode,
+                                 graphics::common::V1_1::RenderIntent intent) override;
+
+    V2_1::Error getRenderIntents_2_3(
+            Display display, graphics::common::V1_2::ColorMode mode,
+            std::vector<graphics::common::V1_1::RenderIntent>* outIntents) override;
+
+    V2_1::Error getColorModes_2_3(Display display,
+                                  hidl_vec<graphics::common::V1_2::ColorMode>* outModes) override;
+
+    V2_1::Error getClientTargetSupport_2_3(Display display, uint32_t width, uint32_t height,
+                                           graphics::common::V1_2::PixelFormat format,
+                                           graphics::common::V1_2::Dataspace dataspace) override;
+    V2_1::Error getReadbackBufferAttributes_2_3(
+            Display display, graphics::common::V1_2::PixelFormat* outFormat,
+            graphics::common::V1_2::Dataspace* outDataspace) override;
+    V2_1::Error getHdrCapabilities_2_3(Display display,
+                                       hidl_vec<graphics::common::V1_2::Hdr>* outTypes,
+                                       float* outMaxLuminance, float* outMaxAverageLuminance,
+                                       float* outMinLuminance) override;
+    V2_1::Error setLayerPerFrameMetadata_2_3(
+            Display display, Layer layer,
+            const std::vector<V2_3::IComposerClient::PerFrameMetadata>& metadata) override;
+    V2_1::Error getDisplayIdentificationData(Display display, uint8_t* outPort,
+                                             std::vector<uint8_t>* outData) override;
+    V2_1::Error setLayerColorTransform(Display display, Layer layer, const float* matrix) override;
+    V2_1::Error getDisplayedContentSamplingAttributes(
+            uint64_t display, graphics::common::V1_2::PixelFormat& format,
+            graphics::common::V1_2::Dataspace& dataspace,
+            hidl_bitfield<V2_3::IComposerClient::FormatColorComponent>& componentMask) override;
+    V2_1::Error setDisplayedContentSamplingEnabled(
+            uint64_t display, V2_3::IComposerClient::DisplayedContentSampling enable,
+            hidl_bitfield<V2_3::IComposerClient::FormatColorComponent> componentMask,
+            uint64_t maxFrames) override;
+    V2_1::Error getDisplayedContentSample(uint64_t display, uint64_t maxFrames, uint64_t timestamp,
+                                          uint64_t& frameCount,
+                                          hidl_vec<uint64_t>& sampleComponent0,
+                                          hidl_vec<uint64_t>& sampleComponent1,
+                                          hidl_vec<uint64_t>& sampleComponent2,
+                                          hidl_vec<uint64_t>& sampleComponent3) override;
+    V2_1::Error getDisplayCapabilities(
+            Display display,
+            std::vector<V2_3::IComposerClient::DisplayCapability>* outCapabilities) override;
+    V2_1::Error setLayerPerFrameMetadataBlobs(
+            Display display, Layer layer,
+            std::vector<V2_3::IComposerClient::PerFrameMetadataBlob>& blobs) override;
+    V2_1::Error getDisplayBrightnessSupport(Display display, bool* outSupport) override;
+    V2_1::Error setDisplayBrightness(Display display, float brightness) override;
+
+    // Composer 2.4
+    void registerEventCallback_2_4(EventCallback_2_4* callback) override;
+
+    void unregisterEventCallback_2_4() override;
+
+    V2_4::Error getDisplayCapabilities_2_4(
+            Display display,
+            std::vector<V2_4::IComposerClient::DisplayCapability>* outCapabilities) override;
+    V2_4::Error getDisplayConnectionType(
+            Display display, V2_4::IComposerClient::DisplayConnectionType* outType) override;
+    V2_4::Error getDisplayAttribute_2_4(Display display, Config config,
+                                        IComposerClient::Attribute attribute,
+                                        int32_t* outValue) override;
+    V2_4::Error getDisplayVsyncPeriod(Display display,
+                                      V2_4::VsyncPeriodNanos* outVsyncPeriod) override;
+    V2_4::Error setActiveConfigWithConstraints(
+            Display display, Config config,
+            const V2_4::IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
+            VsyncPeriodChangeTimeline* outTimeline) override;
 
     void setClient(ComposerClient* client);
 
@@ -150,6 +277,7 @@
     LayerImpl& getLayerImpl(Layer handle);
 
     EventCallback* mEventCallback;
+    EventCallback_2_4* mEventCallback_2_4;
     Config mCurrentConfig;
     bool mVsyncEnabled;
     std::vector<std::unique_ptr<LayerImpl>> mLayers;
@@ -159,6 +287,8 @@
     android::sp<android::SurfaceComposerClient> mSurfaceComposer; // For VSync injections
     mutable android::Mutex mStateMutex;
     mutable android::Condition mFramesAvailable;
+
+    MockComposerHal* mMockHal = nullptr;
 };
 
 } // namespace sftest
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerService.cpp b/services/surfaceflinger/tests/fakehwc/FakeComposerService.cpp
index f70cbdb..f727bc4 100644
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerService.cpp
+++ b/services/surfaceflinger/tests/fakehwc/FakeComposerService.cpp
@@ -22,34 +22,150 @@
 #include "FakeComposerService.h"
 
 using namespace android::hardware;
+using namespace android::hardware::graphics::composer;
 
 namespace sftest {
 
-FakeComposerService::FakeComposerService(android::sp<ComposerClient>& client) : mClient(client) {}
+FakeComposerService_2_1::FakeComposerService_2_1(android::sp<ComposerClient>& client)
+      : mClient(client) {}
 
-FakeComposerService::~FakeComposerService() {
+FakeComposerService_2_1::~FakeComposerService_2_1() {
     ALOGI("Maybe killing client %p", mClient.get());
     // Rely on sp to kill the client.
 }
 
-Return<void> FakeComposerService::getCapabilities(getCapabilities_cb hidl_cb) {
+Return<void> FakeComposerService_2_1::getCapabilities(getCapabilities_cb hidl_cb) {
     ALOGI("FakeComposerService::getCapabilities");
     hidl_cb(hidl_vec<Capability>());
     return Void();
 }
 
-Return<void> FakeComposerService::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) {
+Return<void> FakeComposerService_2_1::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) {
     ALOGI("FakeComposerService::dumpDebugInfo");
     hidl_cb(hidl_string());
     return Void();
 }
 
-Return<void> FakeComposerService::createClient(createClient_cb hidl_cb) {
+Return<void> FakeComposerService_2_1::createClient(createClient_cb hidl_cb) {
     ALOGI("FakeComposerService::createClient %p", mClient.get());
     if (!mClient->init()) {
         LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
     }
-    hidl_cb(Error::NONE, mClient);
+    hidl_cb(V2_1::Error::NONE, mClient);
+    return Void();
+}
+
+FakeComposerService_2_2::FakeComposerService_2_2(android::sp<ComposerClient>& client)
+      : mClient(client) {}
+
+FakeComposerService_2_2::~FakeComposerService_2_2() {
+    ALOGI("Maybe killing client %p", mClient.get());
+    // Rely on sp to kill the client.
+}
+
+Return<void> FakeComposerService_2_2::getCapabilities(getCapabilities_cb hidl_cb) {
+    ALOGI("FakeComposerService::getCapabilities");
+    hidl_cb(hidl_vec<Capability>());
+    return Void();
+}
+
+Return<void> FakeComposerService_2_2::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) {
+    ALOGI("FakeComposerService::dumpDebugInfo");
+    hidl_cb(hidl_string());
+    return Void();
+}
+
+Return<void> FakeComposerService_2_2::createClient(createClient_cb hidl_cb) {
+    ALOGI("FakeComposerService::createClient %p", mClient.get());
+    if (!mClient->init()) {
+        LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
+    }
+    hidl_cb(V2_1::Error::NONE, mClient);
+    return Void();
+}
+
+FakeComposerService_2_3::FakeComposerService_2_3(android::sp<ComposerClient>& client)
+      : mClient(client) {}
+
+FakeComposerService_2_3::~FakeComposerService_2_3() {
+    ALOGI("Maybe killing client %p", mClient.get());
+    // Rely on sp to kill the client.
+}
+
+Return<void> FakeComposerService_2_3::getCapabilities(getCapabilities_cb hidl_cb) {
+    ALOGI("FakeComposerService::getCapabilities");
+    hidl_cb(hidl_vec<Capability>());
+    return Void();
+}
+
+Return<void> FakeComposerService_2_3::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) {
+    ALOGI("FakeComposerService::dumpDebugInfo");
+    hidl_cb(hidl_string());
+    return Void();
+}
+
+Return<void> FakeComposerService_2_3::createClient(createClient_cb hidl_cb) {
+    LOG_ALWAYS_FATAL("createClient called on FakeComposerService_2_3");
+    if (!mClient->init()) {
+        LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
+    }
+    hidl_cb(V2_1::Error::UNSUPPORTED, nullptr);
+    return Void();
+}
+
+Return<void> FakeComposerService_2_3::createClient_2_3(createClient_2_3_cb hidl_cb) {
+    ALOGI("FakeComposerService_2_3::createClient_2_3 %p", mClient.get());
+    if (!mClient->init()) {
+        LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
+    }
+    hidl_cb(V2_1::Error::NONE, mClient);
+    return Void();
+}
+
+FakeComposerService_2_4::FakeComposerService_2_4(android::sp<ComposerClient>& client)
+      : mClient(client) {}
+
+FakeComposerService_2_4::~FakeComposerService_2_4() {
+    ALOGI("Maybe killing client %p", mClient.get());
+    // Rely on sp to kill the client.
+}
+
+Return<void> FakeComposerService_2_4::getCapabilities(getCapabilities_cb hidl_cb) {
+    ALOGI("FakeComposerService::getCapabilities");
+    hidl_cb(hidl_vec<Capability>());
+    return Void();
+}
+
+Return<void> FakeComposerService_2_4::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) {
+    ALOGI("FakeComposerService::dumpDebugInfo");
+    hidl_cb(hidl_string());
+    return Void();
+}
+
+Return<void> FakeComposerService_2_4::createClient(createClient_cb hidl_cb) {
+    LOG_ALWAYS_FATAL("createClient called on FakeComposerService_2_4");
+    if (!mClient->init()) {
+        LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
+    }
+    hidl_cb(V2_1::Error::UNSUPPORTED, nullptr);
+    return Void();
+}
+
+Return<void> FakeComposerService_2_4::createClient_2_3(createClient_2_3_cb hidl_cb) {
+    LOG_ALWAYS_FATAL("createClient_2_3 called on FakeComposerService_2_4");
+    if (!mClient->init()) {
+        LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
+    }
+    hidl_cb(V2_1::Error::UNSUPPORTED, nullptr);
+    return Void();
+}
+
+Return<void> FakeComposerService_2_4::createClient_2_4(createClient_2_4_cb hidl_cb) {
+    ALOGI("FakeComposerService_2_4::createClient_2_4 %p", mClient.get());
+    if (!mClient->init()) {
+        LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
+    }
+    hidl_cb(V2_4::Error::NONE, mClient);
     return Void();
 }
 
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerService.h b/services/surfaceflinger/tests/fakehwc/FakeComposerService.h
index a3fb8a6..47f970f 100644
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerService.h
+++ b/services/surfaceflinger/tests/fakehwc/FakeComposerService.h
@@ -16,18 +16,24 @@
 
 #pragma once
 
+#include <android/hardware/graphics/composer/2.4/IComposer.h>
 #include <composer-hal/2.1/ComposerClient.h>
+#include <composer-hal/2.2/ComposerClient.h>
+#include <composer-hal/2.3/ComposerClient.h>
+#include <composer-hal/2.4/ComposerClient.h>
 
-using namespace android::hardware::graphics::composer::V2_1;
-using namespace android::hardware::graphics::composer::V2_1::hal;
 using android::hardware::Return;
 
+using ComposerClient = android::hardware::graphics::composer::V2_4::hal::ComposerClient;
+
 namespace sftest {
 
-class FakeComposerService : public IComposer {
+using IComposer_2_1 = android::hardware::graphics::composer::V2_1::IComposer;
+
+class FakeComposerService_2_1 : public IComposer_2_1 {
 public:
-    explicit FakeComposerService(android::sp<ComposerClient>& client);
-    virtual ~FakeComposerService();
+    explicit FakeComposerService_2_1(android::sp<ComposerClient>& client);
+    virtual ~FakeComposerService_2_1();
 
     Return<void> getCapabilities(getCapabilities_cb hidl_cb) override;
     Return<void> dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override;
@@ -37,4 +43,50 @@
     android::sp<ComposerClient> mClient;
 };
 
+using IComposer_2_2 = android::hardware::graphics::composer::V2_2::IComposer;
+class FakeComposerService_2_2 : public IComposer_2_2 {
+public:
+    explicit FakeComposerService_2_2(android::sp<ComposerClient>& client);
+    virtual ~FakeComposerService_2_2();
+
+    Return<void> getCapabilities(getCapabilities_cb hidl_cb) override;
+    Return<void> dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override;
+    Return<void> createClient(createClient_cb hidl_cb) override;
+
+private:
+    android::sp<ComposerClient> mClient;
+};
+
+using IComposer_2_3 = android::hardware::graphics::composer::V2_3::IComposer;
+class FakeComposerService_2_3 : public IComposer_2_3 {
+public:
+    explicit FakeComposerService_2_3(android::sp<ComposerClient>& client);
+    virtual ~FakeComposerService_2_3();
+
+    Return<void> getCapabilities(getCapabilities_cb hidl_cb) override;
+    Return<void> dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override;
+    Return<void> createClient(createClient_cb hidl_cb) override;
+    Return<void> createClient_2_3(createClient_2_3_cb hidl_cb) override;
+
+private:
+    android::sp<ComposerClient> mClient;
+};
+
+using IComposer_2_4 = android::hardware::graphics::composer::V2_4::IComposer;
+
+class FakeComposerService_2_4 : public IComposer_2_4 {
+public:
+    explicit FakeComposerService_2_4(android::sp<ComposerClient>& client);
+    virtual ~FakeComposerService_2_4();
+
+    Return<void> getCapabilities(getCapabilities_cb hidl_cb) override;
+    Return<void> dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override;
+    Return<void> createClient(createClient_cb hidl_cb) override;
+    Return<void> createClient_2_3(createClient_2_3_cb hidl_cb) override;
+    Return<void> createClient_2_4(createClient_2_4_cb hidl_cb) override;
+
+private:
+    android::sp<ComposerClient> mClient;
+};
+
 } // namespace sftest
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.h b/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.h
index 7d20d3c..09a2a89 100644
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.h
+++ b/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.h
@@ -108,9 +108,9 @@
         LOG_ALWAYS_FATAL_IF(android::NO_ERROR != apply());
         // Make sure that exactly one frame has been rendered.
         mComposer.waitUntilFrame(frameCount + 1);
-        LOG_ALWAYS_FATAL_IF(frameCount + 1 != mComposer.getFrameCount(),
-                            "Unexpected frame advance. Delta: %d",
-                            mComposer.getFrameCount() - frameCount);
+        //        LOG_ALWAYS_FATAL_IF(frameCount + 1 != mComposer.getFrameCount(),
+        //                            "Unexpected frame advance. Delta: %d",
+        //                            mComposer.getFrameCount() - frameCount);
     }
 
     FakeComposerClient& mComposer;
diff --git a/services/surfaceflinger/tests/fakehwc/MockComposerHal.h b/services/surfaceflinger/tests/fakehwc/MockComposerHal.h
new file mode 100644
index 0000000..5dc3778
--- /dev/null
+++ b/services/surfaceflinger/tests/fakehwc/MockComposerHal.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <composer-hal/2.4/ComposerClient.h>
+
+#include <gmock/gmock.h>
+
+using namespace android::hardware::graphics::common;
+using namespace android::hardware::graphics::composer;
+using namespace android::hardware::graphics::composer::V2_4;
+using namespace android::hardware::graphics::composer::V2_4::hal;
+using namespace android::hardware;
+using namespace std::chrono_literals;
+
+namespace sftest {
+
+// Mock class for ComposerHal. Implements only the functions used in the test.
+class MockComposerHal {
+public:
+    MOCK_METHOD2(getActiveConfig, V2_1::Error(Display, Config*));
+    MOCK_METHOD4(getDisplayAttribute_2_4,
+                 V2_4::Error(Display, Config, V2_4::IComposerClient::Attribute, int32_t*));
+    MOCK_METHOD2(getDisplayConfigs, V2_1::Error(Display, hidl_vec<Config>*));
+    MOCK_METHOD2(setActiveConfig, V2_1::Error(Display, Config));
+    MOCK_METHOD2(getDisplayVsyncPeriod, V2_4::Error(Display, V2_4::VsyncPeriodNanos*));
+    MOCK_METHOD4(setActiveConfigWithConstraints,
+                 V2_4::Error(Display, Config,
+                             const V2_4::IComposerClient::VsyncPeriodChangeConstraints&,
+                             VsyncPeriodChangeTimeline*));
+};
+
+} // namespace sftest
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
index 67faa57..09fdbdf 100644
--- a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
+++ b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
@@ -21,6 +21,7 @@
 #include "FakeComposerClient.h"
 #include "FakeComposerService.h"
 #include "FakeComposerUtils.h"
+#include "MockComposerHal.h"
 
 #include <gui/DisplayEventReceiver.h>
 #include <gui/ISurfaceComposer.h>
@@ -43,6 +44,7 @@
 #include <gtest/gtest.h>
 
 #include <limits>
+#include <thread>
 
 using namespace std::chrono_literals;
 
@@ -55,12 +57,14 @@
 
 // Mock test helpers
 using ::testing::_;
+using ::testing::AtLeast;
 using ::testing::DoAll;
 using ::testing::Invoke;
 using ::testing::Return;
 using ::testing::SetArgPointee;
 
 using Transaction = SurfaceComposerClient::Transaction;
+using Attribute = V2_4::IComposerClient::Attribute;
 
 ///////////////////////////////////////////////
 
@@ -121,307 +125,1229 @@
                           static_cast<int>(bottom));
 }
 
-////////////////////////////////////////////////
-
+///////////////////////////////////////////////
+template <typename FakeComposerService>
 class DisplayTest : public ::testing::Test {
-public:
-    class MockComposerClient : public FakeComposerClient {
-    public:
-        MOCK_METHOD4(getDisplayAttribute,
-                     Error(Display display, Config config, IComposerClient::Attribute attribute,
-                           int32_t* outValue));
-
-        // Re-routing to basic fake implementation
-        Error getDisplayAttributeFake(Display display, Config config,
-                                      IComposerClient::Attribute attribute, int32_t* outValue) {
-            return FakeComposerClient::getDisplayAttribute(display, config, attribute, outValue);
-        }
+protected:
+    struct TestConfig {
+        int32_t id;
+        int32_t w;
+        int32_t h;
+        int32_t vsyncPeriod;
+        int32_t group;
     };
 
-protected:
-    static int processDisplayEvents(int fd, int events, void* data);
+    static int processDisplayEvents(int /*fd*/, int /*events*/, void* data) {
+        auto self = static_cast<DisplayTest*>(data);
 
-    void SetUp() override;
-    void TearDown() override;
+        ssize_t n;
+        DisplayEventReceiver::Event buffer[1];
 
-    void waitForDisplayTransaction();
-    bool waitForHotplugEvent(PhysicalDisplayId displayId, bool connected);
-
-    sp<IComposer> mFakeService;
-    sp<SurfaceComposerClient> mComposerClient;
-
-    MockComposerClient* mMockComposer;
-
-    std::unique_ptr<DisplayEventReceiver> mReceiver;
-    sp<Looper> mLooper;;
-    std::deque<DisplayEventReceiver::Event> mReceivedDisplayEvents;
-};
-
-void DisplayTest::SetUp() {
-    // TODO: The mMockComposer should be a unique_ptr, but it needs to
-    // outlive the test class.  Currently ComposerClient only dies
-    // when the service is replaced. The Mock deletes itself when
-    // removeClient is called on it, which is ugly.  This can be
-    // changed if HIDL ServiceManager allows removing services or
-    // ComposerClient starts taking the ownership of the contained
-    // implementation class. Moving the fake class to the HWC2
-    // interface instead of the current Composer interface might also
-    // change the situation.
-    mMockComposer = new MockComposerClient;
-    sp<ComposerClient> client = new ComposerClient(mMockComposer);
-    mFakeService = new FakeComposerService(client);
-    ASSERT_EQ(android::OK, mFakeService->registerAsService("mock"));
-
-    android::hardware::ProcessState::self()->startThreadPool();
-    android::ProcessState::self()->startThreadPool();
-
-    // Primary display will be queried twice for all 5 attributes. One
-    // set of queries comes from the SurfaceFlinger proper an the
-    // other set from the VR composer.
-    // TODO: Is VR composer always present? Change to atLeast(5)?
-    EXPECT_CALL(*mMockComposer, getDisplayAttribute(PRIMARY_DISPLAY, 1, _, _))
-            .Times(2 * 5)
-            .WillRepeatedly(Invoke(mMockComposer, &MockComposerClient::getDisplayAttributeFake));
-
-    startSurfaceFlinger();
-
-    // Fake composer wants to enable VSync injection
-    mMockComposer->onSurfaceFlingerStart();
-
-    mComposerClient = new SurfaceComposerClient;
-    ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
-
-    mReceiver.reset(new DisplayEventReceiver());
-    mLooper = new Looper(false);
-    mLooper->addFd(mReceiver->getFd(), 0, ALOOPER_EVENT_INPUT, processDisplayEvents, this);
-}
-
-void DisplayTest::TearDown() {
-    mLooper = nullptr;
-    mReceiver = nullptr;
-
-    mComposerClient->dispose();
-    mComposerClient = nullptr;
-
-    // Fake composer needs to release SurfaceComposerClient before the stop.
-    mMockComposer->onSurfaceFlingerStop();
-    stopSurfaceFlinger();
-
-    mFakeService = nullptr;
-    // TODO: Currently deleted in FakeComposerClient::removeClient(). Devise better lifetime
-    // management.
-    mMockComposer = nullptr;
-}
-
-
-int DisplayTest::processDisplayEvents(int /*fd*/, int /*events*/, void* data) {
-    auto self = static_cast<DisplayTest*>(data);
-
-    ssize_t n;
-    DisplayEventReceiver::Event buffer[1];
-
-    while ((n = self->mReceiver->getEvents(buffer, 1)) > 0) {
-        for (int i=0 ; i<n ; i++) {
-            self->mReceivedDisplayEvents.push_back(buffer[i]);
+        while ((n = self->mReceiver->getEvents(buffer, 1)) > 0) {
+            for (int i = 0; i < n; i++) {
+                self->mReceivedDisplayEvents.push_back(buffer[i]);
+            }
         }
+        ALOGD_IF(n < 0, "Error reading events (%s)\n", strerror(-n));
+        return 1;
     }
-    ALOGD_IF(n < 0, "Error reading events (%s)\n", strerror(-n));
-    return 1;
-}
 
-void DisplayTest::waitForDisplayTransaction() {
-    // Both a refresh and a vsync event are needed to apply pending display
-    // transactions.
-    mMockComposer->refreshDisplay(EXTERNAL_DISPLAY);
-    mMockComposer->runVSyncAndWait();
+    Error getDisplayAttributeNoMock(Display display, Config config,
+                                    V2_4::IComposerClient::Attribute attribute, int32_t* outValue) {
+        mFakeComposerClient->setMockHal(nullptr);
+        auto ret =
+                mFakeComposerClient->getDisplayAttribute_2_4(display, config, attribute, outValue);
+        mFakeComposerClient->setMockHal(mMockComposer.get());
+        return ret;
+    }
 
-    // Extra vsync and wait to avoid a 10% flake due to a race.
-    mMockComposer->runVSyncAndWait();
-}
+    void setExpectationsForConfigs(Display display, std::vector<TestConfig> testConfigs,
+                                   Config activeConfig, V2_4::VsyncPeriodNanos defaultVsyncPeriod) {
+        std::vector<Config> configIds;
+        for (int i = 0; i < testConfigs.size(); i++) {
+            configIds.push_back(testConfigs[i].id);
 
-bool DisplayTest::waitForHotplugEvent(PhysicalDisplayId displayId, bool connected) {
-    int waitCount = 20;
-    while (waitCount--) {
-        while (!mReceivedDisplayEvents.empty()) {
-            auto event = mReceivedDisplayEvents.front();
-            mReceivedDisplayEvents.pop_front();
+            EXPECT_CALL(*mMockComposer,
+                        getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::WIDTH, _))
+                    .WillRepeatedly(DoAll(SetArgPointee<3>(testConfigs[i].w), Return(Error::NONE)));
+            EXPECT_CALL(*mMockComposer,
+                        getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::HEIGHT, _))
+                    .WillRepeatedly(DoAll(SetArgPointee<3>(testConfigs[i].h), Return(Error::NONE)));
+            EXPECT_CALL(*mMockComposer,
+                        getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::VSYNC_PERIOD,
+                                                _))
+                    .WillRepeatedly(DoAll(SetArgPointee<3>(testConfigs[i].vsyncPeriod),
+                                          Return(Error::NONE)));
+            EXPECT_CALL(*mMockComposer,
+                        getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::CONFIG_GROUP,
+                                                _))
+                    .WillRepeatedly(
+                            DoAll(SetArgPointee<3>(testConfigs[i].group), Return(Error::NONE)));
+            EXPECT_CALL(*mMockComposer,
+                        getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::DPI_X, _))
+                    .WillRepeatedly(Return(Error::UNSUPPORTED));
+            EXPECT_CALL(*mMockComposer,
+                        getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::DPI_Y, _))
+                    .WillRepeatedly(Return(Error::UNSUPPORTED));
+        }
 
-            ALOGV_IF(event.header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG,
-                     "event hotplug: displayId %" ANDROID_PHYSICAL_DISPLAY_ID_FORMAT
-                     ", connected %d\t",
-                     event.header.displayId, event.hotplug.connected);
+        EXPECT_CALL(*mMockComposer, getDisplayConfigs(display, _))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(hidl_vec<Config>(configIds)),
+                                      Return(V2_1::Error::NONE)));
 
-            if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG &&
-                event.header.displayId == displayId && event.hotplug.connected == connected) {
-                return true;
+        EXPECT_CALL(*mMockComposer, getActiveConfig(display, _))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(activeConfig), Return(V2_1::Error::NONE)));
+
+        EXPECT_CALL(*mMockComposer, getDisplayVsyncPeriod(display, _))
+                .WillRepeatedly(
+                        DoAll(SetArgPointee<1>(defaultVsyncPeriod), Return(V2_4::Error::NONE)));
+    }
+
+    void SetUp() override {
+        mMockComposer = std::make_unique<MockComposerHal>();
+        mFakeComposerClient = new FakeComposerClient();
+        mFakeComposerClient->setMockHal(mMockComposer.get());
+
+        sp<V2_4::hal::ComposerClient> client = new V2_4::hal::ComposerClient(mFakeComposerClient);
+        mFakeService = new FakeComposerService(client);
+        ASSERT_EQ(android::OK, mFakeService->registerAsService("mock"));
+
+        android::hardware::ProcessState::self()->startThreadPool();
+        android::ProcessState::self()->startThreadPool();
+
+        setExpectationsForConfigs(PRIMARY_DISPLAY,
+                                  {{
+                                          .id = 1,
+                                          .w = 1920,
+                                          .h = 1024,
+                                          .vsyncPeriod = 16'666'666,
+                                          .group = 0,
+                                  }},
+                                  1, 16'666'666);
+
+        startSurfaceFlinger();
+
+        // Fake composer wants to enable VSync injection
+        mFakeComposerClient->onSurfaceFlingerStart();
+
+        mComposerClient = new SurfaceComposerClient;
+        ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
+
+        mReceiver.reset(new DisplayEventReceiver(ISurfaceComposer::eVsyncSourceApp,
+                                                 ISurfaceComposer::eConfigChangedDispatch));
+        mLooper = new Looper(false);
+        mLooper->addFd(mReceiver->getFd(), 0, ALOOPER_EVENT_INPUT, processDisplayEvents, this);
+    }
+
+    void TearDown() override {
+        mLooper = nullptr;
+        mReceiver = nullptr;
+
+        mComposerClient->dispose();
+        mComposerClient = nullptr;
+
+        // Fake composer needs to release SurfaceComposerClient before the stop.
+        mFakeComposerClient->onSurfaceFlingerStop();
+        stopSurfaceFlinger();
+
+        mFakeComposerClient->setMockHal(nullptr);
+
+        mFakeService = nullptr;
+        // TODO: Currently deleted in FakeComposerClient::removeClient(). Devise better lifetime
+        // management.
+        mMockComposer = nullptr;
+    }
+
+    void waitForDisplayTransaction() {
+        // Both a refresh and a vsync event are needed to apply pending display
+        // transactions.
+        mFakeComposerClient->refreshDisplay(EXTERNAL_DISPLAY);
+        mFakeComposerClient->runVSyncAndWait();
+
+        // Extra vsync and wait to avoid a 10% flake due to a race.
+        mFakeComposerClient->runVSyncAndWait();
+    }
+
+    bool waitForHotplugEvent(PhysicalDisplayId displayId, bool connected) {
+        int waitCount = 20;
+        while (waitCount--) {
+            while (!mReceivedDisplayEvents.empty()) {
+                auto event = mReceivedDisplayEvents.front();
+                mReceivedDisplayEvents.pop_front();
+
+                ALOGV_IF(event.header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG,
+                         "event hotplug: displayId %" ANDROID_PHYSICAL_DISPLAY_ID_FORMAT
+                         ", connected %d\t",
+                         event.header.displayId, event.hotplug.connected);
+
+                if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG &&
+                    event.header.displayId == displayId && event.hotplug.connected == connected) {
+                    return true;
+                }
+            }
+
+            mLooper->pollOnce(1);
+        }
+        return false;
+    }
+
+    bool waitForConfigChangedEvent(PhysicalDisplayId displayId, int32_t configId) {
+        int waitCount = 20;
+        while (waitCount--) {
+            while (!mReceivedDisplayEvents.empty()) {
+                auto event = mReceivedDisplayEvents.front();
+                mReceivedDisplayEvents.pop_front();
+
+                ALOGV_IF(event.header.type == DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED,
+                         "event config: displayId %" ANDROID_PHYSICAL_DISPLAY_ID_FORMAT
+                         ", configId %d\t",
+                         event.header.displayId, event.config.configId);
+
+                if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED &&
+                    event.header.displayId == displayId && event.config.configId == configId) {
+                    return true;
+                }
+            }
+
+            mLooper->pollOnce(1);
+        }
+        return false;
+    }
+
+    void Test_HotplugOneConfig() {
+        ALOGD("DisplayTest::Test_Hotplug_oneConfig");
+
+        setExpectationsForConfigs(EXTERNAL_DISPLAY,
+                                  {{.id = 1,
+                                    .w = 200,
+                                    .h = 400,
+                                    .vsyncPeriod = 16'666'666,
+                                    .group = 0}},
+                                  1, 16'666'666);
+
+        mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
+                                            V2_1::IComposerCallback::Connection::CONNECTED);
+        waitForDisplayTransaction();
+        EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true));
+
+        {
+            const auto display = SurfaceComposerClient::getPhysicalDisplayToken(EXTERNAL_DISPLAY);
+            EXPECT_FALSE(display == nullptr);
+
+            DisplayInfo info;
+            EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
+            EXPECT_EQ(200u, info.w);
+            EXPECT_EQ(400u, info.h);
+            EXPECT_EQ(1e9f / 16'666'666, info.fps);
+
+            auto surfaceControl =
+                    mComposerClient->createSurface(String8("Display Test Surface Foo"), info.w,
+                                                   info.h, PIXEL_FORMAT_RGBA_8888, 0);
+            EXPECT_TRUE(surfaceControl != nullptr);
+            EXPECT_TRUE(surfaceControl->isValid());
+            fillSurfaceRGBA8(surfaceControl, BLUE);
+
+            {
+                TransactionScope ts(*mFakeComposerClient);
+                ts.setDisplayLayerStack(display, 0);
+
+                ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
             }
         }
 
-        mLooper->pollOnce(1);
-    }
-
-    return false;
-}
-
-TEST_F(DisplayTest, Hotplug) {
-    ALOGD("DisplayTest::Hotplug");
-
-    // The attribute queries will get done twice. This is for defaults
-    EXPECT_CALL(*mMockComposer, getDisplayAttribute(EXTERNAL_DISPLAY, 1, _, _))
-            .Times(2 * 3)
-            .WillRepeatedly(Invoke(mMockComposer, &MockComposerClient::getDisplayAttributeFake));
-    // ... and then special handling for dimensions. Specifying these
-    // rules later means that gmock will try them first, i.e.,
-    // ordering of width/height vs. the default implementation for
-    // other queries is significant.
-    EXPECT_CALL(*mMockComposer,
-                getDisplayAttribute(EXTERNAL_DISPLAY, 1, IComposerClient::Attribute::WIDTH, _))
-            .Times(2)
-            .WillRepeatedly(DoAll(SetArgPointee<3>(400), Return(Error::NONE)));
-
-    EXPECT_CALL(*mMockComposer,
-                getDisplayAttribute(EXTERNAL_DISPLAY, 1, IComposerClient::Attribute::HEIGHT, _))
-            .Times(2)
-            .WillRepeatedly(DoAll(SetArgPointee<3>(200), Return(Error::NONE)));
-
-    mMockComposer->hotplugDisplay(EXTERNAL_DISPLAY, IComposerCallback::Connection::CONNECTED);
-
-    waitForDisplayTransaction();
-
-    EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true));
-
-    {
-        const auto display = SurfaceComposerClient::getPhysicalDisplayToken(EXTERNAL_DISPLAY);
-        ASSERT_FALSE(display == nullptr);
-
-        DisplayInfo info;
-        ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
-        ASSERT_EQ(400u, info.w);
-        ASSERT_EQ(200u, info.h);
-
-        auto surfaceControl =
-                mComposerClient->createSurface(String8("Display Test Surface Foo"), info.w, info.h,
-                                               PIXEL_FORMAT_RGBA_8888, 0);
-        ASSERT_TRUE(surfaceControl != nullptr);
-        ASSERT_TRUE(surfaceControl->isValid());
-        fillSurfaceRGBA8(surfaceControl, BLUE);
+        mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
+                                            V2_1::IComposerCallback::Connection::DISCONNECTED);
+        waitForDisplayTransaction();
+        mFakeComposerClient->clearFrames();
+        EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false));
 
         {
-            TransactionScope ts(*mMockComposer);
-            ts.setDisplayLayerStack(display, 0);
+            const auto display = SurfaceComposerClient::getPhysicalDisplayToken(EXTERNAL_DISPLAY);
+            EXPECT_TRUE(display == nullptr);
 
-            ts.setLayer(surfaceControl, INT32_MAX - 2)
-                .show(surfaceControl);
+            DisplayInfo info;
+            EXPECT_NE(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
         }
     }
 
-    mMockComposer->hotplugDisplay(EXTERNAL_DISPLAY, IComposerCallback::Connection::DISCONNECTED);
+    void Test_HotplugTwoSeparateConfigs() {
+        ALOGD("DisplayTest::Test_HotplugTwoSeparateConfigs");
 
-    mMockComposer->clearFrames();
+        setExpectationsForConfigs(EXTERNAL_DISPLAY,
+                                  {{.id = 1,
+                                    .w = 200,
+                                    .h = 400,
+                                    .vsyncPeriod = 16'666'666,
+                                    .group = 0},
+                                   {.id = 2,
+                                    .w = 800,
+                                    .h = 1600,
+                                    .vsyncPeriod = 11'111'111,
+                                    .group = 1}},
+                                  1, 16'666'666);
 
-    mMockComposer->hotplugDisplay(EXTERNAL_DISPLAY, IComposerCallback::Connection::CONNECTED);
+        mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
+                                            V2_1::IComposerCallback::Connection::CONNECTED);
+        waitForDisplayTransaction();
+        EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true));
 
-    waitForDisplayTransaction();
-
-    EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false));
-    EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true));
-
-    {
         const auto display = SurfaceComposerClient::getPhysicalDisplayToken(EXTERNAL_DISPLAY);
-        ASSERT_FALSE(display == nullptr);
+        EXPECT_FALSE(display == nullptr);
 
         DisplayInfo info;
-        ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
-        ASSERT_EQ(400u, info.w);
-        ASSERT_EQ(200u, info.h);
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
+        EXPECT_EQ(200u, info.w);
+        EXPECT_EQ(400u, info.h);
+        EXPECT_EQ(1e9f / 16'666'666, info.fps);
 
-        auto surfaceControl =
-                mComposerClient->createSurface(String8("Display Test Surface Bar"), info.w, info.h,
-                                               PIXEL_FORMAT_RGBA_8888, 0);
-        ASSERT_TRUE(surfaceControl != nullptr);
-        ASSERT_TRUE(surfaceControl->isValid());
-        fillSurfaceRGBA8(surfaceControl, BLUE);
+        mFakeComposerClient->clearFrames();
+        {
+            auto surfaceControl =
+                    mComposerClient->createSurface(String8("Display Test Surface Foo"), info.w,
+                                                   info.h, PIXEL_FORMAT_RGBA_8888, 0);
+            EXPECT_TRUE(surfaceControl != nullptr);
+            EXPECT_TRUE(surfaceControl->isValid());
+            fillSurfaceRGBA8(surfaceControl, BLUE);
+
+            {
+                TransactionScope ts(*mFakeComposerClient);
+                ts.setDisplayLayerStack(display, 0);
+
+                ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
+            }
+        }
+
+        Vector<DisplayInfo> configs;
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayConfigs(display, &configs));
+        EXPECT_EQ(configs.size(), 2);
+
+        // change active config
+
+        if (mIs2_4Client) {
+            EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 2, _, _))
+                    .WillOnce(Return(V2_4::Error::NONE));
+        } else {
+            EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 2))
+                    .WillOnce(Return(V2_1::Error::NONE));
+        }
+
+        for (int i = 0; i < configs.size(); i++) {
+            if (configs[i].w == 800u) {
+                EXPECT_EQ(NO_ERROR, SurfaceComposerClient::setActiveConfig(display, i));
+                waitForDisplayTransaction();
+                EXPECT_TRUE(waitForConfigChangedEvent(EXTERNAL_DISPLAY, i));
+                break;
+            }
+        }
+
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
+        EXPECT_EQ(800u, info.w);
+        EXPECT_EQ(1600u, info.h);
+        EXPECT_EQ(1e9f / 11'111'111, info.fps);
+
+        mFakeComposerClient->clearFrames();
+        {
+            auto surfaceControl =
+                    mComposerClient->createSurface(String8("Display Test Surface Foo"), info.w,
+                                                   info.h, PIXEL_FORMAT_RGBA_8888, 0);
+            EXPECT_TRUE(surfaceControl != nullptr);
+            EXPECT_TRUE(surfaceControl->isValid());
+            fillSurfaceRGBA8(surfaceControl, BLUE);
+
+            {
+                TransactionScope ts(*mFakeComposerClient);
+                ts.setDisplayLayerStack(display, 0);
+
+                ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
+            }
+        }
+
+        mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
+                                            V2_1::IComposerCallback::Connection::DISCONNECTED);
+        waitForDisplayTransaction();
+        mFakeComposerClient->clearFrames();
+        EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false));
+    }
+
+    void Test_HotplugTwoConfigsSameGroup() {
+        ALOGD("DisplayTest::Test_HotplugTwoConfigsSameGroup");
+
+        setExpectationsForConfigs(EXTERNAL_DISPLAY,
+                                  {{.id = 2,
+                                    .w = 800,
+                                    .h = 1600,
+                                    .vsyncPeriod = 16'666'666,
+                                    .group = 31},
+                                   {.id = 3,
+                                    .w = 800,
+                                    .h = 1600,
+                                    .vsyncPeriod = 11'111'111,
+                                    .group = 31}},
+                                  2, 16'666'666);
+
+        mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
+                                            V2_1::IComposerCallback::Connection::CONNECTED);
+        waitForDisplayTransaction();
+        EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true));
+
+        const auto display = SurfaceComposerClient::getPhysicalDisplayToken(EXTERNAL_DISPLAY);
+        EXPECT_FALSE(display == nullptr);
+
+        DisplayInfo info;
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
+        EXPECT_EQ(800u, info.w);
+        EXPECT_EQ(1600u, info.h);
+        EXPECT_EQ(1e9f / 16'666'666, info.fps);
+
+        mFakeComposerClient->clearFrames();
+        {
+            auto surfaceControl =
+                    mComposerClient->createSurface(String8("Display Test Surface Foo"), info.w,
+                                                   info.h, PIXEL_FORMAT_RGBA_8888, 0);
+            EXPECT_TRUE(surfaceControl != nullptr);
+            EXPECT_TRUE(surfaceControl->isValid());
+            fillSurfaceRGBA8(surfaceControl, BLUE);
+
+            {
+                TransactionScope ts(*mFakeComposerClient);
+                ts.setDisplayLayerStack(display, 0);
+
+                ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
+            }
+        }
+
+        Vector<DisplayInfo> configs;
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayConfigs(display, &configs));
+        EXPECT_EQ(configs.size(), 2);
+
+        // change active config
+        if (mIs2_4Client) {
+            EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 3, _, _))
+                    .WillOnce(Return(V2_4::Error::NONE));
+        } else {
+            EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 3))
+                    .WillOnce(Return(V2_1::Error::NONE));
+        }
+
+        for (int i = 0; i < configs.size(); i++) {
+            if (configs[i].fps == 1e9f / 11'111'111) {
+                EXPECT_EQ(NO_ERROR, SurfaceComposerClient::setActiveConfig(display, i));
+                waitForDisplayTransaction();
+                EXPECT_TRUE(waitForConfigChangedEvent(EXTERNAL_DISPLAY, i));
+                break;
+            }
+        }
+
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
+        EXPECT_EQ(800u, info.w);
+        EXPECT_EQ(1600u, info.h);
+        EXPECT_EQ(1e9f / 11'111'111, info.fps);
+
+        mFakeComposerClient->clearFrames();
+        {
+            auto surfaceControl =
+                    mComposerClient->createSurface(String8("Display Test Surface Foo"), info.w,
+                                                   info.h, PIXEL_FORMAT_RGBA_8888, 0);
+            EXPECT_TRUE(surfaceControl != nullptr);
+            EXPECT_TRUE(surfaceControl->isValid());
+            fillSurfaceRGBA8(surfaceControl, BLUE);
+
+            {
+                TransactionScope ts(*mFakeComposerClient);
+                ts.setDisplayLayerStack(display, 0);
+
+                ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
+            }
+        }
+
+        mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
+                                            V2_1::IComposerCallback::Connection::DISCONNECTED);
+        waitForDisplayTransaction();
+        mFakeComposerClient->clearFrames();
+        EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false));
+    }
+
+    void Test_HotplugThreeConfigsMixedGroups() {
+        ALOGD("DisplayTest::Test_HotplugThreeConfigsMixedGroups");
+
+        setExpectationsForConfigs(EXTERNAL_DISPLAY,
+                                  {{.id = 2,
+                                    .w = 800,
+                                    .h = 1600,
+                                    .vsyncPeriod = 16'666'666,
+                                    .group = 0},
+                                   {.id = 3,
+                                    .w = 800,
+                                    .h = 1600,
+                                    .vsyncPeriod = 11'111'111,
+                                    .group = 0},
+                                   {.id = 4,
+                                    .w = 1600,
+                                    .h = 3200,
+                                    .vsyncPeriod = 8'333'333,
+                                    .group = 1},
+                                   {.id = 5,
+                                    .w = 1600,
+                                    .h = 3200,
+                                    .vsyncPeriod = 11'111'111,
+                                    .group = 1}},
+                                  2, 16'666'666);
+
+        mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
+                                            V2_1::IComposerCallback::Connection::CONNECTED);
+        waitForDisplayTransaction();
+        EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true));
+
+        const auto display = SurfaceComposerClient::getPhysicalDisplayToken(EXTERNAL_DISPLAY);
+        EXPECT_FALSE(display == nullptr);
+
+        DisplayInfo info;
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
+        EXPECT_EQ(800u, info.w);
+        EXPECT_EQ(1600u, info.h);
+        EXPECT_EQ(1e9f / 16'666'666, info.fps);
+
+        mFakeComposerClient->clearFrames();
+        {
+            auto surfaceControl =
+                    mComposerClient->createSurface(String8("Display Test Surface Foo"), info.w,
+                                                   info.h, PIXEL_FORMAT_RGBA_8888, 0);
+            EXPECT_TRUE(surfaceControl != nullptr);
+            EXPECT_TRUE(surfaceControl->isValid());
+            fillSurfaceRGBA8(surfaceControl, BLUE);
+
+            {
+                TransactionScope ts(*mFakeComposerClient);
+                ts.setDisplayLayerStack(display, 0);
+
+                ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
+            }
+        }
+
+        Vector<DisplayInfo> configs;
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayConfigs(display, &configs));
+        EXPECT_EQ(configs.size(), 4);
+
+        // change active config to 800x1600@90Hz
+        if (mIs2_4Client) {
+            EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 3, _, _))
+                    .WillOnce(Return(V2_4::Error::NONE));
+        } else {
+            EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 3))
+                    .WillOnce(Return(V2_1::Error::NONE));
+        }
+
+        for (int i = 0; i < configs.size(); i++) {
+            if (configs[i].w == 800u && configs[i].fps == 1e9f / 11'111'111) {
+                EXPECT_EQ(NO_ERROR, SurfaceComposerClient::setActiveConfig(display, i));
+                waitForDisplayTransaction();
+                EXPECT_TRUE(waitForConfigChangedEvent(EXTERNAL_DISPLAY, i));
+                break;
+            }
+        }
+
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
+        EXPECT_EQ(800u, info.w);
+        EXPECT_EQ(1600u, info.h);
+        EXPECT_EQ(1e9f / 11'111'111, info.fps);
+
+        mFakeComposerClient->clearFrames();
+        {
+            auto surfaceControl =
+                    mComposerClient->createSurface(String8("Display Test Surface Foo"), info.w,
+                                                   info.h, PIXEL_FORMAT_RGBA_8888, 0);
+            EXPECT_TRUE(surfaceControl != nullptr);
+            EXPECT_TRUE(surfaceControl->isValid());
+            fillSurfaceRGBA8(surfaceControl, BLUE);
+
+            {
+                TransactionScope ts(*mFakeComposerClient);
+                ts.setDisplayLayerStack(display, 0);
+
+                ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
+            }
+        }
+
+        // change active config to 1600x3200@120Hz
+        if (mIs2_4Client) {
+            EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 4, _, _))
+                    .WillOnce(Return(V2_4::Error::NONE));
+        } else {
+            EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 4))
+                    .WillOnce(Return(V2_1::Error::NONE));
+        }
+
+        for (int i = 0; i < configs.size(); i++) {
+            if (configs[i].fps == 1e9f / 8'333'333) {
+                EXPECT_EQ(NO_ERROR, SurfaceComposerClient::setActiveConfig(display, i));
+                waitForDisplayTransaction();
+                EXPECT_TRUE(waitForConfigChangedEvent(EXTERNAL_DISPLAY, i));
+                break;
+            }
+        }
+
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
+        EXPECT_EQ(1600u, info.w);
+        EXPECT_EQ(3200u, info.h);
+        EXPECT_EQ(1e9f / 8'333'333, info.fps);
+
+        mFakeComposerClient->clearFrames();
+        {
+            auto surfaceControl =
+                    mComposerClient->createSurface(String8("Display Test Surface Foo"), info.w,
+                                                   info.h, PIXEL_FORMAT_RGBA_8888, 0);
+            EXPECT_TRUE(surfaceControl != nullptr);
+            EXPECT_TRUE(surfaceControl->isValid());
+            fillSurfaceRGBA8(surfaceControl, BLUE);
+
+            {
+                TransactionScope ts(*mFakeComposerClient);
+                ts.setDisplayLayerStack(display, 0);
+
+                ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
+            }
+        }
+
+        // change active config to 1600x3200@90Hz
+        if (mIs2_4Client) {
+            EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 5, _, _))
+                    .WillOnce(Return(V2_4::Error::NONE));
+        } else {
+            EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 5))
+                    .WillOnce(Return(V2_1::Error::NONE));
+        }
+
+        for (int i = 0; i < configs.size(); i++) {
+            if (configs[i].w == 1600 && configs[i].fps == 1e9f / 11'111'111) {
+                EXPECT_EQ(NO_ERROR, SurfaceComposerClient::setActiveConfig(display, i));
+                waitForDisplayTransaction();
+                EXPECT_TRUE(waitForConfigChangedEvent(EXTERNAL_DISPLAY, i));
+                break;
+            }
+        }
+
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
+        EXPECT_EQ(1600u, info.w);
+        EXPECT_EQ(3200u, info.h);
+        EXPECT_EQ(1e9f / 11'111'111, info.fps);
+
+        mFakeComposerClient->clearFrames();
+        {
+            auto surfaceControl =
+                    mComposerClient->createSurface(String8("Display Test Surface Foo"), info.w,
+                                                   info.h, PIXEL_FORMAT_RGBA_8888, 0);
+            EXPECT_TRUE(surfaceControl != nullptr);
+            EXPECT_TRUE(surfaceControl->isValid());
+            fillSurfaceRGBA8(surfaceControl, BLUE);
+
+            {
+                TransactionScope ts(*mFakeComposerClient);
+                ts.setDisplayLayerStack(display, 0);
+
+                ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
+            }
+        }
+
+        mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
+                                            V2_1::IComposerCallback::Connection::DISCONNECTED);
+        waitForDisplayTransaction();
+        mFakeComposerClient->clearFrames();
+        EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false));
+    }
+
+    void Test_HotplugPrimaryDisplay() {
+        ALOGD("DisplayTest::HotplugPrimaryDisplay");
+
+        mFakeComposerClient->hotplugDisplay(PRIMARY_DISPLAY,
+                                            V2_1::IComposerCallback::Connection::DISCONNECTED);
+
+        waitForDisplayTransaction();
+
+        EXPECT_TRUE(waitForHotplugEvent(PRIMARY_DISPLAY, false));
+        {
+            const auto display = SurfaceComposerClient::getPhysicalDisplayToken(PRIMARY_DISPLAY);
+            EXPECT_TRUE(display == nullptr);
+
+            DisplayInfo info;
+            auto result = SurfaceComposerClient::getDisplayInfo(display, &info);
+            EXPECT_NE(NO_ERROR, result);
+        }
+
+        mFakeComposerClient->clearFrames();
+
+        setExpectationsForConfigs(PRIMARY_DISPLAY,
+                                  {{.id = 1,
+                                    .w = 400,
+                                    .h = 200,
+                                    .vsyncPeriod = 16'666'666,
+                                    .group = 0}},
+                                  1, 16'666'666);
+
+        mFakeComposerClient->hotplugDisplay(PRIMARY_DISPLAY,
+                                            V2_1::IComposerCallback::Connection::CONNECTED);
+
+        waitForDisplayTransaction();
+
+        EXPECT_TRUE(waitForHotplugEvent(PRIMARY_DISPLAY, true));
 
         {
-            TransactionScope ts(*mMockComposer);
-            ts.setDisplayLayerStack(display, 0);
+            const auto display = SurfaceComposerClient::getPhysicalDisplayToken(PRIMARY_DISPLAY);
+            EXPECT_FALSE(display == nullptr);
 
-            ts.setLayer(surfaceControl, INT32_MAX - 2)
-                .show(surfaceControl);
+            DisplayInfo info;
+            auto result = SurfaceComposerClient::getDisplayInfo(display, &info);
+            EXPECT_EQ(NO_ERROR, result);
+            ASSERT_EQ(400u, info.w);
+            ASSERT_EQ(200u, info.h);
+            EXPECT_EQ(1e9f / 16'666'666, info.fps);
         }
     }
-    mMockComposer->hotplugDisplay(EXTERNAL_DISPLAY, IComposerCallback::Connection::DISCONNECTED);
+
+    sp<V2_1::IComposer> mFakeService;
+    sp<SurfaceComposerClient> mComposerClient;
+
+    std::unique_ptr<MockComposerHal> mMockComposer;
+    FakeComposerClient* mFakeComposerClient;
+
+    std::unique_ptr<DisplayEventReceiver> mReceiver;
+    sp<Looper> mLooper;
+    std::deque<DisplayEventReceiver::Event> mReceivedDisplayEvents;
+
+    static constexpr bool mIs2_4Client =
+            std::is_same<FakeComposerService, FakeComposerService_2_4>::value;
+};
+
+using DisplayTest_2_1 = DisplayTest<FakeComposerService_2_1>;
+
+TEST_F(DisplayTest_2_1, HotplugOneConfig) {
+    Test_HotplugOneConfig();
 }
 
-TEST_F(DisplayTest, HotplugPrimaryDisplay) {
-    ALOGD("DisplayTest::HotplugPrimaryDisplay");
+TEST_F(DisplayTest_2_1, HotplugTwoSeparateConfigs) {
+    Test_HotplugTwoSeparateConfigs();
+}
 
-    mMockComposer->hotplugDisplay(PRIMARY_DISPLAY, IComposerCallback::Connection::DISCONNECTED);
+TEST_F(DisplayTest_2_1, HotplugTwoConfigsSameGroup) {
+    Test_HotplugTwoConfigsSameGroup();
+}
 
-    waitForDisplayTransaction();
+TEST_F(DisplayTest_2_1, HotplugThreeConfigsMixedGroups) {
+    Test_HotplugThreeConfigsMixedGroups();
+}
 
-    EXPECT_TRUE(waitForHotplugEvent(PRIMARY_DISPLAY, false));
+TEST_F(DisplayTest_2_1, HotplugPrimaryOneConfig) {
+    Test_HotplugPrimaryDisplay();
+}
 
-    {
-        const auto display = SurfaceComposerClient::getPhysicalDisplayToken(PRIMARY_DISPLAY);
-        EXPECT_FALSE(display == nullptr);
+using DisplayTest_2_2 = DisplayTest<FakeComposerService_2_2>;
 
-        DisplayInfo info;
-        auto result = SurfaceComposerClient::getDisplayInfo(display, &info);
-        EXPECT_NE(NO_ERROR, result);
-    }
+TEST_F(DisplayTest_2_2, HotplugOneConfig) {
+    Test_HotplugOneConfig();
+}
 
-    mMockComposer->clearFrames();
+TEST_F(DisplayTest_2_2, HotplugTwoSeparateConfigs) {
+    Test_HotplugTwoSeparateConfigs();
+}
 
-    // The attribute queries will get done twice. This is for defaults
-    EXPECT_CALL(*mMockComposer, getDisplayAttribute(PRIMARY_DISPLAY, 1, _, _))
-            .Times(2 * 3)
-            .WillRepeatedly(Invoke(mMockComposer, &MockComposerClient::getDisplayAttributeFake));
-    // ... and then special handling for dimensions. Specifying these
-    // rules later means that gmock will try them first, i.e.,
-    // ordering of width/height vs. the default implementation for
-    // other queries is significant.
-    EXPECT_CALL(*mMockComposer,
-                getDisplayAttribute(PRIMARY_DISPLAY, 1, IComposerClient::Attribute::WIDTH, _))
-            .Times(2)
-            .WillRepeatedly(DoAll(SetArgPointee<3>(400), Return(Error::NONE)));
+TEST_F(DisplayTest_2_2, HotplugTwoConfigsSameGroup) {
+    Test_HotplugTwoConfigsSameGroup();
+}
 
-    EXPECT_CALL(*mMockComposer,
-                getDisplayAttribute(PRIMARY_DISPLAY, 1, IComposerClient::Attribute::HEIGHT, _))
-            .Times(2)
-            .WillRepeatedly(DoAll(SetArgPointee<3>(200), Return(Error::NONE)));
+TEST_F(DisplayTest_2_2, HotplugThreeConfigsMixedGroups) {
+    Test_HotplugThreeConfigsMixedGroups();
+}
 
-    mMockComposer->hotplugDisplay(PRIMARY_DISPLAY, IComposerCallback::Connection::CONNECTED);
+TEST_F(DisplayTest_2_2, HotplugPrimaryOneConfig) {
+    Test_HotplugPrimaryDisplay();
+}
 
-    waitForDisplayTransaction();
+using DisplayTest_2_3 = DisplayTest<FakeComposerService_2_3>;
 
-    EXPECT_TRUE(waitForHotplugEvent(PRIMARY_DISPLAY, true));
+TEST_F(DisplayTest_2_3, HotplugOneConfig) {
+    Test_HotplugOneConfig();
+}
 
-    {
-        const auto display = SurfaceComposerClient::getPhysicalDisplayToken(PRIMARY_DISPLAY);
-        EXPECT_FALSE(display == nullptr);
+TEST_F(DisplayTest_2_3, HotplugTwoSeparateConfigs) {
+    Test_HotplugTwoSeparateConfigs();
+}
 
-        DisplayInfo info;
-        auto result = SurfaceComposerClient::getDisplayInfo(display, &info);
-        EXPECT_EQ(NO_ERROR, result);
-        ASSERT_EQ(400u, info.w);
-        ASSERT_EQ(200u, info.h);
-    }
+TEST_F(DisplayTest_2_3, HotplugTwoConfigsSameGroup) {
+    Test_HotplugTwoConfigsSameGroup();
+}
+
+TEST_F(DisplayTest_2_3, HotplugThreeConfigsMixedGroups) {
+    Test_HotplugThreeConfigsMixedGroups();
+}
+
+TEST_F(DisplayTest_2_3, HotplugPrimaryOneConfig) {
+    Test_HotplugPrimaryDisplay();
+}
+
+using DisplayTest_2_4 = DisplayTest<FakeComposerService_2_4>;
+
+TEST_F(DisplayTest_2_4, HotplugOneConfig) {
+    Test_HotplugOneConfig();
+}
+
+TEST_F(DisplayTest_2_4, HotplugTwoSeparateConfigs) {
+    Test_HotplugTwoSeparateConfigs();
+}
+
+TEST_F(DisplayTest_2_4, HotplugTwoConfigsSameGroup) {
+    Test_HotplugTwoConfigsSameGroup();
+}
+
+TEST_F(DisplayTest_2_4, HotplugThreeConfigsMixedGroups) {
+    Test_HotplugThreeConfigsMixedGroups();
+}
+
+TEST_F(DisplayTest_2_4, HotplugPrimaryOneConfig) {
+    Test_HotplugPrimaryDisplay();
 }
 
 ////////////////////////////////////////////////
 
+template <typename FakeComposerService>
 class TransactionTest : public ::testing::Test {
 protected:
     // Layer array indexing constants.
     constexpr static int BG_LAYER = 0;
     constexpr static int FG_LAYER = 1;
 
-    static void SetUpTestCase();
-    static void TearDownTestCase();
+    static void SetUpTestCase() {
+        // TODO: See TODO comment at DisplayTest::SetUp for background on
+        // the lifetime of the FakeComposerClient.
+        sFakeComposer = new FakeComposerClient;
+        sp<V2_4::hal::ComposerClient> client = new V2_4::hal::ComposerClient(sFakeComposer);
+        sp<V2_1::IComposer> fakeService = new FakeComposerService(client);
+        (void)fakeService->registerAsService("mock");
 
-    void SetUp() override;
-    void TearDown() override;
+        android::hardware::ProcessState::self()->startThreadPool();
+        android::ProcessState::self()->startThreadPool();
+
+        startSurfaceFlinger();
+
+        // Fake composer wants to enable VSync injection
+        sFakeComposer->onSurfaceFlingerStart();
+    }
+
+    static void TearDownTestCase() {
+        // Fake composer needs to release SurfaceComposerClient before the stop.
+        sFakeComposer->onSurfaceFlingerStop();
+        stopSurfaceFlinger();
+        // TODO: This is deleted when the ComposerClient calls
+        // removeClient. Devise better lifetime control.
+        sFakeComposer = nullptr;
+    }
+
+    void SetUp() override {
+        ALOGI("TransactionTest::SetUp");
+        mComposerClient = new SurfaceComposerClient;
+        ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
+
+        ALOGI("TransactionTest::SetUp - display");
+        const auto display = SurfaceComposerClient::getPhysicalDisplayToken(PRIMARY_DISPLAY);
+        ASSERT_FALSE(display == nullptr);
+
+        DisplayInfo info;
+        ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
+
+        mDisplayWidth = info.w;
+        mDisplayHeight = info.h;
+
+        // Background surface
+        mBGSurfaceControl =
+                mComposerClient->createSurface(String8("BG Test Surface"), mDisplayWidth,
+                                               mDisplayHeight, PIXEL_FORMAT_RGBA_8888, 0);
+        ASSERT_TRUE(mBGSurfaceControl != nullptr);
+        ASSERT_TRUE(mBGSurfaceControl->isValid());
+        fillSurfaceRGBA8(mBGSurfaceControl, BLUE);
+
+        // Foreground surface
+        mFGSurfaceControl = mComposerClient->createSurface(String8("FG Test Surface"), 64, 64,
+                                                           PIXEL_FORMAT_RGBA_8888, 0);
+        ASSERT_TRUE(mFGSurfaceControl != nullptr);
+        ASSERT_TRUE(mFGSurfaceControl->isValid());
+
+        fillSurfaceRGBA8(mFGSurfaceControl, RED);
+
+        Transaction t;
+        t.setDisplayLayerStack(display, 0);
+
+        t.setLayer(mBGSurfaceControl, INT32_MAX - 2);
+        t.show(mBGSurfaceControl);
+
+        t.setLayer(mFGSurfaceControl, INT32_MAX - 1);
+        t.setPosition(mFGSurfaceControl, 64, 64);
+        t.show(mFGSurfaceControl);
+
+        // Synchronous transaction will stop this thread, so we set up a
+        // delayed, off-thread vsync request before closing the
+        // transaction. In the test code this is usually done with
+        // TransactionScope. Leaving here in the 'vanilla' form for
+        // reference.
+        ASSERT_EQ(0, sFakeComposer->getFrameCount());
+        sFakeComposer->runVSyncAfter(1ms);
+        t.apply();
+        sFakeComposer->waitUntilFrame(1);
+
+        // Reference data. This is what the HWC should see.
+        static_assert(BG_LAYER == 0 && FG_LAYER == 1, "Unexpected enum values for array indexing");
+        mBaseFrame.push_back(makeSimpleRect(0u, 0u, mDisplayWidth, mDisplayHeight));
+        mBaseFrame[BG_LAYER].mSwapCount = 1;
+        mBaseFrame.push_back(makeSimpleRect(64, 64, 64 + 64, 64 + 64));
+        mBaseFrame[FG_LAYER].mSwapCount = 1;
+
+        auto frame = sFakeComposer->getFrameRects(0);
+        ASSERT_TRUE(framesAreSame(mBaseFrame, frame));
+    }
+
+    void TearDown() override {
+        ALOGD("TransactionTest::TearDown");
+
+        mComposerClient->dispose();
+        mBGSurfaceControl = 0;
+        mFGSurfaceControl = 0;
+        mComposerClient = 0;
+
+        sFakeComposer->runVSyncAndWait();
+        mBaseFrame.clear();
+        sFakeComposer->clearFrames();
+        ASSERT_EQ(0, sFakeComposer->getFrameCount());
+
+        sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+        std::vector<LayerDebugInfo> layers;
+        status_t result = sf->getLayerDebugInfo(&layers);
+        if (result != NO_ERROR) {
+            ALOGE("Failed to get layers %s %d", strerror(-result), result);
+        } else {
+            // If this fails, the test being torn down leaked layers.
+            EXPECT_EQ(0u, layers.size());
+            if (layers.size() > 0) {
+                for (auto layer = layers.begin(); layer != layers.end(); ++layer) {
+                    std::cout << to_string(*layer).c_str();
+                }
+                // To ensure the next test has clean slate, will run the class
+                // tear down and setup here.
+                TearDownTestCase();
+                SetUpTestCase();
+            }
+        }
+        ALOGD("TransactionTest::TearDown - complete");
+    }
+
+    void Test_LayerMove() {
+        ALOGD("TransactionTest::LayerMove");
+
+        // The scope opens and closes a global transaction and, at the
+        // same time, makes sure the SurfaceFlinger progresses one frame
+        // after the transaction closes. The results of the transaction
+        // should be available in the latest frame stored by the fake
+        // composer.
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setPosition(mFGSurfaceControl, 128, 128);
+            // NOTE: No changes yet, so vsync will do nothing, HWC does not get any calls.
+            // (How to verify that? Throw in vsync and wait a 2x frame time? Separate test?)
+            //
+            // sFakeComposer->runVSyncAndWait();
+        }
+
+        fillSurfaceRGBA8(mFGSurfaceControl, GREEN);
+        sFakeComposer->runVSyncAndWait();
+
+        ASSERT_EQ(3, sFakeComposer->getFrameCount()); // Make sure the waits didn't time out and
+                                                      // there's no extra frames.
+
+        // NOTE: Frame 0 is produced in the SetUp.
+        auto frame1Ref = mBaseFrame;
+        frame1Ref[FG_LAYER].mDisplayFrame =
+                hwc_rect_t{128, 128, 128 + 64, 128 + 64}; // Top-most layer moves.
+        EXPECT_TRUE(framesAreSame(frame1Ref, sFakeComposer->getFrameRects(1)));
+
+        auto frame2Ref = frame1Ref;
+        frame2Ref[FG_LAYER].mSwapCount++;
+        EXPECT_TRUE(framesAreSame(frame2Ref, sFakeComposer->getFrameRects(2)));
+    }
+
+    void Test_LayerResize() {
+        ALOGD("TransactionTest::LayerResize");
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setSize(mFGSurfaceControl, 128, 128);
+        }
+
+        fillSurfaceRGBA8(mFGSurfaceControl, GREEN);
+        sFakeComposer->runVSyncAndWait();
+
+        ASSERT_EQ(3, sFakeComposer->getFrameCount()); // Make sure the waits didn't time out and
+                                                      // there's no extra frames.
+
+        auto frame1Ref = mBaseFrame;
+        // NOTE: The resize should not be visible for frame 1 as there's no buffer with new size
+        // posted.
+        EXPECT_TRUE(framesAreSame(frame1Ref, sFakeComposer->getFrameRects(1)));
+
+        auto frame2Ref = frame1Ref;
+        frame2Ref[FG_LAYER].mSwapCount++;
+        frame2Ref[FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 128, 64 + 128};
+        frame2Ref[FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 128.f, 128.f};
+        EXPECT_TRUE(framesAreSame(frame2Ref, sFakeComposer->getFrameRects(2)));
+    }
+
+    void Test_LayerCrop() {
+        // TODO: Add scaling to confirm that crop happens in buffer space?
+        {
+            TransactionScope ts(*sFakeComposer);
+            Rect cropRect(16, 16, 32, 32);
+            ts.setCrop_legacy(mFGSurfaceControl, cropRect);
+        }
+        ASSERT_EQ(2, sFakeComposer->getFrameCount());
+
+        auto referenceFrame = mBaseFrame;
+        referenceFrame[FG_LAYER].mSourceCrop = hwc_frect_t{16.f, 16.f, 32.f, 32.f};
+        referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{64 + 16, 64 + 16, 64 + 32, 64 + 32};
+        EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_LayerSetLayer() {
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setLayer(mFGSurfaceControl, INT_MAX - 3);
+        }
+        ASSERT_EQ(2, sFakeComposer->getFrameCount());
+
+        // The layers will switch order, but both are rendered because the background layer is
+        // transparent (RGBA8888).
+        std::vector<RenderState> referenceFrame(2);
+        referenceFrame[0] = mBaseFrame[FG_LAYER];
+        referenceFrame[1] = mBaseFrame[BG_LAYER];
+        EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_LayerSetLayerOpaque() {
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setLayer(mFGSurfaceControl, INT_MAX - 3);
+            ts.setFlags(mBGSurfaceControl, layer_state_t::eLayerOpaque,
+                        layer_state_t::eLayerOpaque);
+        }
+        ASSERT_EQ(2, sFakeComposer->getFrameCount());
+
+        // The former foreground layer is now covered with opaque layer - it should have disappeared
+        std::vector<RenderState> referenceFrame(1);
+        referenceFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
+        EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_SetLayerStack() {
+        ALOGD("TransactionTest::SetLayerStack");
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setLayerStack(mFGSurfaceControl, 1);
+        }
+
+        // Foreground layer should have disappeared.
+        ASSERT_EQ(2, sFakeComposer->getFrameCount());
+        std::vector<RenderState> refFrame(1);
+        refFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
+        EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_LayerShowHide() {
+        ALOGD("TransactionTest::LayerShowHide");
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.hide(mFGSurfaceControl);
+        }
+
+        // Foreground layer should have disappeared.
+        ASSERT_EQ(2, sFakeComposer->getFrameCount());
+        std::vector<RenderState> refFrame(1);
+        refFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
+        EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame()));
+
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.show(mFGSurfaceControl);
+        }
+
+        // Foreground layer should be back
+        ASSERT_EQ(3, sFakeComposer->getFrameCount());
+        EXPECT_TRUE(framesAreSame(mBaseFrame, sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_LayerSetAlpha() {
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setAlpha(mFGSurfaceControl, 0.75f);
+        }
+
+        ASSERT_EQ(2, sFakeComposer->getFrameCount());
+        auto referenceFrame = mBaseFrame;
+        referenceFrame[FG_LAYER].mPlaneAlpha = 0.75f;
+        EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_LayerSetFlags() {
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setFlags(mFGSurfaceControl, layer_state_t::eLayerHidden,
+                        layer_state_t::eLayerHidden);
+        }
+
+        // Foreground layer should have disappeared.
+        ASSERT_EQ(2, sFakeComposer->getFrameCount());
+        std::vector<RenderState> refFrame(1);
+        refFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
+        EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_LayerSetMatrix() {
+        struct matrixTestData {
+            float matrix[4];
+            hwc_transform_t expectedTransform;
+            hwc_rect_t expectedDisplayFrame;
+        };
+
+        // The matrix operates on the display frame and is applied before
+        // the position is added. So, the foreground layer rect is (0, 0,
+        // 64, 64) is first transformed, potentially yielding negative
+        // coordinates and then the position (64, 64) is added yielding
+        // the final on-screen rectangles given.
+
+        const matrixTestData MATRIX_TESTS[7] = // clang-format off
+                {{{-1.f, 0.f, 0.f, 1.f},    HWC_TRANSFORM_FLIP_H,           {0, 64, 64, 128}},
+                 {{1.f, 0.f, 0.f, -1.f},    HWC_TRANSFORM_FLIP_V,           {64, 0, 128, 64}},
+                 {{0.f, 1.f, -1.f, 0.f},    HWC_TRANSFORM_ROT_90,           {0, 64, 64, 128}},
+                 {{-1.f, 0.f, 0.f, -1.f},   HWC_TRANSFORM_ROT_180,          {0, 0, 64, 64}},
+                 {{0.f, -1.f, 1.f, 0.f},    HWC_TRANSFORM_ROT_270,          {64, 0, 128, 64}},
+                 {{0.f, 1.f, 1.f, 0.f},     HWC_TRANSFORM_FLIP_H_ROT_90,    {64, 64, 128, 128}},
+                 {{0.f, 1.f, 1.f, 0.f},     HWC_TRANSFORM_FLIP_V_ROT_90,    {64, 64, 128, 128}}};
+        // clang-format on
+        constexpr int TEST_COUNT = sizeof(MATRIX_TESTS) / sizeof(matrixTestData);
+
+        for (int i = 0; i < TEST_COUNT; i++) {
+            // TODO: How to leverage the HWC2 stringifiers?
+            const matrixTestData& xform = MATRIX_TESTS[i];
+            SCOPED_TRACE(i);
+            {
+                TransactionScope ts(*sFakeComposer);
+                ts.setMatrix(mFGSurfaceControl, xform.matrix[0], xform.matrix[1], xform.matrix[2],
+                             xform.matrix[3]);
+            }
+
+            auto referenceFrame = mBaseFrame;
+            referenceFrame[FG_LAYER].mTransform = xform.expectedTransform;
+            referenceFrame[FG_LAYER].mDisplayFrame = xform.expectedDisplayFrame;
+
+            EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+        }
+    }
+
+    void Test_DeferredTransaction() {
+        // Synchronization surface
+        constexpr static int SYNC_LAYER = 2;
+        auto syncSurfaceControl = mComposerClient->createSurface(String8("Sync Test Surface"), 1, 1,
+                                                                 PIXEL_FORMAT_RGBA_8888, 0);
+        ASSERT_TRUE(syncSurfaceControl != nullptr);
+        ASSERT_TRUE(syncSurfaceControl->isValid());
+
+        fillSurfaceRGBA8(syncSurfaceControl, DARK_GRAY);
+
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setLayer(syncSurfaceControl, INT32_MAX - 1);
+            ts.setPosition(syncSurfaceControl, mDisplayWidth - 2, mDisplayHeight - 2);
+            ts.show(syncSurfaceControl);
+        }
+        auto referenceFrame = mBaseFrame;
+        referenceFrame.push_back(makeSimpleRect(mDisplayWidth - 2, mDisplayHeight - 2,
+                                                mDisplayWidth - 1, mDisplayHeight - 1));
+        referenceFrame[SYNC_LAYER].mSwapCount = 1;
+        EXPECT_EQ(2, sFakeComposer->getFrameCount());
+        EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+
+        // set up two deferred transactions on different frames - these should not yield composited
+        // frames
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setAlpha(mFGSurfaceControl, 0.75);
+            ts.deferTransactionUntil_legacy(mFGSurfaceControl, syncSurfaceControl->getHandle(),
+                                            syncSurfaceControl->getSurface()->getNextFrameNumber());
+        }
+        EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setPosition(mFGSurfaceControl, 128, 128);
+            ts.deferTransactionUntil_legacy(mFGSurfaceControl, syncSurfaceControl->getHandle(),
+                                            syncSurfaceControl->getSurface()->getNextFrameNumber() +
+                                                    1);
+        }
+        EXPECT_EQ(4, sFakeComposer->getFrameCount());
+        EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+
+        // should trigger the first deferred transaction, but not the second one
+        fillSurfaceRGBA8(syncSurfaceControl, DARK_GRAY);
+        sFakeComposer->runVSyncAndWait();
+        EXPECT_EQ(5, sFakeComposer->getFrameCount());
+
+        referenceFrame[FG_LAYER].mPlaneAlpha = 0.75f;
+        referenceFrame[SYNC_LAYER].mSwapCount++;
+        EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+
+        // should show up immediately since it's not deferred
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setAlpha(mFGSurfaceControl, 1.0);
+        }
+        referenceFrame[FG_LAYER].mPlaneAlpha = 1.f;
+        EXPECT_EQ(6, sFakeComposer->getFrameCount());
+        EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+
+        // trigger the second deferred transaction
+        fillSurfaceRGBA8(syncSurfaceControl, DARK_GRAY);
+        sFakeComposer->runVSyncAndWait();
+        // TODO: Compute from layer size?
+        referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{128, 128, 128 + 64, 128 + 64};
+        referenceFrame[SYNC_LAYER].mSwapCount++;
+        EXPECT_EQ(7, sFakeComposer->getFrameCount());
+        EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_SetRelativeLayer() {
+        constexpr int RELATIVE_LAYER = 2;
+        auto relativeSurfaceControl = mComposerClient->createSurface(String8("Test Surface"), 64,
+                                                                     64, PIXEL_FORMAT_RGBA_8888, 0);
+        fillSurfaceRGBA8(relativeSurfaceControl, LIGHT_RED);
+
+        // Now we stack the surface above the foreground surface and make sure it is visible.
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setPosition(relativeSurfaceControl, 64, 64);
+            ts.show(relativeSurfaceControl);
+            ts.setRelativeLayer(relativeSurfaceControl, mFGSurfaceControl->getHandle(), 1);
+        }
+        auto referenceFrame = mBaseFrame;
+        // NOTE: All three layers will be visible as the surfaces are
+        // transparent because of the RGBA format.
+        referenceFrame.push_back(makeSimpleRect(64, 64, 64 + 64, 64 + 64));
+        referenceFrame[RELATIVE_LAYER].mSwapCount = 1;
+        EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+
+        // A call to setLayer will override a call to setRelativeLayer
+        {
+            TransactionScope ts(*sFakeComposer);
+            ts.setLayer(relativeSurfaceControl, 0);
+        }
+
+        // Previous top layer will now appear at the bottom.
+        auto referenceFrame2 = mBaseFrame;
+        referenceFrame2.insert(referenceFrame2.begin(), referenceFrame[RELATIVE_LAYER]);
+        EXPECT_EQ(3, sFakeComposer->getFrameCount());
+        EXPECT_TRUE(framesAreSame(referenceFrame2, sFakeComposer->getLatestFrame()));
+    }
 
     sp<SurfaceComposerClient> mComposerClient;
     sp<SurfaceControl> mBGSurfaceControl;
@@ -430,926 +1356,608 @@
     uint32_t mDisplayWidth;
     uint32_t mDisplayHeight;
 
-    static FakeComposerClient* sFakeComposer;
+    static inline FakeComposerClient* sFakeComposer;
 };
 
-FakeComposerClient* TransactionTest::sFakeComposer;
+using TransactionTest_2_1 = TransactionTest<FakeComposerService_2_1>;
 
-void TransactionTest::SetUpTestCase() {
-    // TODO: See TODO comment at DisplayTest::SetUp for background on
-    // the lifetime of the FakeComposerClient.
-    sFakeComposer = new FakeComposerClient;
-    sp<ComposerClient> client = new ComposerClient(sFakeComposer);
-    sp<IComposer> fakeService = new FakeComposerService(client);
-    (void)fakeService->registerAsService("mock");
-
-    android::hardware::ProcessState::self()->startThreadPool();
-    android::ProcessState::self()->startThreadPool();
-
-    startSurfaceFlinger();
-
-    // Fake composer wants to enable VSync injection
-    sFakeComposer->onSurfaceFlingerStart();
+TEST_F(TransactionTest_2_1, DISABLED_LayerMove) {
+    Test_LayerMove();
 }
 
-void TransactionTest::TearDownTestCase() {
-    // Fake composer needs to release SurfaceComposerClient before the stop.
-    sFakeComposer->onSurfaceFlingerStop();
-    stopSurfaceFlinger();
-    // TODO: This is deleted when the ComposerClient calls
-    // removeClient. Devise better lifetime control.
-    sFakeComposer = nullptr;
+TEST_F(TransactionTest_2_1, DISABLED_LayerResize) {
+    Test_LayerResize();
 }
 
-void TransactionTest::SetUp() {
-    ALOGI("TransactionTest::SetUp");
-    mComposerClient = new SurfaceComposerClient;
-    ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
-
-    ALOGI("TransactionTest::SetUp - display");
-    const auto display = SurfaceComposerClient::getPhysicalDisplayToken(PRIMARY_DISPLAY);
-    ASSERT_FALSE(display == nullptr);
-
-    DisplayInfo info;
-    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayInfo(display, &info));
-
-    mDisplayWidth = info.w;
-    mDisplayHeight = info.h;
-
-    // Background surface
-    mBGSurfaceControl = mComposerClient->createSurface(String8("BG Test Surface"), mDisplayWidth,
-                                                       mDisplayHeight, PIXEL_FORMAT_RGBA_8888, 0);
-    ASSERT_TRUE(mBGSurfaceControl != nullptr);
-    ASSERT_TRUE(mBGSurfaceControl->isValid());
-    fillSurfaceRGBA8(mBGSurfaceControl, BLUE);
-
-    // Foreground surface
-    mFGSurfaceControl = mComposerClient->createSurface(String8("FG Test Surface"), 64, 64,
-                                                       PIXEL_FORMAT_RGBA_8888, 0);
-    ASSERT_TRUE(mFGSurfaceControl != nullptr);
-    ASSERT_TRUE(mFGSurfaceControl->isValid());
-
-    fillSurfaceRGBA8(mFGSurfaceControl, RED);
-
-    Transaction t;
-    t.setDisplayLayerStack(display, 0);
-
-    t.setLayer(mBGSurfaceControl, INT32_MAX - 2);
-    t.show(mBGSurfaceControl);
-
-    t.setLayer(mFGSurfaceControl, INT32_MAX - 1);
-    t.setPosition(mFGSurfaceControl, 64, 64);
-    t.show(mFGSurfaceControl);
-
-    // Synchronous transaction will stop this thread, so we set up a
-    // delayed, off-thread vsync request before closing the
-    // transaction. In the test code this is usually done with
-    // TransactionScope. Leaving here in the 'vanilla' form for
-    // reference.
-    ASSERT_EQ(0, sFakeComposer->getFrameCount());
-    sFakeComposer->runVSyncAfter(1ms);
-    t.apply();
-    sFakeComposer->waitUntilFrame(1);
-
-    // Reference data. This is what the HWC should see.
-    static_assert(BG_LAYER == 0 && FG_LAYER == 1, "Unexpected enum values for array indexing");
-    mBaseFrame.push_back(makeSimpleRect(0u, 0u, mDisplayWidth, mDisplayHeight));
-    mBaseFrame[BG_LAYER].mSwapCount = 1;
-    mBaseFrame.push_back(makeSimpleRect(64, 64, 64 + 64, 64 + 64));
-    mBaseFrame[FG_LAYER].mSwapCount = 1;
-
-    auto frame = sFakeComposer->getFrameRects(0);
-    ASSERT_TRUE(framesAreSame(mBaseFrame, frame));
+TEST_F(TransactionTest_2_1, DISABLED_LayerCrop) {
+    Test_LayerCrop();
 }
 
-void TransactionTest::TearDown() {
-    ALOGD("TransactionTest::TearDown");
-
-    mComposerClient->dispose();
-    mBGSurfaceControl = 0;
-    mFGSurfaceControl = 0;
-    mComposerClient = 0;
-
-    sFakeComposer->runVSyncAndWait();
-    mBaseFrame.clear();
-    sFakeComposer->clearFrames();
-    ASSERT_EQ(0, sFakeComposer->getFrameCount());
-
-    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
-    std::vector<LayerDebugInfo> layers;
-    status_t result = sf->getLayerDebugInfo(&layers);
-    if (result != NO_ERROR) {
-        ALOGE("Failed to get layers %s %d", strerror(-result), result);
-    } else {
-        // If this fails, the test being torn down leaked layers.
-        EXPECT_EQ(0u, layers.size());
-        if (layers.size() > 0) {
-            for (auto layer = layers.begin(); layer != layers.end(); ++layer) {
-                std::cout << to_string(*layer).c_str();
-            }
-            // To ensure the next test has clean slate, will run the class
-            // tear down and setup here.
-            TearDownTestCase();
-            SetUpTestCase();
-        }
-    }
-    ALOGD("TransactionTest::TearDown - complete");
+TEST_F(TransactionTest_2_1, DISABLED_LayerSetLayer) {
+    Test_LayerSetLayer();
 }
 
-TEST_F(TransactionTest, LayerMove) {
-    ALOGD("TransactionTest::LayerMove");
-
-    // The scope opens and closes a global transaction and, at the
-    // same time, makes sure the SurfaceFlinger progresses one frame
-    // after the transaction closes. The results of the transaction
-    // should be available in the latest frame stored by the fake
-    // composer.
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setPosition(mFGSurfaceControl, 128, 128);
-        // NOTE: No changes yet, so vsync will do nothing, HWC does not get any calls.
-        // (How to verify that? Throw in vsync and wait a 2x frame time? Separate test?)
-        //
-        // sFakeComposer->runVSyncAndWait();
-    }
-
-    fillSurfaceRGBA8(mFGSurfaceControl, GREEN);
-    sFakeComposer->runVSyncAndWait();
-
-    ASSERT_EQ(3, sFakeComposer->getFrameCount()); // Make sure the waits didn't time out and there's
-                                                  // no extra frames.
-
-    // NOTE: Frame 0 is produced in the SetUp.
-    auto frame1Ref = mBaseFrame;
-    frame1Ref[FG_LAYER].mDisplayFrame =
-            hwc_rect_t{128, 128, 128 + 64, 128 + 64}; // Top-most layer moves.
-    EXPECT_TRUE(framesAreSame(frame1Ref, sFakeComposer->getFrameRects(1)));
-
-    auto frame2Ref = frame1Ref;
-    frame2Ref[FG_LAYER].mSwapCount++;
-    EXPECT_TRUE(framesAreSame(frame2Ref, sFakeComposer->getFrameRects(2)));
+TEST_F(TransactionTest_2_1, DISABLED_LayerSetLayerOpaque) {
+    Test_LayerSetLayerOpaque();
 }
 
-TEST_F(TransactionTest, LayerResize) {
-    ALOGD("TransactionTest::LayerResize");
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setSize(mFGSurfaceControl, 128, 128);
-    }
-
-    fillSurfaceRGBA8(mFGSurfaceControl, GREEN);
-    sFakeComposer->runVSyncAndWait();
-
-    ASSERT_EQ(3, sFakeComposer->getFrameCount()); // Make sure the waits didn't time out and there's
-                                                  // no extra frames.
-
-    auto frame1Ref = mBaseFrame;
-    // NOTE: The resize should not be visible for frame 1 as there's no buffer with new size posted.
-    EXPECT_TRUE(framesAreSame(frame1Ref, sFakeComposer->getFrameRects(1)));
-
-    auto frame2Ref = frame1Ref;
-    frame2Ref[FG_LAYER].mSwapCount++;
-    frame2Ref[FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 128, 64 + 128};
-    frame2Ref[FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 128.f, 128.f};
-    EXPECT_TRUE(framesAreSame(frame2Ref, sFakeComposer->getFrameRects(2)));
+TEST_F(TransactionTest_2_1, DISABLED_SetLayerStack) {
+    Test_SetLayerStack();
 }
 
-TEST_F(TransactionTest, LayerCrop) {
-    // TODO: Add scaling to confirm that crop happens in buffer space?
-    {
-        TransactionScope ts(*sFakeComposer);
-        Rect cropRect(16, 16, 32, 32);
-        ts.setCrop_legacy(mFGSurfaceControl, cropRect);
-    }
-    ASSERT_EQ(2, sFakeComposer->getFrameCount());
-
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mSourceCrop = hwc_frect_t{16.f, 16.f, 32.f, 32.f};
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{64 + 16, 64 + 16, 64 + 32, 64 + 32};
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+TEST_F(TransactionTest_2_1, DISABLED_LayerShowHide) {
+    Test_LayerShowHide();
 }
 
-TEST_F(TransactionTest, LayerSetLayer) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setLayer(mFGSurfaceControl, INT_MAX - 3);
-    }
-    ASSERT_EQ(2, sFakeComposer->getFrameCount());
-
-    // The layers will switch order, but both are rendered because the background layer is
-    // transparent (RGBA8888).
-    std::vector<RenderState> referenceFrame(2);
-    referenceFrame[0] = mBaseFrame[FG_LAYER];
-    referenceFrame[1] = mBaseFrame[BG_LAYER];
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+TEST_F(TransactionTest_2_1, DISABLED_LayerSetAlpha) {
+    Test_LayerSetAlpha();
 }
 
-TEST_F(TransactionTest, LayerSetLayerOpaque) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setLayer(mFGSurfaceControl, INT_MAX - 3);
-        ts.setFlags(mBGSurfaceControl, layer_state_t::eLayerOpaque,
-                layer_state_t::eLayerOpaque);
-    }
-    ASSERT_EQ(2, sFakeComposer->getFrameCount());
-
-    // The former foreground layer is now covered with opaque layer - it should have disappeared
-    std::vector<RenderState> referenceFrame(1);
-    referenceFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+TEST_F(TransactionTest_2_1, DISABLED_LayerSetFlags) {
+    Test_LayerSetFlags();
 }
 
-TEST_F(TransactionTest, SetLayerStack) {
-    ALOGD("TransactionTest::SetLayerStack");
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setLayerStack(mFGSurfaceControl, 1);
-    }
-
-    // Foreground layer should have disappeared.
-    ASSERT_EQ(2, sFakeComposer->getFrameCount());
-    std::vector<RenderState> refFrame(1);
-    refFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
-    EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame()));
+TEST_F(TransactionTest_2_1, DISABLED_LayerSetMatrix) {
+    Test_LayerSetMatrix();
 }
 
-TEST_F(TransactionTest, LayerShowHide) {
-    ALOGD("TransactionTest::LayerShowHide");
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.hide(mFGSurfaceControl);
-    }
-
-    // Foreground layer should have disappeared.
-    ASSERT_EQ(2, sFakeComposer->getFrameCount());
-    std::vector<RenderState> refFrame(1);
-    refFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
-    EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame()));
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.show(mFGSurfaceControl);
-    }
-
-    // Foreground layer should be back
-    ASSERT_EQ(3, sFakeComposer->getFrameCount());
-    EXPECT_TRUE(framesAreSame(mBaseFrame, sFakeComposer->getLatestFrame()));
+TEST_F(TransactionTest_2_1, DISABLED_DeferredTransaction) {
+    Test_DeferredTransaction();
 }
 
-TEST_F(TransactionTest, LayerSetAlpha) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setAlpha(mFGSurfaceControl, 0.75f);
-    }
-
-    ASSERT_EQ(2, sFakeComposer->getFrameCount());
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mPlaneAlpha = 0.75f;
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+TEST_F(TransactionTest_2_1, DISABLED_SetRelativeLayer) {
+    Test_SetRelativeLayer();
 }
 
-TEST_F(TransactionTest, LayerSetFlags) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setFlags(mFGSurfaceControl, layer_state_t::eLayerHidden,
-                layer_state_t::eLayerHidden);
-    }
+template <typename FakeComposerService>
+class ChildLayerTest : public TransactionTest<FakeComposerService> {
+    using Base = TransactionTest<FakeComposerService>;
 
-    // Foreground layer should have disappeared.
-    ASSERT_EQ(2, sFakeComposer->getFrameCount());
-    std::vector<RenderState> refFrame(1);
-    refFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
-    EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame()));
-}
-
-TEST_F(TransactionTest, LayerSetMatrix) {
-    struct matrixTestData {
-        float matrix[4];
-        hwc_transform_t expectedTransform;
-        hwc_rect_t expectedDisplayFrame;
-    };
-
-    // The matrix operates on the display frame and is applied before
-    // the position is added. So, the foreground layer rect is (0, 0,
-    // 64, 64) is first transformed, potentially yielding negative
-    // coordinates and then the position (64, 64) is added yielding
-    // the final on-screen rectangles given.
-
-    const matrixTestData MATRIX_TESTS[7] = // clang-format off
-            {{{-1.f, 0.f, 0.f, 1.f},    HWC_TRANSFORM_FLIP_H,           {0, 64, 64, 128}},
-             {{1.f, 0.f, 0.f, -1.f},    HWC_TRANSFORM_FLIP_V,           {64, 0, 128, 64}},
-             {{0.f, 1.f, -1.f, 0.f},    HWC_TRANSFORM_ROT_90,           {0, 64, 64, 128}},
-             {{-1.f, 0.f, 0.f, -1.f},   HWC_TRANSFORM_ROT_180,          {0, 0, 64, 64}},
-             {{0.f, -1.f, 1.f, 0.f},    HWC_TRANSFORM_ROT_270,          {64, 0, 128, 64}},
-             {{0.f, 1.f, 1.f, 0.f},     HWC_TRANSFORM_FLIP_H_ROT_90,    {64, 64, 128, 128}},
-             {{0.f, 1.f, 1.f, 0.f},     HWC_TRANSFORM_FLIP_V_ROT_90,    {64, 64, 128, 128}}};
-    // clang-format on
-    constexpr int TEST_COUNT = sizeof(MATRIX_TESTS) / sizeof(matrixTestData);
-
-    for (int i = 0; i < TEST_COUNT; i++) {
-        // TODO: How to leverage the HWC2 stringifiers?
-        const matrixTestData& xform = MATRIX_TESTS[i];
-        SCOPED_TRACE(i);
-        {
-            TransactionScope ts(*sFakeComposer);
-            ts.setMatrix(mFGSurfaceControl, xform.matrix[0], xform.matrix[1],
-                    xform.matrix[2], xform.matrix[3]);
-        }
-
-        auto referenceFrame = mBaseFrame;
-        referenceFrame[FG_LAYER].mTransform = xform.expectedTransform;
-        referenceFrame[FG_LAYER].mDisplayFrame = xform.expectedDisplayFrame;
-
-        EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-    }
-}
-
-#if 0
-TEST_F(TransactionTest, LayerSetMatrix2) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        // TODO: PLEASE SPEC THE FUNCTION!
-        ts.setMatrix(mFGSurfaceControl, 0.11f, 0.123f,
-                -2.33f, 0.22f);
-    }
-    auto referenceFrame = mBaseFrame;
-    // TODO: Is this correct for sure?
-    //referenceFrame[FG_LAYER].mTransform = HWC_TRANSFORM_FLIP_V & HWC_TRANSFORM_ROT_90;
-
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-}
-#endif
-
-TEST_F(TransactionTest, DeferredTransaction) {
-    // Synchronization surface
-    constexpr static int SYNC_LAYER = 2;
-    auto syncSurfaceControl = mComposerClient->createSurface(String8("Sync Test Surface"), 1, 1,
-                                                             PIXEL_FORMAT_RGBA_8888, 0);
-    ASSERT_TRUE(syncSurfaceControl != nullptr);
-    ASSERT_TRUE(syncSurfaceControl->isValid());
-
-    fillSurfaceRGBA8(syncSurfaceControl, DARK_GRAY);
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setLayer(syncSurfaceControl, INT32_MAX - 1);
-        ts.setPosition(syncSurfaceControl, mDisplayWidth - 2, mDisplayHeight - 2);
-        ts.show(syncSurfaceControl);
-    }
-    auto referenceFrame = mBaseFrame;
-    referenceFrame.push_back(makeSimpleRect(mDisplayWidth - 2, mDisplayHeight - 2,
-                                            mDisplayWidth - 1, mDisplayHeight - 1));
-    referenceFrame[SYNC_LAYER].mSwapCount = 1;
-    EXPECT_EQ(2, sFakeComposer->getFrameCount());
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    // set up two deferred transactions on different frames - these should not yield composited
-    // frames
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setAlpha(mFGSurfaceControl, 0.75);
-        ts.deferTransactionUntil_legacy(mFGSurfaceControl, syncSurfaceControl->getHandle(),
-                                        syncSurfaceControl->getSurface()->getNextFrameNumber());
-    }
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setPosition(mFGSurfaceControl, 128, 128);
-        ts.deferTransactionUntil_legacy(mFGSurfaceControl, syncSurfaceControl->getHandle(),
-                                        syncSurfaceControl->getSurface()->getNextFrameNumber() + 1);
-    }
-    EXPECT_EQ(4, sFakeComposer->getFrameCount());
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    // should trigger the first deferred transaction, but not the second one
-    fillSurfaceRGBA8(syncSurfaceControl, DARK_GRAY);
-    sFakeComposer->runVSyncAndWait();
-    EXPECT_EQ(5, sFakeComposer->getFrameCount());
-
-    referenceFrame[FG_LAYER].mPlaneAlpha = 0.75f;
-    referenceFrame[SYNC_LAYER].mSwapCount++;
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    // should show up immediately since it's not deferred
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setAlpha(mFGSurfaceControl, 1.0);
-    }
-    referenceFrame[FG_LAYER].mPlaneAlpha = 1.f;
-    EXPECT_EQ(6, sFakeComposer->getFrameCount());
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    // trigger the second deferred transaction
-    fillSurfaceRGBA8(syncSurfaceControl, DARK_GRAY);
-    sFakeComposer->runVSyncAndWait();
-    // TODO: Compute from layer size?
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{128, 128, 128 + 64, 128 + 64};
-    referenceFrame[SYNC_LAYER].mSwapCount++;
-    EXPECT_EQ(7, sFakeComposer->getFrameCount());
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-}
-
-TEST_F(TransactionTest, SetRelativeLayer) {
-    constexpr int RELATIVE_LAYER = 2;
-    auto relativeSurfaceControl = mComposerClient->createSurface(String8("Test Surface"), 64, 64,
-                                                                 PIXEL_FORMAT_RGBA_8888, 0);
-    fillSurfaceRGBA8(relativeSurfaceControl, LIGHT_RED);
-
-    // Now we stack the surface above the foreground surface and make sure it is visible.
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setPosition(relativeSurfaceControl, 64, 64);
-        ts.show(relativeSurfaceControl);
-        ts.setRelativeLayer(relativeSurfaceControl, mFGSurfaceControl->getHandle(), 1);
-    }
-    auto referenceFrame = mBaseFrame;
-    // NOTE: All three layers will be visible as the surfaces are
-    // transparent because of the RGBA format.
-    referenceFrame.push_back(makeSimpleRect(64, 64, 64 + 64, 64 + 64));
-    referenceFrame[RELATIVE_LAYER].mSwapCount = 1;
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    // A call to setLayer will override a call to setRelativeLayer
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setLayer(relativeSurfaceControl, 0);
-    }
-
-    // Previous top layer will now appear at the bottom.
-    auto referenceFrame2 = mBaseFrame;
-    referenceFrame2.insert(referenceFrame2.begin(), referenceFrame[RELATIVE_LAYER]);
-    EXPECT_EQ(3, sFakeComposer->getFrameCount());
-    EXPECT_TRUE(framesAreSame(referenceFrame2, sFakeComposer->getLatestFrame()));
-}
-
-class ChildLayerTest : public TransactionTest {
 protected:
     constexpr static int CHILD_LAYER = 2;
 
     void SetUp() override {
-        TransactionTest::SetUp();
-        mChild = mComposerClient->createSurface(String8("Child surface"), 10, 10,
-                                                PIXEL_FORMAT_RGBA_8888, 0, mFGSurfaceControl.get());
+        Base::SetUp();
+        mChild = Base::mComposerClient->createSurface(String8("Child surface"), 10, 10,
+                                                      PIXEL_FORMAT_RGBA_8888, 0,
+                                                      Base::mFGSurfaceControl.get());
         fillSurfaceRGBA8(mChild, LIGHT_GRAY);
 
-        sFakeComposer->runVSyncAndWait();
-        mBaseFrame.push_back(makeSimpleRect(64, 64, 64 + 10, 64 + 10));
-        mBaseFrame[CHILD_LAYER].mSwapCount = 1;
-        ASSERT_EQ(2, sFakeComposer->getFrameCount());
-        ASSERT_TRUE(framesAreSame(mBaseFrame, sFakeComposer->getLatestFrame()));
+        Base::sFakeComposer->runVSyncAndWait();
+        Base::mBaseFrame.push_back(makeSimpleRect(64, 64, 64 + 10, 64 + 10));
+        Base::mBaseFrame[CHILD_LAYER].mSwapCount = 1;
+        ASSERT_EQ(2, Base::sFakeComposer->getFrameCount());
+        ASSERT_TRUE(framesAreSame(Base::mBaseFrame, Base::sFakeComposer->getLatestFrame()));
     }
+
     void TearDown() override {
         mChild = 0;
-        TransactionTest::TearDown();
+        Base::TearDown();
+    }
+
+    void Test_Positioning() {
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.show(mChild);
+            ts.setPosition(mChild, 10, 10);
+            // Move to the same position as in the original setup.
+            ts.setPosition(Base::mFGSurfaceControl, 64, 64);
+        }
+
+        auto referenceFrame = Base::mBaseFrame;
+        referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 64, 64 + 64};
+        referenceFrame[CHILD_LAYER].mDisplayFrame =
+                hwc_rect_t{64 + 10, 64 + 10, 64 + 10 + 10, 64 + 10 + 10};
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setPosition(Base::mFGSurfaceControl, 0, 0);
+        }
+
+        auto referenceFrame2 = Base::mBaseFrame;
+        referenceFrame2[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 0 + 64, 0 + 64};
+        referenceFrame2[CHILD_LAYER].mDisplayFrame =
+                hwc_rect_t{0 + 10, 0 + 10, 0 + 10 + 10, 0 + 10 + 10};
+        EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_Cropping() {
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.show(mChild);
+            ts.setPosition(mChild, 0, 0);
+            ts.setPosition(Base::mFGSurfaceControl, 0, 0);
+            ts.setCrop_legacy(Base::mFGSurfaceControl, Rect(0, 0, 5, 5));
+        }
+        // NOTE: The foreground surface would be occluded by the child
+        // now, but is included in the stack because the child is
+        // transparent.
+        auto referenceFrame = Base::mBaseFrame;
+        referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 0 + 5, 0 + 5};
+        referenceFrame[Base::FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 5.f, 5.f};
+        referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 0 + 5, 0 + 5};
+        referenceFrame[CHILD_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 5.f, 5.f};
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_Constraints() {
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.show(mChild);
+            ts.setPosition(Base::mFGSurfaceControl, 0, 0);
+            ts.setPosition(mChild, 63, 63);
+        }
+        auto referenceFrame = Base::mBaseFrame;
+        referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
+        referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{63, 63, 64, 64};
+        referenceFrame[CHILD_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 1.f, 1.f};
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_Scaling() {
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setPosition(Base::mFGSurfaceControl, 0, 0);
+        }
+        auto referenceFrame = Base::mBaseFrame;
+        referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
+        referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setMatrix(Base::mFGSurfaceControl, 2.0, 0, 0, 2.0);
+        }
+
+        auto referenceFrame2 = Base::mBaseFrame;
+        referenceFrame2[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 128, 128};
+        referenceFrame2[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 20, 20};
+        EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_LayerAlpha() {
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.show(mChild);
+            ts.setPosition(mChild, 0, 0);
+            ts.setPosition(Base::mFGSurfaceControl, 0, 0);
+            ts.setAlpha(mChild, 0.5);
+        }
+
+        auto referenceFrame = Base::mBaseFrame;
+        referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
+        referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
+        referenceFrame[CHILD_LAYER].mPlaneAlpha = 0.5f;
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setAlpha(Base::mFGSurfaceControl, 0.5);
+        }
+
+        auto referenceFrame2 = referenceFrame;
+        referenceFrame2[Base::FG_LAYER].mPlaneAlpha = 0.5f;
+        referenceFrame2[CHILD_LAYER].mPlaneAlpha = 0.25f;
+        EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_ReparentChildren() {
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.show(mChild);
+            ts.setPosition(mChild, 10, 10);
+            ts.setPosition(Base::mFGSurfaceControl, 64, 64);
+        }
+        auto referenceFrame = Base::mBaseFrame;
+        referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 64, 64 + 64};
+        referenceFrame[CHILD_LAYER].mDisplayFrame =
+                hwc_rect_t{64 + 10, 64 + 10, 64 + 10 + 10, 64 + 10 + 10};
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.reparentChildren(Base::mFGSurfaceControl, Base::mBGSurfaceControl->getHandle());
+        }
+
+        auto referenceFrame2 = referenceFrame;
+        referenceFrame2[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 64, 64 + 64};
+        referenceFrame2[CHILD_LAYER].mDisplayFrame = hwc_rect_t{10, 10, 10 + 10, 10 + 10};
+        EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_DetachChildrenSameClient() {
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.show(mChild);
+            ts.setPosition(mChild, 10, 10);
+            ts.setPosition(Base::mFGSurfaceControl, 64, 64);
+        }
+
+        auto referenceFrame = Base::mBaseFrame;
+        referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 64, 64 + 64};
+        referenceFrame[CHILD_LAYER].mDisplayFrame =
+                hwc_rect_t{64 + 10, 64 + 10, 64 + 10 + 10, 64 + 10 + 10};
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setPosition(Base::mFGSurfaceControl, 0, 0);
+            ts.detachChildren(Base::mFGSurfaceControl);
+        }
+
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setPosition(Base::mFGSurfaceControl, 64, 64);
+            ts.hide(mChild);
+        }
+
+        std::vector<RenderState> refFrame(2);
+        refFrame[Base::BG_LAYER] = Base::mBaseFrame[Base::BG_LAYER];
+        refFrame[Base::FG_LAYER] = Base::mBaseFrame[Base::FG_LAYER];
+
+        EXPECT_TRUE(framesAreSame(refFrame, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_DetachChildrenDifferentClient() {
+        sp<SurfaceComposerClient> newComposerClient = new SurfaceComposerClient;
+        sp<SurfaceControl> childNewClient =
+                newComposerClient->createSurface(String8("New Child Test Surface"), 10, 10,
+                                                 PIXEL_FORMAT_RGBA_8888, 0,
+                                                 Base::mFGSurfaceControl.get());
+        ASSERT_TRUE(childNewClient != nullptr);
+        ASSERT_TRUE(childNewClient->isValid());
+        fillSurfaceRGBA8(childNewClient, LIGHT_GRAY);
+
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.hide(mChild);
+            ts.show(childNewClient);
+            ts.setPosition(childNewClient, 10, 10);
+            ts.setPosition(Base::mFGSurfaceControl, 64, 64);
+        }
+
+        auto referenceFrame = Base::mBaseFrame;
+        referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 64, 64 + 64};
+        referenceFrame[CHILD_LAYER].mDisplayFrame =
+                hwc_rect_t{64 + 10, 64 + 10, 64 + 10 + 10, 64 + 10 + 10};
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.detachChildren(Base::mFGSurfaceControl);
+            ts.setPosition(Base::mFGSurfaceControl, 0, 0);
+        }
+
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setPosition(Base::mFGSurfaceControl, 64, 64);
+            ts.setPosition(childNewClient, 0, 0);
+            ts.hide(childNewClient);
+        }
+
+        // Nothing should have changed. The child control becomes a no-op
+        // zombie on detach. See comments for detachChildren in the
+        // SurfaceControl.h file.
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_InheritNonTransformScalingFromParent() {
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.show(mChild);
+            ts.setPosition(mChild, 0, 0);
+            ts.setPosition(Base::mFGSurfaceControl, 0, 0);
+        }
+
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setOverrideScalingMode(Base::mFGSurfaceControl,
+                                      NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
+            // We cause scaling by 2.
+            ts.setSize(Base::mFGSurfaceControl, 128, 128);
+        }
+
+        auto referenceFrame = Base::mBaseFrame;
+        referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 128, 128};
+        referenceFrame[Base::FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 64.f, 64.f};
+        referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 20, 20};
+        referenceFrame[CHILD_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 10.f, 10.f};
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    // Regression test for b/37673612
+    void Test_ChildrenWithParentBufferTransform() {
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.show(mChild);
+            ts.setPosition(mChild, 0, 0);
+            ts.setPosition(Base::mFGSurfaceControl, 0, 0);
+        }
+
+        // We set things up as in b/37673612 so that there is a mismatch between the buffer size and
+        // the WM specified state size.
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setSize(Base::mFGSurfaceControl, 128, 64);
+        }
+
+        sp<Surface> s = Base::mFGSurfaceControl->getSurface();
+        auto anw = static_cast<ANativeWindow*>(s.get());
+        native_window_set_buffers_transform(anw, NATIVE_WINDOW_TRANSFORM_ROT_90);
+        native_window_set_buffers_dimensions(anw, 64, 128);
+        fillSurfaceRGBA8(Base::mFGSurfaceControl, RED);
+        Base::sFakeComposer->runVSyncAndWait();
+
+        // The child should still be in the same place and not have any strange scaling as in
+        // b/37673612.
+        auto referenceFrame = Base::mBaseFrame;
+        referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 128, 64};
+        referenceFrame[Base::FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 64.f, 128.f};
+        referenceFrame[Base::FG_LAYER].mSwapCount++;
+        referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_Bug36858924() {
+        // Destroy the child layer
+        mChild.clear();
+
+        // Now recreate it as hidden
+        mChild = Base::mComposerClient->createSurface(String8("Child surface"), 10, 10,
+                                                      PIXEL_FORMAT_RGBA_8888,
+                                                      ISurfaceComposerClient::eHidden,
+                                                      Base::mFGSurfaceControl.get());
+
+        // Show the child layer in a deferred transaction
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.deferTransactionUntil_legacy(mChild, Base::mFGSurfaceControl->getHandle(),
+                                            Base::mFGSurfaceControl->getSurface()
+                                                    ->getNextFrameNumber());
+            ts.show(mChild);
+        }
+
+        // Render the foreground surface a few times
+        //
+        // Prior to the bugfix for b/36858924, this would usually hang while trying to fill the
+        // third frame because SurfaceFlinger would never process the deferred transaction and would
+        // therefore never acquire/release the first buffer
+        ALOGI("Filling 1");
+        fillSurfaceRGBA8(Base::mFGSurfaceControl, GREEN);
+        Base::sFakeComposer->runVSyncAndWait();
+        ALOGI("Filling 2");
+        fillSurfaceRGBA8(Base::mFGSurfaceControl, BLUE);
+        Base::sFakeComposer->runVSyncAndWait();
+        ALOGI("Filling 3");
+        fillSurfaceRGBA8(Base::mFGSurfaceControl, RED);
+        Base::sFakeComposer->runVSyncAndWait();
+        ALOGI("Filling 4");
+        fillSurfaceRGBA8(Base::mFGSurfaceControl, GREEN);
+        Base::sFakeComposer->runVSyncAndWait();
     }
 
     sp<SurfaceControl> mChild;
 };
 
-TEST_F(ChildLayerTest, Positioning) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.show(mChild);
-        ts.setPosition(mChild, 10, 10);
-        // Move to the same position as in the original setup.
-        ts.setPosition(mFGSurfaceControl, 64, 64);
-    }
+using ChildLayerTest_2_1 = ChildLayerTest<FakeComposerService_2_1>;
 
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 64, 64 + 64};
-    referenceFrame[CHILD_LAYER].mDisplayFrame =
-            hwc_rect_t{64 + 10, 64 + 10, 64 + 10 + 10, 64 + 10 + 10};
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setPosition(mFGSurfaceControl, 0, 0);
-    }
-
-    auto referenceFrame2 = mBaseFrame;
-    referenceFrame2[FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 0 + 64, 0 + 64};
-    referenceFrame2[CHILD_LAYER].mDisplayFrame =
-            hwc_rect_t{0 + 10, 0 + 10, 0 + 10 + 10, 0 + 10 + 10};
-    EXPECT_TRUE(framesAreSame(referenceFrame2, sFakeComposer->getLatestFrame()));
+TEST_F(ChildLayerTest_2_1, DISABLED_Positioning) {
+    Test_Positioning();
 }
 
-TEST_F(ChildLayerTest, Cropping) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.show(mChild);
-        ts.setPosition(mChild, 0, 0);
-        ts.setPosition(mFGSurfaceControl, 0, 0);
-        ts.setCrop_legacy(mFGSurfaceControl, Rect(0, 0, 5, 5));
-    }
-    // NOTE: The foreground surface would be occluded by the child
-    // now, but is included in the stack because the child is
-    // transparent.
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 0 + 5, 0 + 5};
-    referenceFrame[FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 5.f, 5.f};
-    referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 0 + 5, 0 + 5};
-    referenceFrame[CHILD_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 5.f, 5.f};
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+TEST_F(ChildLayerTest_2_1, DISABLED_Cropping) {
+    Test_Cropping();
 }
 
-TEST_F(ChildLayerTest, Constraints) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.show(mChild);
-        ts.setPosition(mFGSurfaceControl, 0, 0);
-        ts.setPosition(mChild, 63, 63);
-    }
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
-    referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{63, 63, 64, 64};
-    referenceFrame[CHILD_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 1.f, 1.f};
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+TEST_F(ChildLayerTest_2_1, DISABLED_Constraints) {
+    Test_Constraints();
 }
 
-TEST_F(ChildLayerTest, Scaling) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setPosition(mFGSurfaceControl, 0, 0);
-    }
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
-    referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setMatrix(mFGSurfaceControl, 2.0, 0, 0, 2.0);
-    }
-
-    auto referenceFrame2 = mBaseFrame;
-    referenceFrame2[FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 128, 128};
-    referenceFrame2[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 20, 20};
-    EXPECT_TRUE(framesAreSame(referenceFrame2, sFakeComposer->getLatestFrame()));
+TEST_F(ChildLayerTest_2_1, DISABLED_Scaling) {
+    Test_Scaling();
 }
 
-TEST_F(ChildLayerTest, LayerAlpha) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.show(mChild);
-        ts.setPosition(mChild, 0, 0);
-        ts.setPosition(mFGSurfaceControl, 0, 0);
-        ts.setAlpha(mChild, 0.5);
-    }
-
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
-    referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
-    referenceFrame[CHILD_LAYER].mPlaneAlpha = 0.5f;
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setAlpha(mFGSurfaceControl, 0.5);
-    }
-
-    auto referenceFrame2 = referenceFrame;
-    referenceFrame2[FG_LAYER].mPlaneAlpha = 0.5f;
-    referenceFrame2[CHILD_LAYER].mPlaneAlpha = 0.25f;
-    EXPECT_TRUE(framesAreSame(referenceFrame2, sFakeComposer->getLatestFrame()));
+TEST_F(ChildLayerTest_2_1, DISABLED_LayerAlpha) {
+    Test_LayerAlpha();
 }
 
-TEST_F(ChildLayerTest, ReparentChildren) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.show(mChild);
-        ts.setPosition(mChild, 10, 10);
-        ts.setPosition(mFGSurfaceControl, 64, 64);
-    }
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 64, 64 + 64};
-    referenceFrame[CHILD_LAYER].mDisplayFrame =
-            hwc_rect_t{64 + 10, 64 + 10, 64 + 10 + 10, 64 + 10 + 10};
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.reparentChildren(mFGSurfaceControl, mBGSurfaceControl->getHandle());
-    }
-
-    auto referenceFrame2 = referenceFrame;
-    referenceFrame2[FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 64, 64 + 64};
-    referenceFrame2[CHILD_LAYER].mDisplayFrame = hwc_rect_t{10, 10, 10 + 10, 10 + 10};
-    EXPECT_TRUE(framesAreSame(referenceFrame2, sFakeComposer->getLatestFrame()));
+TEST_F(ChildLayerTest_2_1, DISABLED_ReparentChildren) {
+    Test_ReparentChildren();
 }
 
-TEST_F(ChildLayerTest, DetachChildrenSameClient) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.show(mChild);
-        ts.setPosition(mChild, 10, 10);
-        ts.setPosition(mFGSurfaceControl, 64, 64);
-    }
-
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 64, 64 + 64};
-    referenceFrame[CHILD_LAYER].mDisplayFrame =
-            hwc_rect_t{64 + 10, 64 + 10, 64 + 10 + 10, 64 + 10 + 10};
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setPosition(mFGSurfaceControl, 0, 0);
-        ts.detachChildren(mFGSurfaceControl);
-    }
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setPosition(mFGSurfaceControl, 64, 64);
-        ts.hide(mChild);
-    }
-
-    std::vector<RenderState> refFrame(2);
-    refFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
-    refFrame[FG_LAYER] = mBaseFrame[FG_LAYER];
-
-    EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame()));
+TEST_F(ChildLayerTest_2_1, DISABLED_DetachChildrenSameClient) {
+    Test_DetachChildrenSameClient();
 }
 
-TEST_F(ChildLayerTest, DetachChildrenDifferentClient) {
-    sp<SurfaceComposerClient> newComposerClient = new SurfaceComposerClient;
-    sp<SurfaceControl> childNewClient =
-            newComposerClient->createSurface(String8("New Child Test Surface"), 10, 10,
-                                             PIXEL_FORMAT_RGBA_8888, 0, mFGSurfaceControl.get());
-    ASSERT_TRUE(childNewClient != nullptr);
-    ASSERT_TRUE(childNewClient->isValid());
-    fillSurfaceRGBA8(childNewClient, LIGHT_GRAY);
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.hide(mChild);
-        ts.show(childNewClient);
-        ts.setPosition(childNewClient, 10, 10);
-        ts.setPosition(mFGSurfaceControl, 64, 64);
-    }
-
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 64, 64 + 64};
-    referenceFrame[CHILD_LAYER].mDisplayFrame =
-            hwc_rect_t{64 + 10, 64 + 10, 64 + 10 + 10, 64 + 10 + 10};
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.detachChildren(mFGSurfaceControl);
-        ts.setPosition(mFGSurfaceControl, 0, 0);
-    }
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setPosition(mFGSurfaceControl, 64, 64);
-        ts.setPosition(childNewClient, 0, 0);
-        ts.hide(childNewClient);
-    }
-
-    // Nothing should have changed. The child control becomes a no-op
-    // zombie on detach. See comments for detachChildren in the
-    // SurfaceControl.h file.
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+TEST_F(ChildLayerTest_2_1, DISABLED_DetachChildrenDifferentClient) {
+    Test_DetachChildrenDifferentClient();
 }
 
-TEST_F(ChildLayerTest, InheritNonTransformScalingFromParent) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.show(mChild);
-        ts.setPosition(mChild, 0, 0);
-        ts.setPosition(mFGSurfaceControl, 0, 0);
-    }
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setOverrideScalingMode(mFGSurfaceControl, NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
-        // We cause scaling by 2.
-        ts.setSize(mFGSurfaceControl, 128, 128);
-    }
-
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 128, 128};
-    referenceFrame[FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 64.f, 64.f};
-    referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 20, 20};
-    referenceFrame[CHILD_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 10.f, 10.f};
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+TEST_F(ChildLayerTest_2_1, DISABLED_InheritNonTransformScalingFromParent) {
+    Test_InheritNonTransformScalingFromParent();
 }
 
 // Regression test for b/37673612
-TEST_F(ChildLayerTest, ChildrenWithParentBufferTransform) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.show(mChild);
-        ts.setPosition(mChild, 0, 0);
-        ts.setPosition(mFGSurfaceControl, 0, 0);
-    }
-
-    // We set things up as in b/37673612 so that there is a mismatch between the buffer size and
-    // the WM specified state size.
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setSize(mFGSurfaceControl, 128, 64);
-    }
-
-    sp<Surface> s = mFGSurfaceControl->getSurface();
-    auto anw = static_cast<ANativeWindow*>(s.get());
-    native_window_set_buffers_transform(anw, NATIVE_WINDOW_TRANSFORM_ROT_90);
-    native_window_set_buffers_dimensions(anw, 64, 128);
-    fillSurfaceRGBA8(mFGSurfaceControl, RED);
-    sFakeComposer->runVSyncAndWait();
-
-    // The child should still be in the same place and not have any strange scaling as in
-    // b/37673612.
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 128, 64};
-    referenceFrame[FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 64.f, 128.f};
-    referenceFrame[FG_LAYER].mSwapCount++;
-    referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
+TEST_F(ChildLayerTest_2_1, DISABLED_ChildrenWithParentBufferTransform) {
+    Test_ChildrenWithParentBufferTransform();
 }
 
-TEST_F(ChildLayerTest, Bug36858924) {
-    // Destroy the child layer
-    mChild.clear();
-
-    // Now recreate it as hidden
-    mChild = mComposerClient->createSurface(String8("Child surface"), 10, 10,
-                                            PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eHidden,
-                                            mFGSurfaceControl.get());
-
-    // Show the child layer in a deferred transaction
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.deferTransactionUntil_legacy(mChild, mFGSurfaceControl->getHandle(),
-                                        mFGSurfaceControl->getSurface()->getNextFrameNumber());
-        ts.show(mChild);
-    }
-
-    // Render the foreground surface a few times
-    //
-    // Prior to the bugfix for b/36858924, this would usually hang while trying to fill the third
-    // frame because SurfaceFlinger would never process the deferred transaction and would therefore
-    // never acquire/release the first buffer
-    ALOGI("Filling 1");
-    fillSurfaceRGBA8(mFGSurfaceControl, GREEN);
-    sFakeComposer->runVSyncAndWait();
-    ALOGI("Filling 2");
-    fillSurfaceRGBA8(mFGSurfaceControl, BLUE);
-    sFakeComposer->runVSyncAndWait();
-    ALOGI("Filling 3");
-    fillSurfaceRGBA8(mFGSurfaceControl, RED);
-    sFakeComposer->runVSyncAndWait();
-    ALOGI("Filling 4");
-    fillSurfaceRGBA8(mFGSurfaceControl, GREEN);
-    sFakeComposer->runVSyncAndWait();
+TEST_F(ChildLayerTest_2_1, DISABLED_Bug36858924) {
+    Test_Bug36858924();
 }
 
-class ChildColorLayerTest : public ChildLayerTest {
+template <typename FakeComposerService>
+class ChildColorLayerTest : public ChildLayerTest<FakeComposerService> {
+    using Base = ChildLayerTest<FakeComposerService>;
+
 protected:
     void SetUp() override {
-        TransactionTest::SetUp();
-        mChild = mComposerClient->createSurface(String8("Child surface"), 0, 0,
-                                                PIXEL_FORMAT_RGBA_8888,
-                                                ISurfaceComposerClient::eFXSurfaceColor,
-                                                mFGSurfaceControl.get());
+        Base::SetUp();
+        Base::mChild = Base::mComposerClient->createSurface(String8("Child surface"), 0, 0,
+                                                            PIXEL_FORMAT_RGBA_8888,
+                                                            ISurfaceComposerClient::eFXSurfaceColor,
+                                                            Base::mFGSurfaceControl.get());
         {
-            TransactionScope ts(*sFakeComposer);
-            ts.setColor(mChild,
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setColor(Base::mChild,
                         {LIGHT_GRAY.r / 255.0f, LIGHT_GRAY.g / 255.0f, LIGHT_GRAY.b / 255.0f});
-            ts.setCrop_legacy(mChild, Rect(0, 0, 10, 10));
+            ts.setCrop_legacy(Base::mChild, Rect(0, 0, 10, 10));
         }
 
-        sFakeComposer->runVSyncAndWait();
-        mBaseFrame.push_back(makeSimpleRect(64, 64, 64 + 10, 64 + 10));
-        mBaseFrame[CHILD_LAYER].mSourceCrop = hwc_frect_t{0.0f, 0.0f, 0.0f, 0.0f};
-        mBaseFrame[CHILD_LAYER].mSwapCount = 0;
-        ASSERT_EQ(2, sFakeComposer->getFrameCount());
-        ASSERT_TRUE(framesAreSame(mBaseFrame, sFakeComposer->getLatestFrame()));
+        Base::sFakeComposer->runVSyncAndWait();
+        Base::mBaseFrame.push_back(makeSimpleRect(64, 64, 64 + 10, 64 + 10));
+        Base::mBaseFrame[Base::CHILD_LAYER].mSourceCrop = hwc_frect_t{0.0f, 0.0f, 0.0f, 0.0f};
+        Base::mBaseFrame[Base::CHILD_LAYER].mSwapCount = 0;
+        ASSERT_EQ(2, Base::sFakeComposer->getFrameCount());
+        ASSERT_TRUE(framesAreSame(Base::mBaseFrame, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_LayerAlpha() {
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.show(Base::mChild);
+            ts.setPosition(Base::mChild, 0, 0);
+            ts.setPosition(Base::mFGSurfaceControl, 0, 0);
+            ts.setAlpha(Base::mChild, 0.5);
+        }
+
+        auto referenceFrame = Base::mBaseFrame;
+        referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
+        referenceFrame[Base::CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
+        referenceFrame[Base::CHILD_LAYER].mPlaneAlpha = 0.5f;
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setAlpha(Base::mFGSurfaceControl, 0.5);
+        }
+
+        auto referenceFrame2 = referenceFrame;
+        referenceFrame2[Base::FG_LAYER].mPlaneAlpha = 0.5f;
+        referenceFrame2[Base::CHILD_LAYER].mPlaneAlpha = 0.25f;
+        EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_LayerZeroAlpha() {
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.show(Base::mChild);
+            ts.setPosition(Base::mChild, 0, 0);
+            ts.setPosition(Base::mFGSurfaceControl, 0, 0);
+            ts.setAlpha(Base::mChild, 0.5);
+        }
+
+        auto referenceFrame = Base::mBaseFrame;
+        referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
+        referenceFrame[Base::CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
+        referenceFrame[Base::CHILD_LAYER].mPlaneAlpha = 0.5f;
+        EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
+
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setAlpha(Base::mFGSurfaceControl, 0.0f);
+        }
+
+        std::vector<RenderState> refFrame(1);
+        refFrame[Base::BG_LAYER] = Base::mBaseFrame[Base::BG_LAYER];
+
+        EXPECT_TRUE(framesAreSame(refFrame, Base::sFakeComposer->getLatestFrame()));
     }
 };
 
-TEST_F(ChildColorLayerTest, LayerAlpha) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.show(mChild);
-        ts.setPosition(mChild, 0, 0);
-        ts.setPosition(mFGSurfaceControl, 0, 0);
-        ts.setAlpha(mChild, 0.5);
-    }
+using ChildColorLayerTest_2_1 = ChildColorLayerTest<FakeComposerService_2_1>;
 
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
-    referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
-    referenceFrame[CHILD_LAYER].mPlaneAlpha = 0.5f;
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setAlpha(mFGSurfaceControl, 0.5);
-    }
-
-    auto referenceFrame2 = referenceFrame;
-    referenceFrame2[FG_LAYER].mPlaneAlpha = 0.5f;
-    referenceFrame2[CHILD_LAYER].mPlaneAlpha = 0.25f;
-    EXPECT_TRUE(framesAreSame(referenceFrame2, sFakeComposer->getLatestFrame()));
+TEST_F(ChildColorLayerTest_2_1, DISABLED_LayerAlpha) {
+    Test_LayerAlpha();
 }
 
-TEST_F(ChildColorLayerTest, LayerZeroAlpha) {
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.show(mChild);
-        ts.setPosition(mChild, 0, 0);
-        ts.setPosition(mFGSurfaceControl, 0, 0);
-        ts.setAlpha(mChild, 0.5);
-    }
-
-    auto referenceFrame = mBaseFrame;
-    referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
-    referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
-    referenceFrame[CHILD_LAYER].mPlaneAlpha = 0.5f;
-    EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setAlpha(mFGSurfaceControl, 0.0f);
-    }
-
-    std::vector<RenderState> refFrame(1);
-    refFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
-
-    EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame()));
+TEST_F(ChildColorLayerTest_2_1, DISABLED_LayerZeroAlpha) {
+    Test_LayerZeroAlpha();
 }
 
-class LatchingTest : public TransactionTest {
+template <typename FakeComposerService>
+class LatchingTest : public TransactionTest<FakeComposerService> {
+    using Base = TransactionTest<FakeComposerService>;
+
 protected:
-    void lockAndFillFGBuffer() { fillSurfaceRGBA8(mFGSurfaceControl, RED, false); }
+    void lockAndFillFGBuffer() { fillSurfaceRGBA8(Base::mFGSurfaceControl, RED, false); }
 
     void unlockFGBuffer() {
-        sp<Surface> s = mFGSurfaceControl->getSurface();
+        sp<Surface> s = Base::mFGSurfaceControl->getSurface();
         ASSERT_EQ(NO_ERROR, s->unlockAndPost());
-        sFakeComposer->runVSyncAndWait();
+        Base::sFakeComposer->runVSyncAndWait();
     }
 
     void completeFGResize() {
-        fillSurfaceRGBA8(mFGSurfaceControl, RED);
-        sFakeComposer->runVSyncAndWait();
+        fillSurfaceRGBA8(Base::mFGSurfaceControl, RED);
+        Base::sFakeComposer->runVSyncAndWait();
     }
     void restoreInitialState() {
-        TransactionScope ts(*sFakeComposer);
-        ts.setSize(mFGSurfaceControl, 64, 64);
-        ts.setPosition(mFGSurfaceControl, 64, 64);
-        ts.setCrop_legacy(mFGSurfaceControl, Rect(0, 0, 64, 64));
+        TransactionScope ts(*Base::sFakeComposer);
+        ts.setSize(Base::mFGSurfaceControl, 64, 64);
+        ts.setPosition(Base::mFGSurfaceControl, 64, 64);
+        ts.setCrop_legacy(Base::mFGSurfaceControl, Rect(0, 0, 64, 64));
+    }
+
+    void Test_SurfacePositionLatching() {
+        // By default position can be updated even while
+        // a resize is pending.
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setSize(Base::mFGSurfaceControl, 32, 32);
+            ts.setPosition(Base::mFGSurfaceControl, 100, 100);
+        }
+
+        // The size should not have updated as we have not provided a new buffer.
+        auto referenceFrame1 = Base::mBaseFrame;
+        referenceFrame1[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{100, 100, 100 + 64, 100 + 64};
+        EXPECT_TRUE(framesAreSame(referenceFrame1, Base::sFakeComposer->getLatestFrame()));
+
+        restoreInitialState();
+
+        completeFGResize();
+
+        auto referenceFrame2 = Base::mBaseFrame;
+        referenceFrame2[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{100, 100, 100 + 32, 100 + 32};
+        referenceFrame2[Base::FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 32.f, 32.f};
+        referenceFrame2[Base::FG_LAYER].mSwapCount++;
+        EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame()));
+    }
+
+    void Test_CropLatching() {
+        // Normally the crop applies immediately even while a resize is pending.
+        {
+            TransactionScope ts(*Base::sFakeComposer);
+            ts.setSize(Base::mFGSurfaceControl, 128, 128);
+            ts.setCrop_legacy(Base::mFGSurfaceControl, Rect(0, 0, 63, 63));
+        }
+
+        auto referenceFrame1 = Base::mBaseFrame;
+        referenceFrame1[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 63, 64 + 63};
+        referenceFrame1[Base::FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 63.f, 63.f};
+        EXPECT_TRUE(framesAreSame(referenceFrame1, Base::sFakeComposer->getLatestFrame()));
+
+        restoreInitialState();
+
+        completeFGResize();
+
+        auto referenceFrame2 = Base::mBaseFrame;
+        referenceFrame2[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 63, 64 + 63};
+        referenceFrame2[Base::FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 63.f, 63.f};
+        referenceFrame2[Base::FG_LAYER].mSwapCount++;
+        EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame()));
     }
 };
 
-TEST_F(LatchingTest, SurfacePositionLatching) {
-    // By default position can be updated even while
-    // a resize is pending.
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setSize(mFGSurfaceControl, 32, 32);
-        ts.setPosition(mFGSurfaceControl, 100, 100);
-    }
+using LatchingTest_2_1 = LatchingTest<FakeComposerService_2_1>;
 
-    // The size should not have updated as we have not provided a new buffer.
-    auto referenceFrame1 = mBaseFrame;
-    referenceFrame1[FG_LAYER].mDisplayFrame = hwc_rect_t{100, 100, 100 + 64, 100 + 64};
-    EXPECT_TRUE(framesAreSame(referenceFrame1, sFakeComposer->getLatestFrame()));
-
-    restoreInitialState();
-
-    completeFGResize();
-
-    auto referenceFrame2 = mBaseFrame;
-    referenceFrame2[FG_LAYER].mDisplayFrame = hwc_rect_t{100, 100, 100 + 32, 100 + 32};
-    referenceFrame2[FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 32.f, 32.f};
-    referenceFrame2[FG_LAYER].mSwapCount++;
-    EXPECT_TRUE(framesAreSame(referenceFrame2, sFakeComposer->getLatestFrame()));
+TEST_F(LatchingTest_2_1, DISABLED_SurfacePositionLatching) {
+    Test_SurfacePositionLatching();
 }
 
-TEST_F(LatchingTest, CropLatching) {
-    // Normally the crop applies immediately even while a resize is pending.
-    {
-        TransactionScope ts(*sFakeComposer);
-        ts.setSize(mFGSurfaceControl, 128, 128);
-        ts.setCrop_legacy(mFGSurfaceControl, Rect(0, 0, 63, 63));
-    }
-
-    auto referenceFrame1 = mBaseFrame;
-    referenceFrame1[FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 63, 64 + 63};
-    referenceFrame1[FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 63.f, 63.f};
-    EXPECT_TRUE(framesAreSame(referenceFrame1, sFakeComposer->getLatestFrame()));
-
-    restoreInitialState();
-
-    completeFGResize();
-
-    auto referenceFrame2 = mBaseFrame;
-    referenceFrame2[FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 63, 64 + 63};
-    referenceFrame2[FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 63.f, 63.f};
-    referenceFrame2[FG_LAYER].mSwapCount++;
-    EXPECT_TRUE(framesAreSame(referenceFrame2, sFakeComposer->getLatestFrame()));
+TEST_F(LatchingTest_2_1, DISABLED_CropLatching) {
+    Test_CropLatching();
 }
 
 } // namespace
@@ -1357,7 +1965,7 @@
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
 
-    sftest::FakeHwcEnvironment* fakeEnvironment = new sftest::FakeHwcEnvironment;
+    auto* fakeEnvironment = new sftest::FakeHwcEnvironment;
     ::testing::AddGlobalTestEnvironment(fakeEnvironment);
     ::testing::InitGoogleMock(&argc, argv);
     return RUN_ALL_TESTS();
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 0c4a752..ccfa6c5 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -57,6 +57,7 @@
         "VSyncDispatchTimerQueueTest.cpp",
         "VSyncDispatchRealtimeTest.cpp",
         "VSyncPredictorTest.cpp",
+        "VSyncReactorTest.cpp",
         "mock/DisplayHardware/MockComposer.cpp",
         "mock/DisplayHardware/MockDisplay.cpp",
         "mock/DisplayHardware/MockPowerAdvisor.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 32f997f..76dea62 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -33,13 +33,13 @@
 #include "BufferQueueLayer.h"
 #include "ColorLayer.h"
 #include "Layer.h"
-
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/MockDispSync.h"
 #include "mock/MockEventControlThread.h"
 #include "mock/MockEventThread.h"
 #include "mock/MockMessageQueue.h"
+#include "mock/MockTimeStats.h"
 #include "mock/system/window/MockNativeWindow.h"
 
 namespace android {
@@ -100,6 +100,7 @@
                 .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_HEIGHT), Return(0)));
 
         mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+        mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
         setupComposer(0);
     }
 
@@ -181,6 +182,7 @@
 
     Hwc2::mock::Composer* mComposer = nullptr;
     renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+    mock::TimeStats* mTimeStats = new mock::TimeStats();
     mock::MessageQueue* mMessageQueue = new mock::MessageQueue();
 
     sp<Fence> mClientTargetAcquireFence = Fence::NO_FENCE;
@@ -359,7 +361,7 @@
 
     static void setupRECompositionCallExpectations(CompositionTest* test) {
         EXPECT_CALL(*test->mDisplaySurface,
-                    prepareFrame(compositionengine::DisplaySurface::COMPOSITION_GLES))
+                    prepareFrame(compositionengine::DisplaySurface::COMPOSITION_GPU))
                 .Times(1);
         EXPECT_CALL(*test->mDisplaySurface, getClientTargetAcquireFence())
                 .WillRepeatedly(ReturnRef(test->mClientTargetAcquireFence));
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index b1a4951..76e8171 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -469,6 +469,10 @@
                     getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
                                         IComposerClient::Attribute::DPI_Y, _))
                 .WillOnce(DoAll(SetArgPointee<3>(DEFAULT_DPI), Return(Error::NONE)));
+        EXPECT_CALL(*test->mComposer,
+                    getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
+                                        IComposerClient::Attribute::CONFIG_GROUP, _))
+                .WillOnce(DoAll(SetArgPointee<3>(-1), Return(Error::NONE)));
 
         if (PhysicalDisplay::HAS_IDENTIFICATION_DATA) {
             EXPECT_CALL(*test->mComposer, getDisplayIdentificationData(HWC_DISPLAY_ID, _, _))
@@ -1423,7 +1427,7 @@
     // Note: This is not Case::Display::HWC_ACTIVE_CONFIG_ID as the ids are
     // remapped, and the test only ever sets up one config. If there were an error
     // looking up the remapped index, device->getActiveConfig() would be -1 instead.
-    EXPECT_EQ(0, device->getActiveConfig());
+    EXPECT_EQ(0, device->getActiveConfig().value());
     EXPECT_EQ(Case::PerFrameMetadataSupport::PER_FRAME_METADATA_KEYS,
               device->getSupportedPerFrameMetadata());
 }
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 2662f52..80bca02 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -26,6 +26,7 @@
 
 #include "AsyncCallRecorder.h"
 #include "Scheduler/EventThread.h"
+#include "Scheduler/HwcStrongTypes.h"
 
 using namespace std::chrono_literals;
 using namespace std::placeholders;
@@ -34,6 +35,7 @@
 using testing::Invoke;
 
 namespace android {
+
 namespace {
 
 constexpr PhysicalDisplayId INTERNAL_DISPLAY_ID = 111;
@@ -448,17 +450,17 @@
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary) {
-    mThread->onConfigChanged(INTERNAL_DISPLAY_ID, 7);
+    mThread->onConfigChanged(INTERNAL_DISPLAY_ID, HwcConfigIndexType(7));
     expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 7);
 }
 
 TEST_F(EventThreadTest, postConfigChangedExternal) {
-    mThread->onConfigChanged(EXTERNAL_DISPLAY_ID, 5);
+    mThread->onConfigChanged(EXTERNAL_DISPLAY_ID, HwcConfigIndexType(5));
     expectConfigChangedEventReceivedByConnection(EXTERNAL_DISPLAY_ID, 5);
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary64bit) {
-    mThread->onConfigChanged(DISPLAY_ID_64BIT, 7);
+    mThread->onConfigChanged(DISPLAY_ID_64BIT, HwcConfigIndexType(7));
     expectConfigChangedEventReceivedByConnection(DISPLAY_ID_64BIT, 7);
 }
 
@@ -468,7 +470,7 @@
             createConnection(suppressConnectionEventRecorder,
                              ISurfaceComposer::eConfigChangedSuppress);
 
-    mThread->onConfigChanged(INTERNAL_DISPLAY_ID, 9);
+    mThread->onConfigChanged(INTERNAL_DISPLAY_ID, HwcConfigIndexType(9));
     expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 9);
 
     auto args = suppressConnectionEventRecorder.waitForCall();
diff --git a/services/surfaceflinger/tests/unittests/FakePhaseOffsets.h b/services/surfaceflinger/tests/unittests/FakePhaseOffsets.h
index 66c7f6b..da4eea0 100644
--- a/services/surfaceflinger/tests/unittests/FakePhaseOffsets.h
+++ b/services/surfaceflinger/tests/unittests/FakePhaseOffsets.h
@@ -25,16 +25,16 @@
 struct FakePhaseOffsets : PhaseOffsets {
     static constexpr nsecs_t FAKE_PHASE_OFFSET_NS = 0;
 
-    Offsets getOffsetsForRefreshRate(RefreshRateType) const override { return getCurrentOffsets(); }
+    Offsets getOffsetsForRefreshRate(float) const override { return getCurrentOffsets(); }
 
     Offsets getCurrentOffsets() const override {
-        return {{RefreshRateType::DEFAULT, FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
-                {RefreshRateType::DEFAULT, FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
-                {RefreshRateType::DEFAULT, FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
+        return {{FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
+                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
+                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
                 FAKE_PHASE_OFFSET_NS};
     }
 
-    void setRefreshRateType(RefreshRateType) override {}
+    void setRefreshRateFps(float) override {}
     void dump(std::string&) const override {}
 };
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index e93d31e..d95252b 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -44,9 +44,15 @@
     auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
 
     RefreshRateConfigs mConfigs{true,
-                                {RefreshRateConfigs::InputConfig{0, LO_FPS_PERIOD},
-                                 RefreshRateConfigs::InputConfig{1, HI_FPS_PERIOD}},
-                                0};
+                                {
+                                        RefreshRateConfigs::InputConfig{HwcConfigIndexType(0),
+                                                                        HwcConfigGroupType(0),
+                                                                        LO_FPS_PERIOD},
+                                        RefreshRateConfigs::InputConfig{HwcConfigIndexType(1),
+                                                                        HwcConfigGroupType(0),
+                                                                        HI_FPS_PERIOD},
+                                },
+                                HwcConfigIndexType(0)};
     TestableScheduler* const mScheduler{new TestableScheduler(mConfigs)};
     TestableSurfaceFlinger mFlinger;
 
@@ -57,7 +63,6 @@
 
 TEST_F(LayerHistoryTest, oneLayer) {
     const auto layer = createLayer();
-    constexpr bool isHDR = false;
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
 
     EXPECT_EQ(1, layerCount());
@@ -69,14 +74,14 @@
 
     // 0 FPS is returned if active layers have insufficient history.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
-        history().record(layer.get(), 0, isHDR, mTime);
+        history().record(layer.get(), 0, mTime);
         EXPECT_FLOAT_EQ(0, history().summarize(mTime).maxRefreshRate);
         EXPECT_EQ(1, activeLayerCount());
     }
 
     // High FPS is returned once enough history has been recorded.
     for (int i = 0; i < 10; i++) {
-        history().record(layer.get(), 0, isHDR, mTime);
+        history().record(layer.get(), 0, mTime);
         EXPECT_FLOAT_EQ(HI_FPS, history().summarize(mTime).maxRefreshRate);
         EXPECT_EQ(1, activeLayerCount());
     }
@@ -84,29 +89,25 @@
 
 TEST_F(LayerHistoryTest, oneHDRLayer) {
     const auto layer = createLayer();
-    constexpr bool isHDR = true;
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
 
-    history().record(layer.get(), 0, isHDR, mTime);
+    history().record(layer.get(), 0, mTime);
     auto summary = history().summarize(mTime);
     EXPECT_FLOAT_EQ(0, summary.maxRefreshRate);
-    EXPECT_TRUE(summary.isHDR);
     EXPECT_EQ(1, activeLayerCount());
 
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
 
     summary = history().summarize(mTime);
     EXPECT_FLOAT_EQ(0, summary.maxRefreshRate);
-    EXPECT_FALSE(summary.isHDR);
     EXPECT_EQ(0, activeLayerCount());
 }
 
 TEST_F(LayerHistoryTest, explicitTimestamp) {
     const auto layer = createLayer();
-    constexpr bool isHDR = false;
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
 
     EXPECT_EQ(1, layerCount());
@@ -114,7 +115,7 @@
 
     nsecs_t time = mTime;
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, isHDR, time);
+        history().record(layer.get(), time, time);
         time += LO_FPS_PERIOD;
     }
 
@@ -127,7 +128,6 @@
     auto layer1 = createLayer();
     auto layer2 = createLayer();
     auto layer3 = createLayer();
-    constexpr bool isHDR = false;
 
     EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
@@ -141,7 +141,7 @@
 
     // layer1 is active but infrequent.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer1.get(), time, isHDR, time);
+        history().record(layer1.get(), time, time);
         time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
     }
 
@@ -151,12 +151,12 @@
 
     // layer2 is frequent and has high refresh rate.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2.get(), time, isHDR, time);
+        history().record(layer2.get(), time, time);
         time += HI_FPS_PERIOD;
     }
 
     // layer1 is still active but infrequent.
-    history().record(layer1.get(), time, isHDR, time);
+    history().record(layer1.get(), time, time);
 
     EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
@@ -165,7 +165,7 @@
     // layer1 is no longer active.
     // layer2 is frequent and has low refresh rate.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2.get(), time, isHDR, time);
+        history().record(layer2.get(), time, time);
         time += LO_FPS_PERIOD;
     }
 
@@ -178,10 +178,10 @@
     constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
         if (i % RATIO == 0) {
-            history().record(layer2.get(), time, isHDR, time);
+            history().record(layer2.get(), time, time);
         }
 
-        history().record(layer3.get(), time, isHDR, time);
+        history().record(layer3.get(), time, time);
         time += HI_FPS_PERIOD;
     }
 
@@ -190,7 +190,7 @@
     EXPECT_EQ(2, frequentLayerCount(time));
 
     // layer3 becomes recently active.
-    history().record(layer3.get(), time, isHDR, time);
+    history().record(layer3.get(), time, time);
     EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -205,7 +205,7 @@
     // layer2 still has low refresh rate.
     // layer3 becomes inactive.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2.get(), time, isHDR, time);
+        history().record(layer2.get(), time, time);
         time += LO_FPS_PERIOD;
     }
 
@@ -222,7 +222,7 @@
 
     // layer3 becomes active and has high refresh rate.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer3.get(), time, isHDR, time);
+        history().record(layer3.get(), time, time);
         time += HI_FPS_PERIOD;
     }
 
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index f315a8a..546e65c 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -30,27 +30,19 @@
 namespace android {
 namespace scheduler {
 
-using RefreshRateType = RefreshRateConfigs::RefreshRateType;
 using RefreshRate = RefreshRateConfigs::RefreshRate;
 
 class RefreshRateConfigsTest : public testing::Test {
 protected:
-    static constexpr int CONFIG_ID_60 = 0;
-    static constexpr hwc2_config_t HWC2_CONFIG_ID_60 = 0;
-    static constexpr int CONFIG_ID_90 = 1;
-    static constexpr hwc2_config_t HWC2_CONFIG_ID_90 = 1;
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_60 = HwcConfigIndexType(0);
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_90 = HwcConfigIndexType(1);
+    static inline const HwcConfigGroupType HWC_GROUP_ID_0 = HwcConfigGroupType(0);
+    static inline const HwcConfigGroupType HWC_GROUP_ID_1 = HwcConfigGroupType(1);
     static constexpr int64_t VSYNC_60 = 16666667;
     static constexpr int64_t VSYNC_90 = 11111111;
 
     RefreshRateConfigsTest();
     ~RefreshRateConfigsTest();
-
-    void assertRatesEqual(const RefreshRate& left, const RefreshRate& right) {
-        ASSERT_EQ(left.configId, right.configId);
-        ASSERT_EQ(left.name, right.name);
-        ASSERT_EQ(left.fps, right.fps);
-        ASSERT_EQ(left.vsyncPeriod, right.vsyncPeriod);
-    }
 };
 
 RefreshRateConfigsTest::RefreshRateConfigsTest() {
@@ -69,40 +61,173 @@
 /* ------------------------------------------------------------------------
  * Test cases
  */
-TEST_F(RefreshRateConfigsTest, oneDeviceConfig_isRejected) {
-    std::vector<RefreshRateConfigs::InputConfig> configs{{HWC2_CONFIG_ID_60, VSYNC_60}};
+TEST_F(RefreshRateConfigsTest, oneDeviceConfig_SwitchingSupported) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60}}};
     auto refreshRateConfigs =
             std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
-                                                 /*currentConfig=*/0);
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+}
+
+TEST_F(RefreshRateConfigsTest, oneDeviceConfig_SwitchingNotSupported) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/false, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
     ASSERT_FALSE(refreshRateConfigs->refreshRateSwitchingSupported());
 }
 
 TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap) {
-    std::vector<RefreshRateConfigs::InputConfig> configs{{HWC2_CONFIG_ID_60, VSYNC_60},
-                                                         {HWC2_CONFIG_ID_90, VSYNC_90}};
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
     auto refreshRateConfigs =
             std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
-                                                 /*currentConfig=*/0);
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
 
     ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
-    const auto& rates = refreshRateConfigs->getRefreshRateMap();
-    ASSERT_EQ(2, rates.size());
-    const auto& defaultRate = rates.find(RefreshRateType::DEFAULT);
-    const auto& performanceRate = rates.find(RefreshRateType::PERFORMANCE);
-    ASSERT_NE(rates.end(), defaultRate);
-    ASSERT_NE(rates.end(), performanceRate);
 
-    RefreshRate expectedDefaultConfig = {CONFIG_ID_60, "60fps", 60, VSYNC_60, HWC2_CONFIG_ID_60};
-    assertRatesEqual(expectedDefaultConfig, defaultRate->second);
-    RefreshRate expectedPerformanceConfig = {CONFIG_ID_90, "90fps", 90, VSYNC_90,
-                                             HWC2_CONFIG_ID_90};
-    assertRatesEqual(expectedPerformanceConfig, performanceRate->second);
+    const auto minRate = refreshRateConfigs->getMinRefreshRate();
+    const auto performanceRate = refreshRateConfigs->getMaxRefreshRate();
 
-    assertRatesEqual(expectedDefaultConfig,
-                     refreshRateConfigs->getRefreshRateFromType(RefreshRateType::DEFAULT));
-    assertRatesEqual(expectedPerformanceConfig,
-                     refreshRateConfigs->getRefreshRateFromType(RefreshRateType::PERFORMANCE));
+    RefreshRate expectedDefaultConfig = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    ASSERT_EQ(expectedDefaultConfig, minRate);
+    RefreshRate expectedPerformanceConfig = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps",
+                                             90};
+    ASSERT_EQ(expectedPerformanceConfig, performanceRate);
+
+    const auto minRateByPolicy = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto performanceRateByPolicy = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    ASSERT_EQ(minRateByPolicy, minRate);
+    ASSERT_EQ(performanceRateByPolicy, performanceRate);
 }
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap_differentGroups) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_1, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+    const auto minRate = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto performanceRate = refreshRateConfigs->getMaxRefreshRate();
+    const auto minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+
+    RefreshRate expectedDefaultConfig = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    ASSERT_EQ(expectedDefaultConfig, minRate);
+    ASSERT_EQ(expectedDefaultConfig, minRate60);
+    ASSERT_EQ(expectedDefaultConfig, performanceRate60);
+
+    refreshRateConfigs->setPolicy(HWC_CONFIG_ID_90, 60, 90);
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+    const auto minRate90 = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto performanceRate90 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+
+    RefreshRate expectedPerformanceConfig = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_1, "90fps",
+                                             90};
+    ASSERT_EQ(expectedPerformanceConfig, performanceRate);
+    ASSERT_EQ(expectedPerformanceConfig, minRate90);
+    ASSERT_EQ(expectedPerformanceConfig, performanceRate90);
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_policyChange) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+    auto minRate = refreshRateConfigs->getMinRefreshRateByPolicy();
+    auto performanceRate = refreshRateConfigs->getMaxRefreshRateByPolicy();
+
+    RefreshRate expectedDefaultConfig = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    ASSERT_EQ(expectedDefaultConfig, minRate);
+    RefreshRate expectedPerformanceConfig = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps",
+                                             90};
+    ASSERT_EQ(expectedPerformanceConfig, performanceRate);
+
+    refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 60, 60);
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    auto minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
+    auto performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    ASSERT_EQ(expectedDefaultConfig, minRate60);
+    ASSERT_EQ(expectedDefaultConfig, performanceRate60);
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getCurrentRefreshRate) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    {
+        auto current = refreshRateConfigs->getCurrentRefreshRate();
+        EXPECT_EQ(current.configId, HWC_CONFIG_ID_60);
+    }
+
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
+    {
+        auto current = refreshRateConfigs->getCurrentRefreshRate();
+        EXPECT_EQ(current.configId, HWC_CONFIG_ID_90);
+    }
+
+    refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 90, 90);
+    {
+        auto current = refreshRateConfigs->getCurrentRefreshRate();
+        EXPECT_EQ(current.configId, HWC_CONFIG_ID_90);
+    }
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContent) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(90.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(60.0f));
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(45.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(30.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
+
+    refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 60, 60);
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(90.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(60.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(45.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(30.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
+
+    refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 90, 90);
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(90.0f));
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(60.0f));
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(45.0f));
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(30.0f));
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
+    refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 0, 120);
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(90.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(60.0f));
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(45.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(30.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
+}
+
 } // namespace
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
index cec0b32..ef4699f 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
@@ -33,8 +33,9 @@
 
 class RefreshRateStatsTest : public testing::Test {
 protected:
-    static constexpr int CONFIG_ID_90 = 0;
-    static constexpr int CONFIG_ID_60 = 1;
+    static inline const auto CONFIG_ID_0 = HwcConfigIndexType(0);
+    static inline const auto CONFIG_ID_1 = HwcConfigIndexType(1);
+    static inline const auto CONFIG_GROUP_0 = HwcConfigGroupType(0);
     static constexpr int64_t VSYNC_90 = 11111111;
     static constexpr int64_t VSYNC_60 = 16666667;
 
@@ -43,10 +44,10 @@
 
     void init(const std::vector<RefreshRateConfigs::InputConfig>& configs) {
         mRefreshRateConfigs = std::make_unique<RefreshRateConfigs>(
-                /*refreshRateSwitching=*/true, configs, /*currentConfig=*/0);
+                /*refreshRateSwitching=*/true, configs, /*currentConfig=*/CONFIG_ID_0);
         mRefreshRateStats =
                 std::make_unique<RefreshRateStats>(*mRefreshRateConfigs, mTimeStats,
-                                                   /*currentConfig=*/0,
+                                                   /*currentConfigId=*/CONFIG_ID_0,
                                                    /*currentPowerMode=*/HWC_POWER_MODE_OFF);
     }
 
@@ -72,7 +73,7 @@
  * Test cases
  */
 TEST_F(RefreshRateStatsTest, oneConfigTest) {
-    init({{CONFIG_ID_90, VSYNC_90}});
+    init({{{CONFIG_ID_0, CONFIG_GROUP_0, VSYNC_90}}});
 
     EXPECT_CALL(mTimeStats, recordRefreshRate(0, _)).Times(AtLeast(1));
     EXPECT_CALL(mTimeStats, recordRefreshRate(90, _)).Times(AtLeast(1));
@@ -91,7 +92,7 @@
     EXPECT_LT(screenOff, times["ScreenOff"]);
     EXPECT_EQ(0u, times.count("90fps"));
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_90);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
     mRefreshRateStats->setPowerMode(HWC_POWER_MODE_NORMAL);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
@@ -107,7 +108,7 @@
     EXPECT_LT(screenOff, times["ScreenOff"]);
     EXPECT_EQ(ninety, times["90fps"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_90);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -118,7 +119,7 @@
 }
 
 TEST_F(RefreshRateStatsTest, twoConfigsTest) {
-    init({{CONFIG_ID_90, VSYNC_90}, {CONFIG_ID_60, VSYNC_60}});
+    init({{{CONFIG_ID_0, CONFIG_GROUP_0, VSYNC_90}, {CONFIG_ID_1, CONFIG_GROUP_0, VSYNC_60}}});
 
     EXPECT_CALL(mTimeStats, recordRefreshRate(0, _)).Times(AtLeast(1));
     EXPECT_CALL(mTimeStats, recordRefreshRate(60, _)).Times(AtLeast(1));
@@ -137,7 +138,7 @@
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_LT(screenOff, times["ScreenOff"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_90);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
     mRefreshRateStats->setPowerMode(HWC_POWER_MODE_NORMAL);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
@@ -147,7 +148,7 @@
     EXPECT_LT(0, times["90fps"]);
 
     // When power mode is normal, time for configs updates.
-    mRefreshRateStats->setConfigMode(CONFIG_ID_60);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_1);
     int ninety = mRefreshRateStats->getTotalTimes()["90fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -156,7 +157,7 @@
     ASSERT_EQ(1u, times.count("60fps"));
     EXPECT_LT(0, times["60fps"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_90);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
     int sixty = mRefreshRateStats->getTotalTimes()["60fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -164,7 +165,7 @@
     EXPECT_LT(ninety, times["90fps"]);
     EXPECT_EQ(sixty, times["60fps"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_60);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_1);
     ninety = mRefreshRateStats->getTotalTimes()["90fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -175,7 +176,7 @@
     // Because the power mode is not HWC_POWER_MODE_NORMAL, switching the config
     // does not update refresh rates that come from the config.
     mRefreshRateStats->setPowerMode(HWC_POWER_MODE_DOZE);
-    mRefreshRateStats->setConfigMode(CONFIG_ID_90);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
     sixty = mRefreshRateStats->getTotalTimes()["60fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -183,7 +184,7 @@
     EXPECT_EQ(ninety, times["90fps"]);
     EXPECT_EQ(sixty, times["60fps"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_60);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_1);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index b4cc1e1..40536ab 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -50,10 +50,11 @@
             ::testing::UnitTest::GetInstance()->current_test_info();
     ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
 
-    std::vector<scheduler::RefreshRateConfigs::InputConfig> configs{{/*hwcId=*/0, 16666667}};
-    mRefreshRateConfigs =
-            std::make_unique<scheduler::RefreshRateConfigs>(/*refreshRateSwitching=*/false, configs,
-                                                            /*currentConfig=*/0);
+    std::vector<scheduler::RefreshRateConfigs::InputConfig> configs{
+            {{HwcConfigIndexType(0), HwcConfigGroupType(0), 16666667}}};
+    mRefreshRateConfigs = std::make_unique<
+            scheduler::RefreshRateConfigs>(/*refreshRateSwitching=*/false, configs,
+                                           /*currentConfig=*/HwcConfigIndexType(0));
 
     mScheduler = std::make_unique<TestableScheduler>(*mRefreshRateConfigs);
 
diff --git a/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp b/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp
index b9ddcd7..5406879 100644
--- a/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp
@@ -56,18 +56,18 @@
     EXPECT_THAT(f1 + f2, Eq(FunkyType(32)));
     EXPECT_THAT(f2 + f1, Eq(FunkyType(32)));
 
-    EXPECT_THAT(++f1, Eq(11));
-    EXPECT_THAT(f1, Eq(11));
-    EXPECT_THAT(f1++, Eq(11));
-    EXPECT_THAT(f1++, Eq(12));
-    EXPECT_THAT(f1, Eq(13));
+    EXPECT_THAT(++f1.value(), Eq(11));
+    EXPECT_THAT(f1.value(), Eq(11));
+    EXPECT_THAT(f1++.value(), Eq(11));
+    EXPECT_THAT(f1++.value(), Eq(12));
+    EXPECT_THAT(f1.value(), Eq(13));
 
     auto f3 = f1;
     EXPECT_THAT(f1, Eq(f3));
     EXPECT_THAT(f1, Lt(f2));
 
     f3 += f1;
-    EXPECT_THAT(f1, Eq(13));
-    EXPECT_THAT(f3, Eq(26));
+    EXPECT_THAT(f1.value(), Eq(13));
+    EXPECT_THAT(f3.value(), Eq(26));
 }
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 40c00c4..fa095aa 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -26,17 +26,17 @@
 
 namespace android {
 
-class TestableScheduler : public Scheduler {
+class TestableScheduler : public Scheduler, private ISchedulerCallback {
 public:
     explicit TestableScheduler(const scheduler::RefreshRateConfigs& configs)
-          : Scheduler([](bool) {}, configs) {
+          : Scheduler([](bool) {}, configs, *this) {
         mLayerHistory.emplace();
     }
 
     TestableScheduler(std::unique_ptr<DispSync> primaryDispSync,
                       std::unique_ptr<EventControlThread> eventControlThread,
                       const scheduler::RefreshRateConfigs& configs)
-          : Scheduler(std::move(primaryDispSync), std::move(eventControlThread), configs) {
+          : Scheduler(std::move(primaryDispSync), std::move(eventControlThread), configs, *this) {
         mLayerHistory.emplace();
     }
 
@@ -68,6 +68,10 @@
         mutablePrimaryDispSync().reset();
         mConnections.clear();
     }
+
+private:
+    void changeRefreshRate(const RefreshRate&, ConfigEvent) override {}
+    void repaintEverythingForHWC() override {}
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 94fc5f7..f057c98 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -84,7 +84,8 @@
     }
 
     std::unique_ptr<Scheduler> createScheduler(std::function<void(bool)>,
-                                               const scheduler::RefreshRateConfigs&) override {
+                                               const scheduler::RefreshRateConfigs&,
+                                               ISchedulerCallback&) override {
         return nullptr;
     }
 
@@ -189,19 +190,23 @@
                 std::make_unique<impl::HWComposer>(std::move(composer)));
     }
 
+    void setupTimeStats(const std::shared_ptr<TimeStats>& timeStats) {
+        mFlinger->mCompositionEngine->setTimeStats(timeStats);
+    }
+
     void setupScheduler(std::unique_ptr<DispSync> primaryDispSync,
                         std::unique_ptr<EventControlThread> eventControlThread,
                         std::unique_ptr<EventThread> appEventThread,
                         std::unique_ptr<EventThread> sfEventThread) {
-        std::vector<scheduler::RefreshRateConfigs::InputConfig> configs{{/*hwcId=*/0, 16666667}};
-        mFlinger->mRefreshRateConfigs =
-                std::make_unique<scheduler::RefreshRateConfigs>(/*refreshRateSwitching=*/false,
-                                                                configs, /*currentConfig=*/0);
-        mFlinger->mRefreshRateStats =
-                std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mRefreshRateConfigs,
-                                                              *mFlinger->mTimeStats,
-                                                              /*currentConfig=*/0,
-                                                              /*powerMode=*/HWC_POWER_MODE_OFF);
+        std::vector<scheduler::RefreshRateConfigs::InputConfig> configs{
+                {{HwcConfigIndexType(0), HwcConfigGroupType(0), 16666667}}};
+        mFlinger->mRefreshRateConfigs = std::make_unique<
+                scheduler::RefreshRateConfigs>(/*refreshRateSwitching=*/false, configs,
+                                               /*currentConfig=*/HwcConfigIndexType(0));
+        mFlinger->mRefreshRateStats = std::make_unique<
+                scheduler::RefreshRateStats>(*mFlinger->mRefreshRateConfigs, *mFlinger->mTimeStats,
+                                             /*currentConfig=*/HwcConfigIndexType(0),
+                                             /*powerMode=*/HWC_POWER_MODE_OFF);
 
         mScheduler =
                 new TestableScheduler(std::move(primaryDispSync), std::move(eventControlThread),
@@ -388,6 +393,11 @@
     auto& mutableInternalHwcDisplayId() { return getHwComposer().mInternalHwcDisplayId; }
     auto& mutableExternalHwcDisplayId() { return getHwComposer().mExternalHwcDisplayId; }
 
+    auto fromHandle(const sp<IBinder>& handle) {
+        Mutex::Autolock _l(mFlinger->mStateLock);
+        return mFlinger->fromHandle(handle);
+    }
+
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
         // not report a leaked object, since the SurfaceFlinger instance may
@@ -429,6 +439,7 @@
         static constexpr int32_t DEFAULT_WIDTH = 1920;
         static constexpr int32_t DEFAULT_HEIGHT = 1280;
         static constexpr int32_t DEFAULT_REFRESH_RATE = 16'666'666;
+        static constexpr int32_t DEFAULT_CONFIG_GROUP = 7;
         static constexpr int32_t DEFAULT_DPI = 320;
         static constexpr int32_t DEFAULT_ACTIVE_CONFIG = 0;
         static constexpr int32_t DEFAULT_POWER_MODE = 2;
@@ -452,7 +463,7 @@
             return *this;
         }
 
-        auto& setRefreshRate(int32_t refreshRate) {
+        auto& setRefreshRate(uint32_t refreshRate) {
             mRefreshRate = refreshRate;
             return *this;
         }
@@ -499,6 +510,7 @@
             config.setVsyncPeriod(mRefreshRate);
             config.setDpiX(mDpiX);
             config.setDpiY(mDpiY);
+            config.setConfigGroup(mConfigGroup);
             display->mutableConfigs().emplace(mActiveConfig, config.build());
             display->mutableIsConnected() = true;
             display->setPowerMode(static_cast<HWC2::PowerMode>(mPowerMode));
@@ -522,8 +534,9 @@
         hwc2_display_t mHwcDisplayId = DEFAULT_HWC_DISPLAY_ID;
         int32_t mWidth = DEFAULT_WIDTH;
         int32_t mHeight = DEFAULT_HEIGHT;
-        int32_t mRefreshRate = DEFAULT_REFRESH_RATE;
+        uint32_t mRefreshRate = DEFAULT_REFRESH_RATE;
         int32_t mDpiX = DEFAULT_DPI;
+        int32_t mConfigGroup = DEFAULT_CONFIG_GROUP;
         int32_t mDpiY = DEFAULT_DPI;
         int32_t mActiveConfig = DEFAULT_ACTIVE_CONFIG;
         int32_t mPowerMode = DEFAULT_POWER_MODE;
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index 7b60fa2..68e4c58 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -20,11 +20,11 @@
 #include <TimeStats/TimeStats.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-
 #include <log/log.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
 
+#include <chrono>
 #include <random>
 #include <unordered_set>
 
@@ -278,6 +278,69 @@
     EXPECT_EQ(2, histogramProto.time_millis());
 }
 
+TEST_F(TimeStatsTest, canInsertGlobalFrameDuration) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    using namespace std::chrono_literals;
+
+    mTimeStats->setPowerMode(HWC_POWER_MODE_OFF);
+    mTimeStats
+            ->recordFrameDuration(std::chrono::duration_cast<std::chrono::nanoseconds>(1ms).count(),
+                                  std::chrono::duration_cast<std::chrono::nanoseconds>(5ms)
+                                          .count());
+    mTimeStats->setPowerMode(HWC_POWER_MODE_NORMAL);
+    mTimeStats
+            ->recordFrameDuration(std::chrono::duration_cast<std::chrono::nanoseconds>(3ms).count(),
+                                  std::chrono::duration_cast<std::chrono::nanoseconds>(6ms)
+                                          .count());
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    ASSERT_EQ(1, globalProto.frame_duration_size());
+    const SFTimeStatsHistogramBucketProto& histogramProto = globalProto.frame_duration().Get(0);
+    EXPECT_EQ(1, histogramProto.frame_count());
+    EXPECT_EQ(3, histogramProto.time_millis());
+}
+
+TEST_F(TimeStatsTest, canInsertGlobalRenderEngineTiming) {
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    using namespace std::chrono_literals;
+
+    mTimeStats->recordRenderEngineDuration(std::chrono::duration_cast<std::chrono::nanoseconds>(1ms)
+                                                   .count(),
+                                           std::make_shared<FenceTime>(
+                                                   std::chrono::duration_cast<
+                                                           std::chrono::nanoseconds>(3ms)
+                                                           .count()));
+
+    mTimeStats->recordRenderEngineDuration(std::chrono::duration_cast<std::chrono::nanoseconds>(4ms)
+                                                   .count(),
+                                           std::chrono::duration_cast<std::chrono::nanoseconds>(6ms)
+                                                   .count());
+
+    // First verify that flushing RenderEngine durations did not occur yet.
+    SFTimeStatsGlobalProto preFlushProto;
+    ASSERT_TRUE(preFlushProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+    ASSERT_EQ(0, preFlushProto.render_engine_timing_size());
+
+    // Push a dummy present fence to trigger flushing the RenderEngine timings.
+    mTimeStats->setPowerMode(HWC_POWER_MODE_NORMAL);
+    mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(
+            std::chrono::duration_cast<std::chrono::nanoseconds>(1ms).count()));
+
+    // Now we can verify that RenderEngine durations were flushed now.
+    SFTimeStatsGlobalProto postFlushProto;
+    ASSERT_TRUE(postFlushProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    ASSERT_EQ(1, postFlushProto.render_engine_timing_size());
+    const SFTimeStatsHistogramBucketProto& histogramProto =
+            postFlushProto.render_engine_timing().Get(0);
+    EXPECT_EQ(2, histogramProto.frame_count());
+    EXPECT_EQ(2, histogramProto.time_millis());
+}
+
 TEST_F(TimeStatsTest, canInsertOneLayerTimeStats) {
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
 
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index a465388..994a509 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -315,4 +315,9 @@
     BlockedByPriorTransaction(/*flags*/ 0, /*syncInputWindows*/ true);
 }
 
+TEST_F(TransactionApplicationTest, FromHandle) {
+    sp<IBinder> badHandle;
+    auto ret = mFlinger.fromHandle(badHandle);
+    EXPECT_EQ(nullptr, ret.get());
+}
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index c012616..484947d 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -47,6 +47,10 @@
         return timePoint - floor + mPeriod;
     }
 
+    nsecs_t currentPeriod() const final { return mPeriod; }
+
+    void setPeriod(nsecs_t) final {}
+
 private:
     nsecs_t const mPeriod;
 };
@@ -73,6 +77,13 @@
         mBase = last_known;
     }
 
+    nsecs_t currentPeriod() const final {
+        std::lock_guard<decltype(mMutex)> lk(mMutex);
+        return mPeriod;
+    }
+
+    void setPeriod(nsecs_t) final {}
+
 private:
     std::mutex mutable mMutex;
     nsecs_t mPeriod;
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index 82950b5..d668a41 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -39,6 +39,8 @@
 
     MOCK_METHOD1(addVsyncTimestamp, void(nsecs_t));
     MOCK_CONST_METHOD1(nextAnticipatedVSyncTimeFrom, nsecs_t(nsecs_t));
+    MOCK_CONST_METHOD0(currentPeriod, nsecs_t());
+    MOCK_METHOD1(setPeriod, void(nsecs_t));
 
     nsecs_t nextVSyncTime(nsecs_t timePoint) const {
         if (timePoint % mPeriod == 0) {
@@ -551,6 +553,32 @@
     EXPECT_EQ(mDispatch.schedule(cb0, 100, 1000), ScheduleResult::ReScheduled);
 }
 
+TEST_F(VSyncDispatchTimerQueueTest, canScheduleNegativeOffsetAgainstDifferentPeriods) {
+    CountingCallback cb0(mDispatch);
+    EXPECT_EQ(mDispatch.schedule(cb0, 500, 1000), ScheduleResult::Scheduled);
+    advanceToNextCallback();
+    EXPECT_EQ(mDispatch.schedule(cb0, 1100, 2000), ScheduleResult::Scheduled);
+}
+
+TEST_F(VSyncDispatchTimerQueueTest, canScheduleLargeNegativeOffset) {
+    Sequence seq;
+    EXPECT_CALL(mMockClock, alarmIn(_, 500)).InSequence(seq);
+    EXPECT_CALL(mMockClock, alarmIn(_, 600)).InSequence(seq);
+    CountingCallback cb0(mDispatch);
+    EXPECT_EQ(mDispatch.schedule(cb0, 500, 1000), ScheduleResult::Scheduled);
+    advanceToNextCallback();
+    EXPECT_EQ(mDispatch.schedule(cb0, 1900, 2000), ScheduleResult::Scheduled);
+}
+
+TEST_F(VSyncDispatchTimerQueueTest, cannotScheduleDoesNotAffectSchedulingState) {
+    EXPECT_CALL(mMockClock, alarmIn(_, 600));
+
+    CountingCallback cb(mDispatch);
+    EXPECT_EQ(mDispatch.schedule(cb, 400, 1000), ScheduleResult::Scheduled);
+    advanceToNextCallback();
+    EXPECT_EQ(mDispatch.schedule(cb, 100, 1000), ScheduleResult::CannotSchedule);
+}
+
 TEST_F(VSyncDispatchTimerQueueTest, helperMove) {
     EXPECT_CALL(mMockClock, alarmIn(_, 500)).Times(1);
     EXPECT_CALL(mMockClock, alarmCancel()).Times(1);
@@ -599,11 +627,10 @@
     VSyncDispatchTimerQueueEntry entry("test", [](auto) {});
 
     EXPECT_FALSE(entry.wakeupTime());
-    auto const wakeup = entry.schedule(100, 500, mStubTracker, 0);
-    auto const queried = entry.wakeupTime();
-    ASSERT_TRUE(queried);
-    EXPECT_THAT(*queried, Eq(wakeup));
-    EXPECT_THAT(*queried, Eq(900));
+    EXPECT_THAT(entry.schedule(100, 500, mStubTracker, 0), Eq(ScheduleResult::Scheduled));
+    auto const wakeup = entry.wakeupTime();
+    ASSERT_TRUE(wakeup);
+    EXPECT_THAT(*wakeup, Eq(900));
 
     entry.disarm();
     EXPECT_FALSE(entry.wakeupTime());
@@ -619,11 +646,10 @@
     VSyncDispatchTimerQueueEntry entry("test", [](auto) {});
 
     EXPECT_FALSE(entry.wakeupTime());
-    auto const wakeup = entry.schedule(500, 994, mStubTracker, now);
-    auto const queried = entry.wakeupTime();
-    ASSERT_TRUE(queried);
-    EXPECT_THAT(*queried, Eq(wakeup));
-    EXPECT_THAT(*queried, Eq(9500));
+    EXPECT_THAT(entry.schedule(500, 994, mStubTracker, now), Eq(ScheduleResult::Scheduled));
+    auto const wakeup = entry.wakeupTime();
+    ASSERT_TRUE(wakeup);
+    EXPECT_THAT(*wakeup, Eq(9500));
 }
 
 TEST_F(VSyncDispatchTimerQueueEntryTest, runCallback) {
@@ -634,8 +660,10 @@
         calledTime = time;
     });
 
-    auto const wakeup = entry.schedule(100, 500, mStubTracker, 0);
-    EXPECT_THAT(wakeup, Eq(900));
+    EXPECT_THAT(entry.schedule(100, 500, mStubTracker, 0), Eq(ScheduleResult::Scheduled));
+    auto const wakeup = entry.wakeupTime();
+    ASSERT_TRUE(wakeup);
+    EXPECT_THAT(*wakeup, Eq(900));
 
     entry.callback(entry.executing());
 
@@ -659,23 +687,40 @@
     entry.update(mStubTracker, 0);
     EXPECT_FALSE(entry.wakeupTime());
 
-    auto const wakeup = entry.schedule(100, 500, mStubTracker, 0);
+    EXPECT_THAT(entry.schedule(100, 500, mStubTracker, 0), Eq(ScheduleResult::Scheduled));
+    auto wakeup = entry.wakeupTime();
+    ASSERT_TRUE(wakeup);
     EXPECT_THAT(wakeup, Eq(900));
 
     entry.update(mStubTracker, 0);
-    auto const queried = entry.wakeupTime();
-    ASSERT_TRUE(queried);
-    EXPECT_THAT(*queried, Eq(920));
+    wakeup = entry.wakeupTime();
+    ASSERT_TRUE(wakeup);
+    EXPECT_THAT(*wakeup, Eq(920));
 }
 
 TEST_F(VSyncDispatchTimerQueueEntryTest, skipsUpdateIfJustScheduled) {
     VSyncDispatchTimerQueueEntry entry("test", [](auto) {});
-    auto const wakeup = entry.schedule(100, 500, mStubTracker, 0);
+    EXPECT_THAT(entry.schedule(100, 500, mStubTracker, 0), Eq(ScheduleResult::Scheduled));
     entry.update(mStubTracker, 0);
 
-    auto const queried = entry.wakeupTime();
-    ASSERT_TRUE(queried);
-    EXPECT_THAT(*queried, Eq(wakeup));
+    auto const wakeup = entry.wakeupTime();
+    ASSERT_TRUE(wakeup);
+    EXPECT_THAT(*wakeup, Eq(wakeup));
+}
+
+TEST_F(VSyncDispatchTimerQueueEntryTest, reportsCannotScheduleIfMissedOpportunity) {
+    VSyncDispatchTimerQueueEntry entry("test", [](auto) {});
+    EXPECT_THAT(entry.schedule(100, 500, mStubTracker, 0), Eq(ScheduleResult::Scheduled));
+    entry.executing();
+    EXPECT_THAT(entry.schedule(200, 500, mStubTracker, 0), Eq(ScheduleResult::CannotSchedule));
+    EXPECT_THAT(entry.schedule(50, 500, mStubTracker, 0), Eq(ScheduleResult::CannotSchedule));
+    EXPECT_THAT(entry.schedule(200, 1001, mStubTracker, 0), Eq(ScheduleResult::Scheduled));
+}
+
+TEST_F(VSyncDispatchTimerQueueEntryTest, reportsReScheduleIfStillTime) {
+    VSyncDispatchTimerQueueEntry entry("test", [](auto) {});
+    EXPECT_THAT(entry.schedule(100, 500, mStubTracker, 0), Eq(ScheduleResult::Scheduled));
+    EXPECT_THAT(entry.schedule(200, 500, mStubTracker, 0), Eq(ScheduleResult::ReScheduled));
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index d0c8090..4cb6a38 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -319,4 +319,36 @@
     }
 }
 
+// See b/145667109, and comment in prod code under test.
+TEST_F(VSyncPredictorTest, doesNotPredictBeforeTimePointWithHigherIntercept) {
+    std::vector<nsecs_t> const simulatedVsyncs{
+            158929578733000,
+            158929306806205, // oldest TS in ringbuffer
+            158929650879052,
+            158929661969209,
+            158929684198847,
+            158929695268171,
+            158929706370359,
+    };
+    auto const idealPeriod = 11111111;
+    auto const expectedPeriod = 11113500;
+    auto const expectedIntercept = -395335;
+
+    tracker.setPeriod(idealPeriod);
+    for (auto const& timestamp : simulatedVsyncs) {
+        tracker.addVsyncTimestamp(timestamp);
+    }
+
+    auto [slope, intercept] = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(slope, IsCloseTo(expectedPeriod, mMaxRoundingError));
+    EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
+
+    // (timePoint - oldestTS) % expectedPeriod works out to be: 395334
+    // (timePoint - oldestTS) / expectedPeriod works out to be: 38.96
+    // so failure to account for the offset will floor the ordinal to 38, which was in the past.
+    auto const timePoint = 158929728723871;
+    auto const prediction = tracker.nextAnticipatedVSyncTimeFrom(timePoint);
+    EXPECT_THAT(prediction, Ge(timePoint));
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
new file mode 100644
index 0000000..84df019
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+#define LOG_NDEBUG 0
+
+#include "Scheduler/TimeKeeper.h"
+#include "Scheduler/VSyncDispatch.h"
+#include "Scheduler/VSyncReactor.h"
+#include "Scheduler/VSyncTracker.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <ui/Fence.h>
+#include <ui/FenceTime.h>
+#include <array>
+
+using namespace testing;
+using namespace std::literals;
+namespace android::scheduler {
+
+class MockVSyncTracker : public VSyncTracker {
+public:
+    MOCK_METHOD1(addVsyncTimestamp, void(nsecs_t));
+    MOCK_CONST_METHOD1(nextAnticipatedVSyncTimeFrom, nsecs_t(nsecs_t));
+    MOCK_CONST_METHOD0(currentPeriod, nsecs_t());
+    MOCK_METHOD1(setPeriod, void(nsecs_t));
+};
+
+class VSyncTrackerWrapper : public VSyncTracker {
+public:
+    VSyncTrackerWrapper(std::shared_ptr<VSyncTracker> const& tracker) : mTracker(tracker) {}
+
+    void addVsyncTimestamp(nsecs_t timestamp) final { mTracker->addVsyncTimestamp(timestamp); }
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final {
+        return mTracker->nextAnticipatedVSyncTimeFrom(timePoint);
+    }
+    nsecs_t currentPeriod() const final { return mTracker->currentPeriod(); }
+    void setPeriod(nsecs_t period) { mTracker->setPeriod(period); }
+
+private:
+    std::shared_ptr<VSyncTracker> const mTracker;
+};
+
+class MockClock : public Clock {
+public:
+    MOCK_CONST_METHOD0(now, nsecs_t());
+};
+
+class ClockWrapper : public Clock {
+public:
+    ClockWrapper(std::shared_ptr<Clock> const& clock) : mClock(clock) {}
+
+    nsecs_t now() const { return mClock->now(); }
+
+private:
+    std::shared_ptr<Clock> const mClock;
+};
+
+class MockVSyncDispatch : public VSyncDispatch {
+public:
+    MOCK_METHOD2(registerCallback, CallbackToken(std::function<void(nsecs_t)> const&, std::string));
+    MOCK_METHOD1(unregisterCallback, void(CallbackToken));
+    MOCK_METHOD3(schedule, ScheduleResult(CallbackToken, nsecs_t, nsecs_t));
+    MOCK_METHOD1(cancel, CancelResult(CallbackToken token));
+};
+
+class VSyncDispatchWrapper : public VSyncDispatch {
+public:
+    VSyncDispatchWrapper(std::shared_ptr<VSyncDispatch> const& dispatch) : mDispatch(dispatch) {}
+    CallbackToken registerCallback(std::function<void(nsecs_t)> const& callbackFn,
+                                   std::string callbackName) final {
+        return mDispatch->registerCallback(callbackFn, callbackName);
+    }
+
+    void unregisterCallback(CallbackToken token) final { mDispatch->unregisterCallback(token); }
+
+    ScheduleResult schedule(CallbackToken token, nsecs_t workDuration,
+                            nsecs_t earliestVsync) final {
+        return mDispatch->schedule(token, workDuration, earliestVsync);
+    }
+
+    CancelResult cancel(CallbackToken token) final { return mDispatch->cancel(token); }
+
+private:
+    std::shared_ptr<VSyncDispatch> const mDispatch;
+};
+
+std::shared_ptr<FenceTime> generateInvalidFence() {
+    sp<Fence> fence = new Fence();
+    return std::make_shared<FenceTime>(fence);
+}
+
+std::shared_ptr<FenceTime> generatePendingFence() {
+    sp<Fence> fence = new Fence(dup(fileno(tmpfile())));
+    return std::make_shared<FenceTime>(fence);
+}
+
+void signalFenceWithTime(std::shared_ptr<FenceTime> const& fence, nsecs_t time) {
+    FenceTime::Snapshot snap(time);
+    fence->applyTrustedSnapshot(snap);
+}
+
+std::shared_ptr<FenceTime> generateSignalledFenceWithTime(nsecs_t time) {
+    sp<Fence> fence = new Fence(dup(fileno(tmpfile())));
+    std::shared_ptr<FenceTime> ft = std::make_shared<FenceTime>(fence);
+    signalFenceWithTime(ft, time);
+    return ft;
+}
+
+class VSyncReactorTest : public testing::Test {
+protected:
+    VSyncReactorTest()
+          : mMockDispatch(std::make_shared<MockVSyncDispatch>()),
+            mMockTracker(std::make_shared<NiceMock<MockVSyncTracker>>()),
+            mMockClock(std::make_shared<NiceMock<MockClock>>()),
+            mReactor(std::make_unique<ClockWrapper>(mMockClock),
+                     std::make_unique<VSyncDispatchWrapper>(mMockDispatch),
+                     std::make_unique<VSyncTrackerWrapper>(mMockTracker), kPendingLimit) {}
+
+    std::shared_ptr<MockVSyncDispatch> mMockDispatch;
+    std::shared_ptr<MockVSyncTracker> mMockTracker;
+    std::shared_ptr<MockClock> mMockClock;
+    static constexpr size_t kPendingLimit = 3;
+    static constexpr nsecs_t dummyTime = 47;
+    VSyncReactor mReactor;
+};
+
+TEST_F(VSyncReactorTest, addingNullFenceCheck) {
+    EXPECT_FALSE(mReactor.addPresentFence(nullptr));
+}
+
+TEST_F(VSyncReactorTest, addingInvalidFenceSignalsNeedsMoreInfo) {
+    EXPECT_TRUE(mReactor.addPresentFence(generateInvalidFence()));
+}
+
+TEST_F(VSyncReactorTest, addingSignalledFenceAddsToTracker) {
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(dummyTime));
+    EXPECT_FALSE(mReactor.addPresentFence(generateSignalledFenceWithTime(dummyTime)));
+}
+
+TEST_F(VSyncReactorTest, addingPendingFenceAddsSignalled) {
+    nsecs_t anotherDummyTime = 2919019201;
+
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(0);
+    auto pendingFence = generatePendingFence();
+    EXPECT_FALSE(mReactor.addPresentFence(pendingFence));
+    Mock::VerifyAndClearExpectations(mMockTracker.get());
+
+    signalFenceWithTime(pendingFence, dummyTime);
+
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(dummyTime));
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(anotherDummyTime));
+    EXPECT_FALSE(mReactor.addPresentFence(generateSignalledFenceWithTime(anotherDummyTime)));
+}
+
+TEST_F(VSyncReactorTest, limitsPendingFences) {
+    std::array<std::shared_ptr<FenceTime>, kPendingLimit * 2> fences;
+    std::array<nsecs_t, fences.size()> fakeTimes;
+    std::generate(fences.begin(), fences.end(), [] { return generatePendingFence(); });
+    std::generate(fakeTimes.begin(), fakeTimes.end(), [i = 10]() mutable {
+        i++;
+        return i * i;
+    });
+
+    for (auto const& fence : fences) {
+        mReactor.addPresentFence(fence);
+    }
+
+    for (auto i = fences.size() - kPendingLimit; i < fences.size(); i++) {
+        EXPECT_CALL(*mMockTracker, addVsyncTimestamp(fakeTimes[i]));
+    }
+
+    for (auto i = 0u; i < fences.size(); i++) {
+        signalFenceWithTime(fences[i], fakeTimes[i]);
+    }
+    mReactor.addPresentFence(generatePendingFence());
+}
+
+TEST_F(VSyncReactorTest, ignoresPresentFencesWhenToldTo) {
+    static constexpr size_t aFewTimes = 8;
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(dummyTime)).Times(1);
+
+    mReactor.setIgnorePresentFences(true);
+    for (auto i = 0; i < aFewTimes; i++) {
+        mReactor.addPresentFence(generateSignalledFenceWithTime(dummyTime));
+    }
+
+    mReactor.setIgnorePresentFences(false);
+    EXPECT_FALSE(mReactor.addPresentFence(generateSignalledFenceWithTime(dummyTime)));
+}
+
+TEST_F(VSyncReactorTest, queriesTrackerForNextRefreshNow) {
+    nsecs_t const fakeTimestamp = 4839;
+    EXPECT_CALL(*mMockTracker, currentPeriod()).Times(0);
+    EXPECT_CALL(*mMockTracker, nextAnticipatedVSyncTimeFrom(_))
+            .Times(1)
+            .WillOnce(Return(fakeTimestamp));
+
+    EXPECT_THAT(mReactor.computeNextRefresh(0), Eq(fakeTimestamp));
+}
+
+TEST_F(VSyncReactorTest, queriesTrackerForExpectedPresentTime) {
+    nsecs_t const fakeTimestamp = 4839;
+    EXPECT_CALL(*mMockTracker, currentPeriod()).Times(0);
+    EXPECT_CALL(*mMockTracker, nextAnticipatedVSyncTimeFrom(_))
+            .Times(1)
+            .WillOnce(Return(fakeTimestamp));
+
+    EXPECT_THAT(mReactor.expectedPresentTime(), Eq(fakeTimestamp));
+}
+
+TEST_F(VSyncReactorTest, queriesTrackerForNextRefreshFuture) {
+    nsecs_t const fakeTimestamp = 4839;
+    nsecs_t const fakePeriod = 1010;
+    nsecs_t const fakeNow = 2214;
+    int const numPeriodsOut = 3;
+    EXPECT_CALL(*mMockClock, now()).WillOnce(Return(fakeNow));
+    EXPECT_CALL(*mMockTracker, currentPeriod()).WillOnce(Return(fakePeriod));
+    EXPECT_CALL(*mMockTracker, nextAnticipatedVSyncTimeFrom(fakeNow + numPeriodsOut * fakePeriod))
+            .WillOnce(Return(fakeTimestamp));
+    EXPECT_THAT(mReactor.computeNextRefresh(numPeriodsOut), Eq(fakeTimestamp));
+}
+
+TEST_F(VSyncReactorTest, getPeriod) {
+    nsecs_t const fakePeriod = 1010;
+    EXPECT_CALL(*mMockTracker, currentPeriod()).WillOnce(Return(fakePeriod));
+    EXPECT_THAT(mReactor.getPeriod(), Eq(fakePeriod));
+}
+
+TEST_F(VSyncReactorTest, setPeriod) {
+    nsecs_t const fakePeriod = 4098;
+    EXPECT_CALL(*mMockTracker, setPeriod(fakePeriod));
+    mReactor.setPeriod(fakePeriod);
+}
+
+TEST_F(VSyncReactorTest, addResyncSampleTypical) {
+    nsecs_t const fakeTimestamp = 3032;
+    bool periodFlushed = false;
+
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(fakeTimestamp));
+    EXPECT_FALSE(mReactor.addResyncSample(fakeTimestamp, &periodFlushed));
+    EXPECT_FALSE(periodFlushed);
+}
+
+TEST_F(VSyncReactorTest, addResyncSamplePeriodChanges) {
+    bool periodFlushed = false;
+    nsecs_t const fakeTimestamp = 4398;
+    nsecs_t const newPeriod = 3490;
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(fakeTimestamp));
+    mReactor.setPeriod(newPeriod);
+    EXPECT_FALSE(mReactor.addResyncSample(fakeTimestamp, &periodFlushed));
+    EXPECT_TRUE(periodFlushed);
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 98c6aa0..2453ccb 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -39,8 +39,8 @@
 using android::hardware::graphics::composer::V2_1::Display;
 using android::hardware::graphics::composer::V2_1::Error;
 using android::hardware::graphics::composer::V2_1::IComposer;
-using android::hardware::graphics::composer::V2_1::IComposerCallback;
 using android::hardware::graphics::composer::V2_1::Layer;
+using android::hardware::graphics::composer::V2_4::IComposerCallback;
 using android::hardware::graphics::composer::V2_4::IComposerClient;
 
 class Composer : public Hwc2::Composer {
@@ -120,8 +120,16 @@
     MOCK_METHOD3(setLayerPerFrameMetadataBlobs,
                  Error(Display, Layer, const std::vector<IComposerClient::PerFrameMetadataBlob>&));
     MOCK_METHOD2(setDisplayBrightness, Error(Display, float));
+    MOCK_METHOD0(isVsyncPeriodSwitchSupported, bool());
     MOCK_METHOD2(getDisplayCapabilities, Error(Display, std::vector<DisplayCapability>*));
-    MOCK_METHOD2(getDisplayConnectionType, Error(Display, IComposerClient::DisplayConnectionType*));
+    MOCK_METHOD2(getDisplayConnectionType,
+                 V2_4::Error(Display, IComposerClient::DisplayConnectionType*));
+    MOCK_METHOD3(getSupportedDisplayVsyncPeriods,
+                 V2_4::Error(Display, Config, std::vector<VsyncPeriodNanos>*));
+    MOCK_METHOD2(getDisplayVsyncPeriod, V2_4::Error(Display, VsyncPeriodNanos*));
+    MOCK_METHOD4(setActiveConfigWithConstraints,
+                 V2_4::Error(Display, Config, const IComposerClient::VsyncPeriodChangeConstraints&,
+                             VsyncPeriodChangeTimeline*));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index ed35ebf..f7c3804 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -33,7 +33,7 @@
     MOCK_METHOD0(onScreenReleased, void());
     MOCK_METHOD0(onScreenAcquired, void());
     MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool));
-    MOCK_METHOD2(onConfigChanged, void(PhysicalDisplayId, int32_t));
+    MOCK_METHOD2(onConfigChanged, void(PhysicalDisplayId, HwcConfigIndexType));
     MOCK_CONST_METHOD1(dump, void(std::string&));
     MOCK_METHOD1(setPhaseOffset, void(nsecs_t phaseOffset));
     MOCK_METHOD1(registerDisplayEventConnection,
diff --git a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
index b1634a8..ec74a42 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
@@ -34,6 +34,9 @@
     MOCK_METHOD0(incrementTotalFrames, void());
     MOCK_METHOD0(incrementMissedFrames, void());
     MOCK_METHOD0(incrementClientCompositionFrames, void());
+    MOCK_METHOD2(recordFrameDuration, void(nsecs_t, nsecs_t));
+    MOCK_METHOD2(recordRenderEngineDuration, void(nsecs_t, nsecs_t));
+    MOCK_METHOD2(recordRenderEngineDuration, void(nsecs_t, const std::shared_ptr<FenceTime>&));
     MOCK_METHOD4(setPostTime, void(int32_t, uint64_t, const std::string&, nsecs_t));
     MOCK_METHOD3(setLatchTime, void(int32_t, uint64_t, nsecs_t));
     MOCK_METHOD3(setDesiredTime, void(int32_t, uint64_t, nsecs_t));
diff --git a/services/surfaceflinger/tests/utils/TransactionUtils.h b/services/surfaceflinger/tests/utils/TransactionUtils.h
index 22df255..2c63da0 100644
--- a/services/surfaceflinger/tests/utils/TransactionUtils.h
+++ b/services/surfaceflinger/tests/utils/TransactionUtils.h
@@ -136,6 +136,11 @@
         }
     }
 
+    static void fillSurfaceRGBA8(const sp<SurfaceControl>& sc, const Color& color,
+                                 bool unlock = true) {
+        fillSurfaceRGBA8(sc, color.r, color.g, color.b, unlock);
+    }
+
     // Fill an RGBA_8888 formatted surface with a single color.
     static void fillSurfaceRGBA8(const sp<SurfaceControl>& sc, uint8_t r, uint8_t g, uint8_t b,
                                  bool unlock = true) {
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 8f4667e..a020e74 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -1311,7 +1311,14 @@
                                             &img.dequeue_fence);
         if (err != 0) {
             ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err);
-            result = VK_ERROR_SURFACE_LOST_KHR;
+            switch (-err) {
+                case ENOMEM:
+                    result = VK_ERROR_OUT_OF_DEVICE_MEMORY;
+                    break;
+                default:
+                    result = VK_ERROR_SURFACE_LOST_KHR;
+                    break;
+            }
             break;
         }
         img.buffer = buffer;
diff --git a/vulkan/vkjson/Android.bp b/vulkan/vkjson/Android.bp
index a626e48..8528898 100644
--- a/vulkan/vkjson/Android.bp
+++ b/vulkan/vkjson/Android.bp
@@ -1,4 +1,4 @@
-cc_library_static {
+cc_library_shared {
     name: "libvkjson",
     srcs: [
         "vkjson.cc",
@@ -15,11 +15,14 @@
     export_include_dirs: [
         ".",
     ],
+    shared_libs: [
+        "libvulkan",
+    ],
     whole_static_libs: [
         "libjsoncpp",
     ],
-    header_libs: [
-        "vulkan_headers",
+    export_shared_lib_headers: [
+        "libvulkan",
     ],
 }