Merge "Allow ADPF WorkDuration CPU duration to be zero" into main
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 481c28e..5ee6b15 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -1563,6 +1563,13 @@
     RunDumpsys("DROPBOX SYSTEM SERVER CRASHES", {"dropbox", "-p", "system_server_crash"});
     RunDumpsys("DROPBOX SYSTEM APP CRASHES", {"dropbox", "-p", "system_app_crash"});
 
+
+    printf("========================================================\n");
+    printf("== ANR Traces\n");
+    printf("========================================================\n");
+
+    AddAnrTraceFiles();
+
     printf("========================================================\n");
     printf("== Final progress (pid %d): %d/%d (estimated %d)\n", ds.pid_, ds.progress_->Get(),
            ds.progress_->GetMax(), ds.progress_->GetInitialMax());
diff --git a/cmds/lshal/Timeout.h b/cmds/lshal/Timeout.h
index d97ba89..37f41be 100644
--- a/cmds/lshal/Timeout.h
+++ b/cmds/lshal/Timeout.h
@@ -16,44 +16,83 @@
 
 #pragma once
 
+#include <condition_variable>
 #include <chrono>
-#include <future>
+#include <functional>
+#include <mutex>
+#include <thread>
 
 #include <hidl/Status.h>
-#include <utils/Errors.h>
 
 namespace android {
 namespace lshal {
 
-// Call function on interfaceObject and wait for result until the given timeout has reached.
-// Callback functions pass to timeoutIPC() may be executed after the this function
-// has returned, especially if deadline has been reached. Hence, care must be taken when passing
-// data between the background thread and the main thread. See b/311143089.
+class BackgroundTaskState {
+public:
+    explicit BackgroundTaskState(std::function<void(void)> &&func)
+            : mFunc(std::forward<decltype(func)>(func)) {}
+    void notify() {
+        std::unique_lock<std::mutex> lock(mMutex);
+        mFinished = true;
+        lock.unlock();
+        mCondVar.notify_all();
+    }
+    template<class C, class D>
+    bool wait(std::chrono::time_point<C, D> end) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        mCondVar.wait_until(lock, end, [this](){ return this->mFinished; });
+        return mFinished;
+    }
+    void operator()() {
+        mFunc();
+    }
+private:
+    std::mutex mMutex;
+    std::condition_variable mCondVar;
+    bool mFinished = false;
+    std::function<void(void)> mFunc;
+};
+
+void *callAndNotify(void *data) {
+    BackgroundTaskState &state = *static_cast<BackgroundTaskState *>(data);
+    state();
+    state.notify();
+    return nullptr;
+}
+
+template<class R, class P>
+bool timeout(std::chrono::duration<R, P> delay, std::function<void(void)> &&func) {
+    auto now = std::chrono::system_clock::now();
+    BackgroundTaskState state{std::forward<decltype(func)>(func)};
+    pthread_t thread;
+    if (pthread_create(&thread, nullptr, callAndNotify, &state)) {
+        std::cerr << "FATAL: could not create background thread." << std::endl;
+        return false;
+    }
+    bool success = state.wait(now + delay);
+    if (!success) {
+        pthread_kill(thread, SIGINT);
+    }
+    pthread_join(thread, nullptr);
+    return success;
+}
+
 template<class R, class P, class Function, class I, class... Args>
 typename std::invoke_result<Function, I *, Args...>::type
 timeoutIPC(std::chrono::duration<R, P> wait, const sp<I> &interfaceObject, Function &&func,
            Args &&... args) {
     using ::android::hardware::Status;
-
-    // Execute on a background thread but do not defer execution.
-    auto future =
-            std::async(std::launch::async, func, interfaceObject, std::forward<Args>(args)...);
-    auto status = future.wait_for(wait);
-    if (status == std::future_status::ready) {
-        return future.get();
-    }
-
-    // This future belongs to a background thread that we no longer care about.
-    // Putting this in the global list avoids std::future::~future() that may wait for the
-    // result to come back.
-    // This leaks memory, but lshal is a debugging tool, so this is fine.
-    static std::vector<decltype(future)> gDeadPool{};
-    gDeadPool.emplace_back(std::move(future));
-
-    if (status == std::future_status::timeout) {
+    typename std::result_of<Function(I *, Args...)>::type ret{Status::ok()};
+    auto boundFunc = std::bind(std::forward<Function>(func),
+            interfaceObject.get(), std::forward<Args>(args)...);
+    bool success = timeout(wait, [&ret, &boundFunc] {
+        ret = std::move(boundFunc());
+    });
+    if (!success) {
         return Status::fromStatusT(TIMED_OUT);
     }
-    return Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE, "Illegal future_status");
+    return ret;
 }
-} // namespace lshal
-} // namespace android
+
+}  // namespace lshal
+}  // namespace android
diff --git a/cmds/lshal/main.cpp b/cmds/lshal/main.cpp
index bd5fa32..366c938 100644
--- a/cmds/lshal/main.cpp
+++ b/cmds/lshal/main.cpp
@@ -18,6 +18,5 @@
 
 int main(int argc, char **argv) {
     using namespace ::android::lshal;
-    // Use _exit() to force terminate background threads in Timeout.h
-    _exit(Lshal{}.main(Arg{argc, argv}));
+    return Lshal{}.main(Arg{argc, argv});
 }
diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp
index c24f827..cba7c4b 100644
--- a/cmds/lshal/test.cpp
+++ b/cmds/lshal/test.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-#include <chrono>
-#include <future>
-#include <mutex>
-#include "android/hidl/base/1.0/IBase.h"
 #define LOG_TAG "Lshal"
 #include <android-base/logging.h>
 
@@ -40,8 +36,6 @@
 
 using namespace testing;
 
-using std::chrono_literals::operator""ms;
-
 using ::android::hidl::base::V1_0::DebugInfo;
 using ::android::hidl::base::V1_0::IBase;
 using ::android::hidl::manager::V1_0::IServiceManager;
@@ -940,9 +934,12 @@
         return hardware::Void();
     }));
     EXPECT_CALL(*serviceManager, get(_, _))
-            .WillRepeatedly(Invoke([&](const hidl_string&, const hidl_string&) -> sp<IBase> {
-                return sp<IBase>(service);
-            }));
+            .WillRepeatedly(
+                    Invoke([&](const hidl_string&, const hidl_string& instance) -> sp<IBase> {
+                        int id = getIdFromInstanceName(instance);
+                        if (id > inheritanceLevel) return nullptr;
+                        return sp<IBase>(service);
+                    }));
 
     const std::string expected = "[fake description 0]\n"
                                  "Interface\n"
@@ -960,110 +957,6 @@
     EXPECT_EQ("", err.str());
 }
 
-// In SlowService, everything goes slooooooow. Each IPC call will wait for
-// the specified time before calling the callback function or returning.
-class SlowService : public IBase {
-public:
-    explicit SlowService(std::chrono::milliseconds wait) : mWait(wait) {}
-    android::hardware::Return<void> interfaceDescriptor(interfaceDescriptor_cb cb) override {
-        std::this_thread::sleep_for(mWait);
-        cb(getInterfaceName(1));
-        storeHistory("interfaceDescriptor");
-        return hardware::Void();
-    }
-    android::hardware::Return<void> interfaceChain(interfaceChain_cb cb) override {
-        std::this_thread::sleep_for(mWait);
-        std::vector<hidl_string> ret;
-        ret.push_back(getInterfaceName(1));
-        ret.push_back(IBase::descriptor);
-        cb(ret);
-        storeHistory("interfaceChain");
-        return hardware::Void();
-    }
-    android::hardware::Return<void> getHashChain(getHashChain_cb cb) override {
-        std::this_thread::sleep_for(mWait);
-        std::vector<hidl_hash> ret;
-        ret.push_back(getHashFromId(0));
-        ret.push_back(getHashFromId(0xff));
-        cb(ret);
-        storeHistory("getHashChain");
-        return hardware::Void();
-    }
-    android::hardware::Return<void> debug(const hidl_handle&,
-                                          const hidl_vec<hidl_string>&) override {
-        std::this_thread::sleep_for(mWait);
-        storeHistory("debug");
-        return Void();
-    }
-
-    template <class R, class P, class Pred>
-    bool waitForHistory(std::chrono::duration<R, P> wait, Pred predicate) {
-        std::unique_lock<std::mutex> lock(mLock);
-        return mCv.wait_for(lock, wait, [&]() { return predicate(mCallHistory); });
-    }
-
-private:
-    void storeHistory(std::string hist) {
-        {
-            std::lock_guard<std::mutex> lock(mLock);
-            mCallHistory.emplace_back(std::move(hist));
-        }
-        mCv.notify_all();
-    }
-
-    const std::chrono::milliseconds mWait;
-    std::mutex mLock;
-    std::condition_variable mCv;
-    // List of functions that have finished being called on this interface.
-    std::vector<std::string> mCallHistory;
-};
-
-class TimeoutTest : public ListTest {
-public:
-    void setMockServiceManager(sp<IBase> service) {
-        EXPECT_CALL(*serviceManager, list(_))
-                .WillRepeatedly(Invoke([&](IServiceManager::list_cb cb) {
-                    std::vector<hidl_string> ret;
-                    ret.push_back(getInterfaceName(1) + "/default");
-                    cb(ret);
-                    return hardware::Void();
-                }));
-        EXPECT_CALL(*serviceManager, get(_, _))
-                .WillRepeatedly(Invoke([&](const hidl_string&, const hidl_string&) -> sp<IBase> {
-                    return service;
-                }));
-    }
-};
-
-TEST_F(TimeoutTest, BackgroundThreadIsKept) {
-    auto lshalIpcTimeout = 100ms;
-    auto serviceIpcTimeout = 200ms;
-    lshal->setWaitTimeForTest(lshalIpcTimeout, lshalIpcTimeout);
-    sp<SlowService> service = new SlowService(serviceIpcTimeout);
-    setMockServiceManager(service);
-
-    optind = 1; // mimic Lshal::parseArg()
-    EXPECT_NE(0u, mockList->main(createArg({"lshal", "--types=b", "-i", "--neat"})));
-    EXPECT_THAT(err.str(), HasSubstr("Skipping \"a.h.foo1@1.0::IFoo/default\""));
-    EXPECT_TRUE(service->waitForHistory(serviceIpcTimeout * 5, [](const auto& hist) {
-        return hist.size() == 1 && hist[0] == "interfaceChain";
-    })) << "The background thread should continue after the main thread moves on, but it is killed";
-}
-
-TEST_F(TimeoutTest, BackgroundThreadDoesNotBlockMainThread) {
-    auto lshalIpcTimeout = 100ms;
-    auto serviceIpcTimeout = 2000ms;
-    auto start = std::chrono::system_clock::now();
-    lshal->setWaitTimeForTest(lshalIpcTimeout, lshalIpcTimeout);
-    sp<SlowService> service = new SlowService(serviceIpcTimeout);
-    setMockServiceManager(service);
-
-    optind = 1; // mimic Lshal::parseArg()
-    EXPECT_NE(0u, mockList->main(createArg({"lshal", "--types=b", "-i", "--neat"})));
-    EXPECT_LE(std::chrono::system_clock::now(), start + 5 * lshalIpcTimeout)
-            << "The main thread should not be blocked by the background task";
-}
-
 class ListVintfTest : public ListTest {
 public:
     virtual void SetUp() override {
@@ -1186,6 +1079,5 @@
 
 int main(int argc, char **argv) {
     ::testing::InitGoogleMock(&argc, argv);
-    // Use _exit() to force terminate background threads in Timeout.h
-    _exit(RUN_ALL_TESTS());
+    return RUN_ALL_TESTS();
 }
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index a401838..adcef91 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -150,7 +150,7 @@
     std::optional<std::string> updatableViaApex;
 
     forEachManifest([&](const ManifestWithDescription& mwd) {
-        mwd.manifest->forEachInstance([&](const auto& manifestInstance) {
+        bool cont = mwd.manifest->forEachInstance([&](const auto& manifestInstance) {
             if (manifestInstance.format() != vintf::HalFormat::AIDL) return true;
             if (manifestInstance.package() != aname.package) return true;
             if (manifestInstance.interface() != aname.iface) return true;
@@ -158,8 +158,7 @@
             updatableViaApex = manifestInstance.updatableViaApex();
             return false; // break (libvintf uses opposite convention)
         });
-        if (updatableViaApex.has_value()) return true; // break (found match)
-        return false; // continue
+        return !cont;
     });
 
     return updatableViaApex;
diff --git a/data/etc/android.hardware.location.gps.xml b/data/etc/android.hardware.location.gps.xml
index 72ab732..2a55370 100644
--- a/data/etc/android.hardware.location.gps.xml
+++ b/data/etc/android.hardware.location.gps.xml
@@ -17,6 +17,5 @@
 <!-- These are the location-related features for devices that include GPS. -->
 <permissions>
     <feature name="android.hardware.location" />
-    <feature name="android.hardware.location.network" />
     <feature name="android.hardware.location.gps" />
 </permissions>
diff --git a/data/etc/car_core_hardware.xml b/data/etc/car_core_hardware.xml
index 95b8110..beb69f8 100644
--- a/data/etc/car_core_hardware.xml
+++ b/data/etc/car_core_hardware.xml
@@ -28,7 +28,6 @@
 
     <feature name="android.hardware.audio.output" />
     <feature name="android.hardware.location" />
-    <feature name="android.hardware.location.network" />
     <feature name="android.hardware.bluetooth" />
     <feature name="android.hardware.touchscreen" />
     <feature name="android.hardware.microphone" />
diff --git a/include/android/keycodes.h b/include/android/keycodes.h
index f8fb256..79cdbca 100644
--- a/include/android/keycodes.h
+++ b/include/android/keycodes.h
@@ -839,6 +839,10 @@
     AKEYCODE_MACRO_3 = 315,
     /** User customizable key #4. */
     AKEYCODE_MACRO_4 = 316,
+    /** Open Emoji picker */
+    AKEYCODE_EMOJI_PICKER = 317,
+    /** Take Screenshot */
+    AKEYCODE_SCREENSHOT = 318,
 
     // NOTE: If you add a new keycode here you must also add it to several other files.
     //       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
diff --git a/include/ftl/details/future.h b/include/ftl/details/future.h
index df1323e..8d82e0f 100644
--- a/include/ftl/details/future.h
+++ b/include/ftl/details/future.h
@@ -73,8 +73,18 @@
     return std::get<Impl>(self()).get();
   }
 
+  template <class Rep, class Period>
+  std::future_status wait_for(const std::chrono::duration<Rep, Period>& timeout_duration) const {
+    if (std::holds_alternative<T>(self())) {
+      return std::future_status::ready;
+    }
+
+    return std::get<Impl>(self()).wait_for(timeout_duration);
+  }
+
  private:
   auto& self() { return static_cast<Self&>(*this).future_; }
+  const auto& self() const { return static_cast<const Self&>(*this).future_; }
 };
 
 template <typename Self, typename T>
@@ -90,6 +100,15 @@
     return std::get<Impl>(self()).get();
   }
 
+  template <class Rep, class Period>
+  std::future_status wait_for(const std::chrono::duration<Rep, Period>& timeout_duration) const {
+    if (std::holds_alternative<T>(self())) {
+      return std::future_status::ready;
+    }
+
+    return std::get<Impl>(self()).wait_for(timeout_duration);
+  }
+
  private:
   const auto& self() const { return static_cast<const Self&>(*this).future_; }
 };
diff --git a/include/ftl/expected.h b/include/ftl/expected.h
new file mode 100644
index 0000000..12b6102
--- /dev/null
+++ b/include/ftl/expected.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 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/expected.h>
+#include <ftl/optional.h>
+
+#include <utility>
+
+namespace android::ftl {
+
+// Superset of base::expected<T, E> with monadic operations.
+//
+// TODO: Extend std::expected<T, E> in C++23.
+//
+template <typename T, typename E>
+struct Expected final : base::expected<T, E> {
+  using Base = base::expected<T, E>;
+  using Base::expected;
+
+  using Base::error;
+  using Base::has_value;
+  using Base::value;
+
+  template <typename P>
+  constexpr bool has_error(P predicate) const {
+    return !has_value() && predicate(error());
+  }
+
+  constexpr Optional<T> value_opt() const& {
+    return has_value() ? Optional(value()) : std::nullopt;
+  }
+
+  constexpr Optional<T> value_opt() && {
+    return has_value() ? Optional(std::move(value())) : std::nullopt;
+  }
+
+  // Delete new for this class. Its base doesn't have a virtual destructor, and
+  // if it got deleted via base class pointer, it would cause undefined
+  // behavior. There's not a good reason to allocate this object on the heap
+  // anyway.
+  static void* operator new(size_t) = delete;
+  static void* operator new[](size_t) = delete;
+};
+
+template <typename E>
+constexpr auto Unexpected(E&& error) {
+  return base::unexpected(std::forward<E>(error));
+}
+
+}  // namespace android::ftl
diff --git a/include/ftl/fake_guard.h b/include/ftl/fake_guard.h
index bacd1b2..e601251 100644
--- a/include/ftl/fake_guard.h
+++ b/include/ftl/fake_guard.h
@@ -85,6 +85,5 @@
 
 #define FTL_MAKE_FAKE_GUARD(arg1, arg2, guard, ...) guard
 
-// The void argument suppresses a warning about zero variadic macro arguments.
 #define FTL_FAKE_GUARD(...) \
-  FTL_MAKE_FAKE_GUARD(__VA_ARGS__, FTL_FAKE_GUARD2, FTL_FAKE_GUARD1, void)(__VA_ARGS__)
+  FTL_MAKE_FAKE_GUARD(__VA_ARGS__, FTL_FAKE_GUARD2, FTL_FAKE_GUARD1, )(__VA_ARGS__)
diff --git a/include/ftl/future.h b/include/ftl/future.h
index c78f9b7..dad180f 100644
--- a/include/ftl/future.h
+++ b/include/ftl/future.h
@@ -51,6 +51,7 @@
   // Forwarding functions. Base::share is only defined when FutureImpl is std::future, whereas the
   // following are defined for either FutureImpl:
   using Base::get;
+  using Base::wait_for;
 
   // Attaches a continuation to the future. The continuation is a function that maps T to either R
   // or ftl::Future<R>. In the former case, the chain wraps the result in a future as if by
diff --git a/include/ftl/small_map.h b/include/ftl/small_map.h
index 49cde7f..83d5967 100644
--- a/include/ftl/small_map.h
+++ b/include/ftl/small_map.h
@@ -107,12 +107,20 @@
   template <typename Q, typename W, std::size_t M, typename E>
   SmallMap(SmallMap<Q, W, M, E> other) : map_(std::move(other.map_)) {}
 
+  static constexpr size_type static_capacity() { return N; }
+
   size_type max_size() const { return map_.max_size(); }
   size_type size() const { return map_.size(); }
   bool empty() const { return map_.empty(); }
 
   // Returns whether the map is backed by static or dynamic storage.
-  bool dynamic() const { return map_.dynamic(); }
+  bool dynamic() const {
+    if constexpr (static_capacity() > 0) {
+      return map_.dynamic();
+    } else {
+      return true;
+    }
+  }
 
   iterator begin() { return map_.begin(); }
   const_iterator begin() const { return cbegin(); }
@@ -171,9 +179,15 @@
       return {it, false};
     }
 
-    auto& ref = map_.emplace_back(std::piecewise_construct, std::forward_as_tuple(key),
-                                  std::forward_as_tuple(std::forward<Args>(args)...));
-    return {&ref, true};
+    decltype(auto) ref_or_it =
+        map_.emplace_back(std::piecewise_construct, std::forward_as_tuple(key),
+                          std::forward_as_tuple(std::forward<Args>(args)...));
+
+    if constexpr (static_capacity() > 0) {
+      return {&ref_or_it, true};
+    } else {
+      return {ref_or_it, true};
+    }
   }
 
   // Replaces a mapping if it exists, and returns an iterator to it. Returns the end() iterator
diff --git a/include/ftl/small_vector.h b/include/ftl/small_vector.h
index 11294c3..43e9fac 100644
--- a/include/ftl/small_vector.h
+++ b/include/ftl/small_vector.h
@@ -124,30 +124,29 @@
   DISPATCH(size_type, size, const)
   DISPATCH(bool, empty, const)
 
-  // noexcept to suppress warning about zero variadic macro arguments.
-  DISPATCH(iterator, begin, noexcept)
+  DISPATCH(iterator, begin, )
   DISPATCH(const_iterator, begin, const)
   DISPATCH(const_iterator, cbegin, const)
 
-  DISPATCH(iterator, end, noexcept)
+  DISPATCH(iterator, end, )
   DISPATCH(const_iterator, end, const)
   DISPATCH(const_iterator, cend, const)
 
-  DISPATCH(reverse_iterator, rbegin, noexcept)
+  DISPATCH(reverse_iterator, rbegin, )
   DISPATCH(const_reverse_iterator, rbegin, const)
   DISPATCH(const_reverse_iterator, crbegin, const)
 
-  DISPATCH(reverse_iterator, rend, noexcept)
+  DISPATCH(reverse_iterator, rend, )
   DISPATCH(const_reverse_iterator, rend, const)
   DISPATCH(const_reverse_iterator, crend, const)
 
-  DISPATCH(iterator, last, noexcept)
+  DISPATCH(iterator, last, )
   DISPATCH(const_iterator, last, const)
 
-  DISPATCH(reference, front, noexcept)
+  DISPATCH(reference, front, )
   DISPATCH(const_reference, front, const)
 
-  DISPATCH(reference, back, noexcept)
+  DISPATCH(reference, back, )
   DISPATCH(const_reference, back, const)
 
   reference operator[](size_type i) {
@@ -211,13 +210,13 @@
   //
   // The last() and end() iterators are invalidated.
   //
-  DISPATCH(void, pop_back, noexcept)
+  DISPATCH(void, pop_back, )
 
   // Removes all elements.
   //
   // All iterators are invalidated.
   //
-  DISPATCH(void, clear, noexcept)
+  DISPATCH(void, clear, )
 
 #undef DISPATCH
 
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index b7751f7..57b659d 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -75,6 +75,17 @@
     bool operator!=(const InputDeviceIdentifier&) const = default;
 };
 
+/**
+ * Holds View related behaviors for an InputDevice.
+ */
+struct InputDeviceViewBehavior {
+    /**
+     * The smooth scroll behavior that applies for all source/axis, if defined by the device.
+     * Empty optional if the device has not specified the default smooth scroll behavior.
+     */
+    std::optional<bool> shouldSmoothScroll;
+};
+
 /* Types of input device sensors. Keep sync with core/java/android/hardware/Sensor.java */
 enum class InputDeviceSensorType : int32_t {
     ACCELEROMETER = ASENSOR_TYPE_ACCELEROMETER,
@@ -266,7 +277,8 @@
 
     void initialize(int32_t id, int32_t generation, int32_t controllerNumber,
                     const InputDeviceIdentifier& identifier, const std::string& alias,
-                    bool isExternal, bool hasMic, int32_t associatedDisplayId);
+                    bool isExternal, bool hasMic, int32_t associatedDisplayId,
+                    InputDeviceViewBehavior viewBehavior = {{}});
 
     inline int32_t getId() const { return mId; }
     inline int32_t getControllerNumber() const { return mControllerNumber; }
@@ -298,6 +310,8 @@
         return mKeyboardLayoutInfo;
     }
 
+    inline const InputDeviceViewBehavior& getViewBehavior() const { return mViewBehavior; }
+
     inline void setKeyCharacterMap(const std::shared_ptr<KeyCharacterMap> value) {
         mKeyCharacterMap = value;
     }
@@ -359,6 +373,8 @@
     std::unordered_map<int32_t, InputDeviceLightInfo> mLights;
     /* Map from battery ID to battery info */
     std::unordered_map<int32_t, InputDeviceBatteryInfo> mBatteries;
+    /** The View related behaviors for the device. */
+    InputDeviceViewBehavior mViewBehavior;
 };
 
 /* Types of input device configuration files. */
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 2d64872..d53e8c6 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -35,18 +35,16 @@
 #include <android-base/result.h>
 #include <android-base/unique_fd.h>
 
+#include <android/os/InputChannelCore.h>
 #include <binder/IBinder.h>
-#include <binder/Parcelable.h>
 #include <input/Input.h>
 #include <input/InputVerifier.h>
 #include <sys/stat.h>
 #include <ui/Transform.h>
 #include <utils/BitSet.h>
 #include <utils/Errors.h>
-#include <utils/RefBase.h>
 #include <utils/Timers.h>
 
-
 namespace android {
 class Parcel;
 
@@ -231,18 +229,15 @@
  * input messages across processes.  Each channel has a descriptive name for debugging purposes.
  *
  * Each endpoint has its own InputChannel object that specifies its file descriptor.
+ * For parceling, this relies on android::os::InputChannelCore, defined in aidl.
  *
  * The input channel is closed when all references to it are released.
  */
-class InputChannel : public Parcelable {
+class InputChannel : private android::os::InputChannelCore {
 public:
-    static std::unique_ptr<InputChannel> create(const std::string& name,
-                                                android::base::unique_fd fd, sp<IBinder> token);
-    InputChannel() = default;
-    InputChannel(const InputChannel& other)
-          : mName(other.mName), mFd(other.dupFd()), mToken(other.mToken){};
-    InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token);
-    ~InputChannel() override;
+    static std::unique_ptr<InputChannel> create(android::os::InputChannelCore&& parceledChannel);
+    ~InputChannel();
+
     /**
      * Create a pair of input channels.
      * The two returned input channels are equivalent, and are labeled as "server" and "client"
@@ -254,9 +249,8 @@
                                          std::unique_ptr<InputChannel>& outServerChannel,
                                          std::unique_ptr<InputChannel>& outClientChannel);
 
-    inline std::string getName() const { return mName; }
-    inline const android::base::unique_fd& getFd() const { return mFd; }
-    inline sp<IBinder> getToken() const { return mToken; }
+    inline std::string getName() const { return name; }
+    inline int getFd() const { return fd.get(); }
 
     /* Send a message to the other endpoint.
      *
@@ -304,10 +298,7 @@
     /* Return a new object that has a duplicate of this channel's fd. */
     std::unique_ptr<InputChannel> dup() const;
 
-    void copyTo(InputChannel& outChannel) const;
-
-    status_t readFromParcel(const android::Parcel* parcel) override;
-    status_t writeToParcel(android::Parcel* parcel) const override;
+    void copyTo(android::os::InputChannelCore& outChannel) const;
 
     /**
      * The connection token is used to identify the input connection, i.e.
@@ -323,26 +314,11 @@
      */
     sp<IBinder> getConnectionToken() const;
 
-    bool operator==(const InputChannel& inputChannel) const {
-        struct stat lhs, rhs;
-        if (fstat(mFd.get(), &lhs) != 0) {
-            return false;
-        }
-        if (fstat(inputChannel.getFd().get(), &rhs) != 0) {
-            return false;
-        }
-        // If file descriptors are pointing to same inode they are duplicated fds.
-        return inputChannel.getName() == getName() && inputChannel.getConnectionToken() == mToken &&
-                lhs.st_ino == rhs.st_ino;
-    }
-
 private:
-    base::unique_fd dupFd() const;
+    static std::unique_ptr<InputChannel> create(const std::string& name,
+                                                android::base::unique_fd fd, sp<IBinder> token);
 
-    std::string mName;
-    base::unique_fd mFd;
-
-    sp<IBinder> mToken;
+    InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token);
 };
 
 /*
@@ -357,7 +333,7 @@
     ~InputPublisher();
 
     /* Gets the underlying input channel. */
-    inline std::shared_ptr<InputChannel> getChannel() { return mChannel; }
+    inline std::shared_ptr<InputChannel> getChannel() const { return mChannel; }
 
     /* Publishes a key event to the input channel.
      *
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index 918680d..5ac965f 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -10,11 +10,15 @@
 cc_test {
     name: "ftl_test",
     test_suites: ["device-tests"],
+    header_libs: [
+        "libbase_headers",
+    ],
     srcs: [
         "algorithm_test.cpp",
         "cast_test.cpp",
         "concat_test.cpp",
         "enum_test.cpp",
+        "expected_test.cpp",
         "fake_guard_test.cpp",
         "flags_test.cpp",
         "function_test.cpp",
diff --git a/libs/ftl/expected_test.cpp b/libs/ftl/expected_test.cpp
new file mode 100644
index 0000000..8cb07e4
--- /dev/null
+++ b/libs/ftl/expected_test.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2024 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 <ftl/expected.h>
+#include <gtest/gtest.h>
+
+#include <string>
+#include <system_error>
+
+namespace android::test {
+
+using IntExp = ftl::Expected<int, std::errc>;
+using StringExp = ftl::Expected<std::string, std::errc>;
+
+using namespace std::string_literals;
+
+TEST(Expected, Construct) {
+  // Default value.
+  EXPECT_TRUE(IntExp().has_value());
+  EXPECT_EQ(IntExp(), IntExp(0));
+
+  EXPECT_TRUE(StringExp().has_value());
+  EXPECT_EQ(StringExp(), StringExp(""));
+
+  // Value.
+  ASSERT_TRUE(IntExp(42).has_value());
+  EXPECT_EQ(42, IntExp(42).value());
+
+  ASSERT_TRUE(StringExp("test").has_value());
+  EXPECT_EQ("test"s, StringExp("test").value());
+
+  // Error.
+  const auto exp = StringExp(ftl::Unexpected(std::errc::invalid_argument));
+  ASSERT_FALSE(exp.has_value());
+  EXPECT_EQ(std::errc::invalid_argument, exp.error());
+}
+
+TEST(Expected, HasError) {
+  EXPECT_FALSE(IntExp(123).has_error([](auto) { return true; }));
+  EXPECT_FALSE(IntExp(ftl::Unexpected(std::errc::io_error)).has_error([](auto) { return false; }));
+
+  EXPECT_TRUE(StringExp(ftl::Unexpected(std::errc::permission_denied)).has_error([](auto e) {
+    return e == std::errc::permission_denied;
+  }));
+}
+
+TEST(Expected, ValueOpt) {
+  EXPECT_EQ(ftl::Optional(-1), IntExp(-1).value_opt());
+  EXPECT_EQ(std::nullopt, IntExp(ftl::Unexpected(std::errc::broken_pipe)).value_opt());
+
+  {
+    const StringExp exp("foo"s);
+    EXPECT_EQ(ftl::Optional('f'),
+              exp.value_opt().transform([](const auto& s) { return s.front(); }));
+    EXPECT_EQ("foo"s, exp.value());
+  }
+  {
+    StringExp exp("foobar"s);
+    EXPECT_EQ(ftl::Optional(6), std::move(exp).value_opt().transform(&std::string::length));
+    EXPECT_TRUE(exp.value().empty());
+  }
+}
+
+}  // namespace android::test
diff --git a/libs/ftl/future_test.cpp b/libs/ftl/future_test.cpp
index 5a245b6..1140639 100644
--- a/libs/ftl/future_test.cpp
+++ b/libs/ftl/future_test.cpp
@@ -102,4 +102,42 @@
   decrement_thread.join();
 }
 
+TEST(Future, WaitFor) {
+  using namespace std::chrono_literals;
+  {
+    auto future = ftl::yield(42);
+    // Check that we can wait_for multiple times without invalidating the future
+    EXPECT_EQ(future.wait_for(1s), std::future_status::ready);
+    EXPECT_EQ(future.wait_for(1s), std::future_status::ready);
+    EXPECT_EQ(future.get(), 42);
+  }
+
+  {
+    std::condition_variable cv;
+    std::mutex m;
+    bool ready = false;
+
+    std::packaged_task<int32_t()> get_int([&] {
+      std::unique_lock lk(m);
+      cv.wait(lk, [&] { return ready; });
+      return 24;
+    });
+
+    auto get_future = ftl::Future(get_int.get_future());
+    std::thread get_thread(std::move(get_int));
+
+    EXPECT_EQ(get_future.wait_for(0s), std::future_status::timeout);
+    {
+      std::unique_lock lk(m);
+      ready = true;
+    }
+    cv.notify_one();
+
+    EXPECT_EQ(get_future.wait_for(1s), std::future_status::ready);
+    EXPECT_EQ(get_future.get(), 24);
+
+    get_thread.join();
+  }
+}
+
 }  // namespace android::test
diff --git a/libs/ftl/small_map_test.cpp b/libs/ftl/small_map_test.cpp
index 634877f..e96d70d 100644
--- a/libs/ftl/small_map_test.cpp
+++ b/libs/ftl/small_map_test.cpp
@@ -189,9 +189,20 @@
   }
 }
 
-TEST(SmallMap, TryEmplace) {
-  SmallMap<int, std::string, 3> map;
-  using Pair = decltype(map)::value_type;
+template <typename Capacity>
+struct SmallMapTest : testing::Test {
+  static constexpr std::size_t kCapacity = Capacity{}();
+};
+
+template <std::size_t N>
+using Capacity = std::integral_constant<std::size_t, N>;
+
+using Capacities = testing::Types<Capacity<3>, Capacity<0>>;
+TYPED_TEST_SUITE(SmallMapTest, Capacities, );
+
+TYPED_TEST(SmallMapTest, TryEmplace) {
+  SmallMap<int, std::string, TestFixture::kCapacity> map;
+  using Pair = typename decltype(map)::value_type;
 
   {
     const auto [it, ok] = map.try_emplace(123, "abc");
@@ -207,14 +218,22 @@
     const auto [it, ok] = map.try_emplace(-1);
     ASSERT_TRUE(ok);
     EXPECT_EQ(*it, Pair(-1, std::string()));
-    EXPECT_FALSE(map.dynamic());
+    if constexpr (map.static_capacity() > 0) {
+      EXPECT_FALSE(map.dynamic());
+    } else {
+      EXPECT_TRUE(map.dynamic());
+    }
   }
   {
     // Insertion fails if mapping exists.
     const auto [it, ok] = map.try_emplace(42, "!!!");
     EXPECT_FALSE(ok);
     EXPECT_EQ(*it, Pair(42, "???"));
-    EXPECT_FALSE(map.dynamic());
+    if constexpr (map.static_capacity() > 0) {
+      EXPECT_FALSE(map.dynamic());
+    } else {
+      EXPECT_TRUE(map.dynamic());
+    }
   }
   {
     // Insertion at capacity promotes the map.
@@ -240,9 +259,9 @@
 
 }  // namespace
 
-TEST(SmallMap, TryReplace) {
-  SmallMap<int, String, 3> map = ftl::init::map(1, "a")(2, "B");
-  using Pair = decltype(map)::value_type;
+TYPED_TEST(SmallMapTest, TryReplace) {
+  SmallMap<int, String, TestFixture::kCapacity> map = ftl::init::map(1, "a")(2, "B");
+  using Pair = typename decltype(map)::value_type;
 
   {
     // Replacing fails unless mapping exists.
@@ -260,7 +279,12 @@
     EXPECT_EQ(*it, Pair(2, "b"));
   }
 
-  EXPECT_FALSE(map.dynamic());
+  if constexpr (map.static_capacity() > 0) {
+    EXPECT_FALSE(map.dynamic());
+  } else {
+    EXPECT_TRUE(map.dynamic());
+  }
+
   EXPECT_TRUE(map.try_emplace(3, "abc").second);
   EXPECT_TRUE(map.try_emplace(4, "d").second);
   EXPECT_TRUE(map.dynamic());
@@ -284,9 +308,9 @@
   EXPECT_EQ(map, SmallMap(ftl::init::map(4, "d"s)(3, "c"s)(2, "b"s)(1, "a"s)));
 }
 
-TEST(SmallMap, EmplaceOrReplace) {
-  SmallMap<int, String, 3> map = ftl::init::map(1, "a")(2, "B");
-  using Pair = decltype(map)::value_type;
+TYPED_TEST(SmallMapTest, EmplaceOrReplace) {
+  SmallMap<int, String, TestFixture::kCapacity> map = ftl::init::map(1, "a")(2, "B");
+  using Pair = typename decltype(map)::value_type;
 
   {
     // New mapping is emplaced.
@@ -305,7 +329,12 @@
     EXPECT_EQ(*it, Pair(2, "b"));
   }
 
-  EXPECT_FALSE(map.dynamic());
+  if constexpr (map.static_capacity() > 0) {
+    EXPECT_FALSE(map.dynamic());
+  } else {
+    EXPECT_TRUE(map.dynamic());
+  }
+
   EXPECT_FALSE(map.emplace_or_replace(3, "abc").second);  // Replace.
   EXPECT_TRUE(map.emplace_or_replace(4, "d").second);     // Emplace.
   EXPECT_TRUE(map.dynamic());
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 7d37fd3..4518b67 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -116,7 +116,7 @@
     std::lock_guard<std::mutex> _l(gChoreographers.lock);
     gChoreographers.ptrs.erase(std::remove_if(gChoreographers.ptrs.begin(),
                                               gChoreographers.ptrs.end(),
-                                              [=](Choreographer* c) { return c == this; }),
+                                              [=, this](Choreographer* c) { return c == this; }),
                                gChoreographers.ptrs.end());
     // Only poke DisplayManagerGlobal to unregister if we previously registered
     // callbacks.
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 38fab9c..7564c26 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -199,7 +199,7 @@
     SAFE_PARCEL(output.writeParcelable, trustedPresentationListener);
     SAFE_PARCEL(output.writeFloat, currentHdrSdrRatio);
     SAFE_PARCEL(output.writeFloat, desiredHdrSdrRatio);
-    SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(cachingHint))
+    SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(cachingHint));
     return NO_ERROR;
 }
 
@@ -484,6 +484,12 @@
             flags &= ~eLayerIsDisplayDecoration;
             ALOGE("Stripped attempt to set LayerIsDisplayDecoration in sanitize");
         }
+        if ((mask & eCanOccludePresentation) &&
+            !(permissions & Permission::ACCESS_SURFACE_FLINGER)) {
+            flags &= ~eCanOccludePresentation;
+            mask &= ~eCanOccludePresentation;
+            ALOGE("Stripped attempt to set eCanOccludePresentation in sanitize");
+        }
     }
 
     if (what & layer_state_t::eInputInfoChanged) {
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 8d18551..83c2b7f 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -56,6 +56,7 @@
 
 #include <android-base/thread_annotations.h>
 #include <gui/LayerStatePermissions.h>
+#include <gui/ScreenCaptureResults.h>
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 
@@ -3138,11 +3139,19 @@
 }
 
 status_t ScreenshotClient::captureLayers(const LayerCaptureArgs& captureArgs,
-                                         const sp<IScreenCaptureListener>& captureListener) {
+                                         const sp<IScreenCaptureListener>& captureListener,
+                                         bool sync) {
     sp<gui::ISurfaceComposer> s(ComposerServiceAIDL::getComposerService());
     if (s == nullptr) return NO_INIT;
 
-    binder::Status status = s->captureLayers(captureArgs, captureListener);
+    binder::Status status;
+    if (sync) {
+        gui::ScreenCaptureResults captureResults;
+        status = s->captureLayersSync(captureArgs, &captureResults);
+        captureListener->onScreenCaptureCompleted(captureResults);
+    } else {
+        status = s->captureLayers(captureArgs, captureListener);
+    }
     return statusTFromBinderStatus(status);
 }
 
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 95b2641..9429d2c 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -109,7 +109,8 @@
             info.inputConfig == inputConfig && info.displayId == displayId &&
             info.replaceTouchableRegionWithCrop == replaceTouchableRegionWithCrop &&
             info.applicationInfo == applicationInfo && info.layoutParamsType == layoutParamsType &&
-            info.layoutParamsFlags == layoutParamsFlags;
+            info.layoutParamsFlags == layoutParamsFlags &&
+            info.canOccludePresentation == canOccludePresentation;
 }
 
 status_t WindowInfo::writeToParcel(android::Parcel* parcel) const {
@@ -158,8 +159,9 @@
         parcel->write(touchableRegion) ?:
         parcel->writeBool(replaceTouchableRegionWithCrop) ?:
         parcel->writeStrongBinder(touchableRegionCropHandle.promote()) ?:
-        parcel->writeStrongBinder(windowToken);
-        parcel->writeStrongBinder(focusTransferTarget);
+        parcel->writeStrongBinder(windowToken) ?:
+        parcel->writeStrongBinder(focusTransferTarget) ?:
+        parcel->writeBool(canOccludePresentation);
     // clang-format on
     return status;
 }
@@ -210,7 +212,8 @@
         parcel->readBool(&replaceTouchableRegionWithCrop) ?:
         parcel->readNullableStrongBinder(&touchableRegionCropHandleSp) ?:
         parcel->readNullableStrongBinder(&windowToken) ?:
-        parcel->readNullableStrongBinder(&focusTransferTarget);
+        parcel->readNullableStrongBinder(&focusTransferTarget) ?:
+        parcel->readBool(&canOccludePresentation);
 
     // clang-format on
 
@@ -273,6 +276,7 @@
         << "ms, token=" << info.token.get()
         << ", touchOcclusionMode=" << ftl::enum_string(info.touchOcclusionMode) << "\n"
         << transform;
+    if (info.canOccludePresentation) out << " canOccludePresentation";
     return out;
 }
 
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index e3122bc..51e0193 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -46,6 +46,7 @@
 import android.gui.LayerDebugInfo;
 import android.gui.OverlayProperties;
 import android.gui.PullAtomData;
+import android.gui.ScreenCaptureResults;
 import android.gui.ARect;
 import android.gui.SchedulingPolicy;
 import android.gui.StalledTransactionInfo;
@@ -245,6 +246,16 @@
     /**
      * Capture a subtree of the layer hierarchy, potentially ignoring the root node.
      * This requires READ_FRAME_BUFFER permission. This function will fail if there
+     * is a secure window on screen. This is a blocking call and will return the
+     * ScreenCaptureResults, including the captured buffer. Because this is blocking, the
+     * caller doesn't care about the fence and the binder thread in SurfaceFlinger will wait
+     * on the fence to fire before returning the results.
+     */
+    ScreenCaptureResults captureLayersSync(in LayerCaptureArgs args);
+
+    /**
+     * Capture a subtree of the layer hierarchy, potentially ignoring the root node.
+     * This requires READ_FRAME_BUFFER permission. This function will fail if there
      * is a secure window on screen
      */
     oneway void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener);
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index c952ba2..2bdbd43 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -104,6 +104,8 @@
                 (int64_t, const gui::CaptureArgs&, const sp<IScreenCaptureListener>&), (override));
     MOCK_METHOD(binder::Status, captureLayers,
                 (const LayerCaptureArgs&, const sp<IScreenCaptureListener>&), (override));
+    MOCK_METHOD(binder::Status, captureLayersSync,
+                (const LayerCaptureArgs&, gui::ScreenCaptureResults*), (override));
     MOCK_METHOD(binder::Status, clearAnimationFrameStats, (), (override));
     MOCK_METHOD(binder::Status, getAnimationFrameStats, (gui::FrameStats*), (override));
     MOCK_METHOD(binder::Status, overrideHdrTypes, (const sp<IBinder>&, const std::vector<int32_t>&),
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index e1dc791..920310e 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -161,6 +161,9 @@
         // See SurfaceView scaling behavior for more details.
         eIgnoreDestinationFrame = 0x400,
         eLayerIsRefreshRateIndicator = 0x800, // REFRESH_RATE_INDICATOR
+        // Sets a property on this layer indicating that its visible region should be considered
+        // when computing TrustedPresentation Thresholds.
+        eCanOccludePresentation = 0x1000,
     };
 
     enum {
@@ -416,26 +419,36 @@
 };
 
 struct DisplayState {
-    enum {
+    enum : uint32_t {
         eSurfaceChanged = 0x01,
         eLayerStackChanged = 0x02,
         eDisplayProjectionChanged = 0x04,
         eDisplaySizeChanged = 0x08,
-        eFlagsChanged = 0x10
+        eFlagsChanged = 0x10,
+
+        eAllChanged = ~0u
     };
 
+    // Not for direct use. Prefer constructor below for new displays.
     DisplayState();
+
+    DisplayState(sp<IBinder> token, ui::LayerStack layerStack)
+          : what(eAllChanged),
+            token(std::move(token)),
+            layerStack(layerStack),
+            layerStackSpaceRect(Rect::INVALID_RECT),
+            orientedDisplaySpaceRect(Rect::INVALID_RECT) {}
+
     void merge(const DisplayState& other);
     void sanitize(int32_t permissions);
 
     uint32_t what = 0;
     uint32_t flags = 0;
     sp<IBinder> token;
-    sp<IGraphicBufferProducer> surface;
 
     ui::LayerStack layerStack = ui::DEFAULT_LAYER_STACK;
 
-    // These states define how layers are projected onto the physical display.
+    // These states define how layers are projected onto the physical or virtual display.
     //
     // Layers are first clipped to `layerStackSpaceRect'.  They are then translated and
     // scaled from `layerStackSpaceRect' to `orientedDisplaySpaceRect'.  Finally, they are rotated
@@ -446,10 +459,17 @@
     // will be scaled by a factor of 2 and translated by (20, 10). When orientation is 1, layers
     // will be additionally rotated by 90 degrees around the origin clockwise and translated by (W,
     // 0).
+    //
+    // Rect::INVALID_RECT sizes the space to the active resolution of the physical display, or the
+    // default dimensions of the virtual display surface.
+    //
     ui::Rotation orientation = ui::ROTATION_0;
     Rect layerStackSpaceRect = Rect::EMPTY_RECT;
     Rect orientedDisplaySpaceRect = Rect::EMPTY_RECT;
 
+    // Exclusive to virtual displays: The sink surface into which the virtual display is rendered,
+    // and an optional resolution that overrides its default dimensions.
+    sp<IGraphicBufferProducer> surface;
     uint32_t width = 0;
     uint32_t height = 0;
 
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 14e3dd5..88a2c34 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -849,7 +849,8 @@
     static status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
     static status_t captureDisplay(DisplayId, const gui::CaptureArgs&,
                                    const sp<IScreenCaptureListener>&);
-    static status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
+    static status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&,
+                                  bool sync);
 
     [[deprecated]] static status_t captureDisplay(DisplayId id,
                                                   const sp<IScreenCaptureListener>& listener) {
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index e72fd59..32d60be 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -246,6 +246,10 @@
     // any other window.
     sp<IBinder> focusTransferTarget;
 
+    // Sets a property on this window indicating that its visible region should be considered when
+    // computing TrustedPresentation Thresholds.
+    bool canOccludePresentation = false;
+
     void setInputConfig(ftl::Flags<InputConfig> config, bool value);
 
     void addTouchableRegion(const Rect& region);
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index b9ab803..a9d6e8d 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -63,8 +63,7 @@
 using Transaction = SurfaceComposerClient::Transaction;
 
 sp<IInputFlinger> getInputFlinger() {
-   sp<IBinder> input(defaultServiceManager()->getService(
-            String16("inputflinger")));
+    sp<IBinder> input(defaultServiceManager()->waitForService(String16("inputflinger")));
     if (input == nullptr) {
         ALOGE("Failed to link to input service");
     } else { ALOGE("Linked to input"); }
@@ -104,8 +103,13 @@
         if (noInputChannel) {
             mInputInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, true);
         } else {
-            mClientChannel = std::make_shared<InputChannel>();
-            mInputFlinger->createInputChannel("testchannels", mClientChannel.get());
+            android::os::InputChannelCore tempChannel;
+            android::binder::Status result =
+                    mInputFlinger->createInputChannel("testchannels", &tempChannel);
+            if (!result.isOk()) {
+                ADD_FAILURE() << "binder call to createInputChannel failed";
+            }
+            mClientChannel = InputChannel::create(std::move(tempChannel));
             mInputInfo.token = mClientChannel->getConnectionToken();
             mInputConsumer = new InputConsumer(mClientChannel);
         }
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index c6ea317..577d239 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -791,6 +791,10 @@
         return binder::Status::ok();
     }
 
+    binder::Status captureLayersSync(const LayerCaptureArgs&, ScreenCaptureResults*) override {
+        return binder::Status::ok();
+    }
+
     binder::Status captureLayers(const LayerCaptureArgs&,
                                  const sp<IScreenCaptureListener>&) override {
         return binder::Status::ok();
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 3d3bf13..e5fb2c5 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -175,6 +175,7 @@
     ],
     srcs: [
         "android/os/IInputFlinger.aidl",
+        "android/os/InputChannelCore.aidl",
         "AccelerationCurve.cpp",
         "Input.cpp",
         "InputDevice.cpp",
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 8eaff00..0d29b4d 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -374,7 +374,7 @@
     out << ", deviceId=" << event.getDeviceId();
     out << ", source=" << inputEventSourceToString(event.getSource());
     out << ", displayId=" << event.getDisplayId();
-    out << ", eventId=" << event.getId();
+    out << ", eventId=0x" << std::hex << event.getId() << std::dec;
     out << "}";
     return out;
 }
@@ -1051,7 +1051,7 @@
     out << ", deviceId=" << event.getDeviceId();
     out << ", source=" << inputEventSourceToString(event.getSource());
     out << ", displayId=" << event.getDisplayId();
-    out << ", eventId=" << event.getId();
+    out << ", eventId=0x" << std::hex << event.getId() << std::dec;
     out << "}";
     return out;
 }
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 9c7c0c1..d4dbc45 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -190,14 +190,16 @@
         mHasSensor(other.mHasSensor),
         mMotionRanges(other.mMotionRanges),
         mSensors(other.mSensors),
-        mLights(other.mLights) {}
+        mLights(other.mLights),
+        mViewBehavior(other.mViewBehavior) {}
 
 InputDeviceInfo::~InputDeviceInfo() {
 }
 
 void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t controllerNumber,
                                  const InputDeviceIdentifier& identifier, const std::string& alias,
-                                 bool isExternal, bool hasMic, int32_t associatedDisplayId) {
+                                 bool isExternal, bool hasMic, int32_t associatedDisplayId,
+                                 InputDeviceViewBehavior viewBehavior) {
     mId = id;
     mGeneration = generation;
     mControllerNumber = controllerNumber;
@@ -212,6 +214,7 @@
     mHasBattery = false;
     mHasButtonUnderPad = false;
     mHasSensor = false;
+    mViewBehavior = viewBehavior;
     mUsiVersion.reset();
     mMotionRanges.clear();
     mSensors.clear();
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index 0e627e5..8db0ca5 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -348,7 +348,9 @@
     DEFINE_KEYCODE(MACRO_1), \
     DEFINE_KEYCODE(MACRO_2), \
     DEFINE_KEYCODE(MACRO_3), \
-    DEFINE_KEYCODE(MACRO_4)
+    DEFINE_KEYCODE(MACRO_4), \
+    DEFINE_KEYCODE(EMOJI_PICKER), \
+    DEFINE_KEYCODE(SCREENSHOT)
 
 // NOTE: If you add a new axis here you must also add it to several other files.
 //       Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 37de00c..0e0e80d 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -95,6 +95,21 @@
     return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
 }
 
+android::base::unique_fd dupChannelFd(int fd) {
+    android::base::unique_fd newFd(::dup(fd));
+    if (!newFd.ok()) {
+        ALOGE("Could not duplicate fd %i : %s", fd, strerror(errno));
+        const bool hitFdLimit = errno == EMFILE || errno == ENFILE;
+        // If this process is out of file descriptors, then throwing that might end up exploding
+        // on the other side of a binder call, which isn't really helpful.
+        // Better to just crash here and hope that the FD leak is slow.
+        // Other failures could be client errors, so we still propagate those back to the caller.
+        LOG_ALWAYS_FATAL_IF(hitFdLimit, "Too many open files, could not duplicate input channel");
+        return {};
+    }
+    return newFd;
+}
+
 } // namespace
 
 using android::base::Result;
@@ -395,15 +410,23 @@
     return std::unique_ptr<InputChannel>(new InputChannel(name, std::move(fd), token));
 }
 
-InputChannel::InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token)
-      : mName(std::move(name)), mFd(std::move(fd)), mToken(std::move(token)) {
+std::unique_ptr<InputChannel> InputChannel::create(
+        android::os::InputChannelCore&& parceledChannel) {
+    return InputChannel::create(parceledChannel.name, parceledChannel.fd.release(),
+                                parceledChannel.token);
+}
+
+InputChannel::InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token) {
+    this->name = std::move(name);
+    this->fd.reset(std::move(fd));
+    this->token = std::move(token);
     ALOGD_IF(DEBUG_CHANNEL_LIFECYCLE, "Input channel constructed: name='%s', fd=%d",
-             getName().c_str(), getFd().get());
+             getName().c_str(), getFd());
 }
 
 InputChannel::~InputChannel() {
     ALOGD_IF(DEBUG_CHANNEL_LIFECYCLE, "Input channel destroyed: name='%s', fd=%d",
-             getName().c_str(), getFd().get());
+             getName().c_str(), getFd());
 }
 
 status_t InputChannel::openInputChannelPair(const std::string& name,
@@ -441,19 +464,19 @@
     ATRACE_NAME_IF(ATRACE_ENABLED(),
                    StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=0x%" PRIx32
                                 ")",
-                                mName.c_str(), msg->header.seq, msg->header.type));
+                                name.c_str(), msg->header.seq, msg->header.type));
     const size_t msgLength = msg->size();
     InputMessage cleanMsg;
     msg->getSanitizedCopy(&cleanMsg);
     ssize_t nWrite;
     do {
-        nWrite = ::send(getFd().get(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
+        nWrite = ::send(getFd(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
     } while (nWrite == -1 && errno == EINTR);
 
     if (nWrite < 0) {
         int error = errno;
         ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ error sending message of type %s, %s",
-                 mName.c_str(), ftl::enum_string(msg->header.type).c_str(), strerror(error));
+                 name.c_str(), ftl::enum_string(msg->header.type).c_str(), strerror(error));
         if (error == EAGAIN || error == EWOULDBLOCK) {
             return WOULD_BLOCK;
         }
@@ -465,12 +488,12 @@
 
     if (size_t(nWrite) != msgLength) {
         ALOGD_IF(DEBUG_CHANNEL_MESSAGES,
-                 "channel '%s' ~ error sending message type %s, send was incomplete", mName.c_str(),
+                 "channel '%s' ~ error sending message type %s, send was incomplete", name.c_str(),
                  ftl::enum_string(msg->header.type).c_str());
         return DEAD_OBJECT;
     }
 
-    ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ sent message of type %s", mName.c_str(),
+    ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ sent message of type %s", name.c_str(),
              ftl::enum_string(msg->header.type).c_str());
 
     return OK;
@@ -479,13 +502,13 @@
 status_t InputChannel::receiveMessage(InputMessage* msg) {
     ssize_t nRead;
     do {
-        nRead = ::recv(getFd().get(), msg, sizeof(InputMessage), MSG_DONTWAIT);
+        nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT);
     } while (nRead == -1 && errno == EINTR);
 
     if (nRead < 0) {
         int error = errno;
         ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ receive message failed, errno=%d",
-                 mName.c_str(), errno);
+                 name.c_str(), errno);
         if (error == EAGAIN || error == EWOULDBLOCK) {
             return WOULD_BLOCK;
         }
@@ -497,29 +520,29 @@
 
     if (nRead == 0) { // check for EOF
         ALOGD_IF(DEBUG_CHANNEL_MESSAGES,
-                 "channel '%s' ~ receive message failed because peer was closed", mName.c_str());
+                 "channel '%s' ~ receive message failed because peer was closed", name.c_str());
         return DEAD_OBJECT;
     }
 
     if (!msg->isValid(nRead)) {
-        ALOGE("channel '%s' ~ received invalid message of size %zd", mName.c_str(), nRead);
+        ALOGE("channel '%s' ~ received invalid message of size %zd", name.c_str(), nRead);
         return BAD_VALUE;
     }
 
-    ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", mName.c_str(),
+    ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", name.c_str(),
              ftl::enum_string(msg->header.type).c_str());
     if (ATRACE_ENABLED()) {
         // Add an additional trace point to include data about the received message.
         std::string message = StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32
                                            ", type=0x%" PRIx32 ")",
-                                           mName.c_str(), msg->header.seq, msg->header.type);
+                                           name.c_str(), msg->header.seq, msg->header.type);
         ATRACE_NAME(message.c_str());
     }
     return OK;
 }
 
 bool InputChannel::probablyHasInput() const {
-    struct pollfd pfds = {.fd = mFd, .events = POLLIN};
+    struct pollfd pfds = {.fd = fd.get(), .events = POLLIN};
     if (::poll(&pfds, /*nfds=*/1, /*timeout=*/0) <= 0) {
         // This can be a false negative because EINTR and ENOMEM are not handled. The latter should
         // be extremely rare. The EINTR is also unlikely because it happens only when the signal
@@ -538,7 +561,7 @@
     if (timeout < 0ms) {
         LOG(FATAL) << "Timeout cannot be negative, received " << timeout.count();
     }
-    struct pollfd pfds = {.fd = mFd, .events = POLLIN};
+    struct pollfd pfds = {.fd = fd.get(), .events = POLLIN};
     int ret;
     std::chrono::time_point<std::chrono::steady_clock> stopTime =
             std::chrono::steady_clock::now() + timeout;
@@ -551,53 +574,18 @@
 }
 
 std::unique_ptr<InputChannel> InputChannel::dup() const {
-    base::unique_fd newFd(dupFd());
+    base::unique_fd newFd(dupChannelFd(fd.get()));
     return InputChannel::create(getName(), std::move(newFd), getConnectionToken());
 }
 
-void InputChannel::copyTo(InputChannel& outChannel) const {
-    outChannel.mName = getName();
-    outChannel.mFd = dupFd();
-    outChannel.mToken = getConnectionToken();
-}
-
-status_t InputChannel::writeToParcel(android::Parcel* parcel) const {
-    if (parcel == nullptr) {
-        ALOGE("%s: Null parcel", __func__);
-        return BAD_VALUE;
-    }
-    return parcel->writeStrongBinder(mToken)
-            ?: parcel->writeUtf8AsUtf16(mName) ?: parcel->writeUniqueFileDescriptor(mFd);
-}
-
-status_t InputChannel::readFromParcel(const android::Parcel* parcel) {
-    if (parcel == nullptr) {
-        ALOGE("%s: Null parcel", __func__);
-        return BAD_VALUE;
-    }
-    mToken = parcel->readStrongBinder();
-    return parcel->readUtf8FromUtf16(&mName) ?: parcel->readUniqueFileDescriptor(&mFd);
+void InputChannel::copyTo(android::os::InputChannelCore& outChannel) const {
+    outChannel.name = getName();
+    outChannel.fd.reset(dupChannelFd(fd.get()));
+    outChannel.token = getConnectionToken();
 }
 
 sp<IBinder> InputChannel::getConnectionToken() const {
-    return mToken;
-}
-
-base::unique_fd InputChannel::dupFd() const {
-    base::unique_fd newFd(::dup(getFd().get()));
-    if (!newFd.ok()) {
-        ALOGE("Could not duplicate fd %i for channel %s: %s", getFd().get(), getName().c_str(),
-              strerror(errno));
-        const bool hitFdLimit = errno == EMFILE || errno == ENFILE;
-        // If this process is out of file descriptors, then throwing that might end up exploding
-        // on the other side of a binder call, which isn't really helpful.
-        // Better to just crash here and hope that the FD leak is slow.
-        // Other failures could be client errors, so we still propagate those back to the caller.
-        LOG_ALWAYS_FATAL_IF(hitFdLimit, "Too many open files, could not duplicate input channel %s",
-                            getName().c_str());
-        return {};
-    }
-    return newFd;
+    return token;
 }
 
 // --- InputPublisher ---
diff --git a/libs/input/android/os/IInputFlinger.aidl b/libs/input/android/os/IInputFlinger.aidl
index 00ebd4d..c1aacfb 100644
--- a/libs/input/android/os/IInputFlinger.aidl
+++ b/libs/input/android/os/IInputFlinger.aidl
@@ -16,14 +16,13 @@
 
 package android.os;
 
-import android.InputChannel;
+import android.os.InputChannelCore;
 import android.gui.FocusRequest;
-import android.gui.WindowInfo;
 
 /** @hide */
 interface IInputFlinger
 {
-    InputChannel createInputChannel(in @utf8InCpp String name);
+    InputChannelCore createInputChannel(in @utf8InCpp String name);
     void removeInputChannel(in IBinder connectionToken);
     /**
      * Sets focus to the window identified by the token. This must be called
diff --git a/libs/input/android/InputChannel.aidl b/libs/input/android/os/InputChannelCore.aidl
similarity index 72%
rename from libs/input/android/InputChannel.aidl
rename to libs/input/android/os/InputChannelCore.aidl
index c2d1112..888a553 100644
--- a/libs/input/android/InputChannel.aidl
+++ b/libs/input/android/os/InputChannelCore.aidl
@@ -15,6 +15,16 @@
 ** limitations under the License.
 */
 
-package android;
+package android.os;
 
-parcelable InputChannel cpp_header "input/InputTransport.h";
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Input channel struct for sending InputChannel between processes.
+ * @hide
+ */
+parcelable InputChannelCore {
+    @utf8InCpp String name;
+    ParcelFileDescriptor fd;
+    IBinder token;
+}
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 0c850fe..ec7a284 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -26,6 +26,13 @@
   namespace: "input"
   description: "Set to true to enable timer support for the touchpad Gestures library"
   bug: "297192727"
+ }
+
+ flag {
+  name: "enable_input_event_tracing"
+  namespace: "input"
+  description: "Set to true to enable input event tracing, including always-on tracing on non-user builds"
+  bug: "210460522"
 }
 
 flag {
@@ -104,3 +111,10 @@
   description: "Move user-activity poke rate-limiting from PowerManagerService to InputDispatcher."
   bug: "320499729"
 }
+
+flag {
+  name: "input_device_view_behavior_api"
+  namespace: "input"
+  description: "Controls the API to provide InputDevice view behavior."
+  bug: "246946631"
+}
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
index 867af79..b1d7760 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -79,7 +79,7 @@
         logger::init(
             logger::Config::default()
                 .with_tag_on_device("InputVerifier")
-                .with_min_level(log::Level::Trace),
+                .with_max_level(log::LevelFilter::Trace),
         );
         Self {
             name: name.to_owned(),
diff --git a/libs/input/tests/InputChannel_test.cpp b/libs/input/tests/InputChannel_test.cpp
index 650c930..60feb53 100644
--- a/libs/input/tests/InputChannel_test.cpp
+++ b/libs/input/tests/InputChannel_test.cpp
@@ -32,37 +32,31 @@
 
 namespace android {
 
+namespace {
+bool operator==(const InputChannel& left, const InputChannel& right) {
+    struct stat lhs, rhs;
+    if (fstat(left.getFd(), &lhs) != 0) {
+        return false;
+    }
+    if (fstat(right.getFd(), &rhs) != 0) {
+        return false;
+    }
+    // If file descriptors are pointing to same inode they are duplicated fds.
+    return left.getName() == right.getName() &&
+            left.getConnectionToken() == right.getConnectionToken() && lhs.st_ino == rhs.st_ino;
+}
+} // namespace
+
 class InputChannelTest : public testing::Test {
 };
 
+TEST_F(InputChannelTest, ClientAndServerTokensMatch) {
+    std::unique_ptr<InputChannel> serverChannel, clientChannel;
 
-TEST_F(InputChannelTest, ConstructorAndDestructor_TakesOwnershipOfFileDescriptors) {
-    // Our purpose here is to verify that the input channel destructor closes the
-    // file descriptor provided to it.  One easy way is to provide it with one end
-    // of a pipe and to check for EPIPE on the other end after the channel is destroyed.
-    Pipe pipe;
-
-    android::base::unique_fd sendFd(pipe.sendFd);
-
-    std::unique_ptr<InputChannel> inputChannel =
-            InputChannel::create("channel name", std::move(sendFd), new BBinder());
-
-    EXPECT_NE(inputChannel, nullptr) << "channel should be successfully created";
-    EXPECT_STREQ("channel name", inputChannel->getName().c_str())
-            << "channel should have provided name";
-    EXPECT_NE(-1, inputChannel->getFd()) << "channel should have valid fd";
-
-    // InputChannel should be the owner of the file descriptor now
-    ASSERT_FALSE(sendFd.ok());
-}
-
-TEST_F(InputChannelTest, SetAndGetToken) {
-    Pipe pipe;
-    sp<IBinder> token = new BBinder();
-    std::unique_ptr<InputChannel> channel =
-            InputChannel::create("test channel", android::base::unique_fd(pipe.sendFd), token);
-
-    EXPECT_EQ(token, channel->getConnectionToken());
+    status_t result =
+            InputChannel::openInputChannelPair("channel name", serverChannel, clientChannel);
+    ASSERT_EQ(OK, result) << "should have successfully opened a channel pair";
+    EXPECT_EQ(serverChannel->getConnectionToken(), clientChannel->getConnectionToken());
 }
 
 TEST_F(InputChannelTest, OpenInputChannelPair_ReturnsAPairOfConnectedChannels) {
@@ -71,8 +65,7 @@
     status_t result = InputChannel::openInputChannelPair("channel name",
             serverChannel, clientChannel);
 
-    ASSERT_EQ(OK, result)
-            << "should have successfully opened a channel pair";
+    ASSERT_EQ(OK, result) << "should have successfully opened a channel pair";
 
     // Name
     EXPECT_STREQ("channel name (server)", serverChannel->getName().c_str())
@@ -235,25 +228,6 @@
     }
 }
 
-TEST_F(InputChannelTest, InputChannelParcelAndUnparcel) {
-    std::unique_ptr<InputChannel> serverChannel, clientChannel;
-
-    status_t result =
-            InputChannel::openInputChannelPair("channel parceling", serverChannel, clientChannel);
-
-    ASSERT_EQ(OK, result) << "should have successfully opened a channel pair";
-
-    InputChannel chan;
-    Parcel parcel;
-    ASSERT_EQ(OK, serverChannel->writeToParcel(&parcel));
-    parcel.setDataPosition(0);
-    chan.readFromParcel(&parcel);
-
-    EXPECT_EQ(chan == *serverChannel, true)
-            << "inputchannel should be equal after parceling and unparceling.\n"
-            << "name " << chan.getName() << " name " << serverChannel->getName();
-}
-
 TEST_F(InputChannelTest, DuplicateChannelAndAssertEqual) {
     std::unique_ptr<InputChannel> serverChannel, clientChannel;
 
diff --git a/libs/nativewindow/rust/src/surface.rs b/libs/nativewindow/rust/src/surface.rs
index c812612..25fea80 100644
--- a/libs/nativewindow/rust/src/surface.rs
+++ b/libs/nativewindow/rust/src/surface.rs
@@ -127,6 +127,9 @@
 // SAFETY: The underlying *ANativeWindow can be moved between threads.
 unsafe impl Send for Surface {}
 
+// SAFETY: The underlying *ANativeWindow can be used from multiple threads concurrently.
+unsafe impl Sync for Surface {}
+
 /// An error code returned by methods on [`Surface`].
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 pub struct ErrorCode(i32);
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index 3e1ac33..233134d 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -28,28 +28,28 @@
 namespace renderengine {
 
 std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArgs& args) {
-    switch (args.renderEngineType) {
-        case RenderEngineType::SKIA_GL:
+    if (args.threaded == Threaded::YES) {
+        switch (args.graphicsApi) {
+            case GraphicsApi::GL:
+                ALOGD("Threaded RenderEngine with SkiaGL Backend");
+                return renderengine::threaded::RenderEngineThreaded::create([args]() {
+                    return android::renderengine::skia::SkiaGLRenderEngine::create(args);
+                });
+            case GraphicsApi::VK:
+                ALOGD("Threaded RenderEngine with SkiaVK Backend");
+                return renderengine::threaded::RenderEngineThreaded::create([args]() {
+                    return android::renderengine::skia::SkiaVkRenderEngine::create(args);
+                });
+        }
+    }
+
+    switch (args.graphicsApi) {
+        case GraphicsApi::GL:
             ALOGD("RenderEngine with SkiaGL Backend");
             return renderengine::skia::SkiaGLRenderEngine::create(args);
-        case RenderEngineType::SKIA_VK:
+        case GraphicsApi::VK:
             ALOGD("RenderEngine with SkiaVK Backend");
             return renderengine::skia::SkiaVkRenderEngine::create(args);
-        case RenderEngineType::SKIA_GL_THREADED: {
-            ALOGD("Threaded RenderEngine with SkiaGL Backend");
-            return renderengine::threaded::RenderEngineThreaded::create(
-                    [args]() {
-                        return android::renderengine::skia::SkiaGLRenderEngine::create(args);
-                    },
-                    args.renderEngineType);
-        }
-        case RenderEngineType::SKIA_VK_THREADED:
-            ALOGD("Threaded RenderEngine with SkiaVK Backend");
-            return renderengine::threaded::RenderEngineThreaded::create(
-                    [args]() {
-                        return android::renderengine::skia::SkiaVkRenderEngine::create(args);
-                    },
-                    args.renderEngineType);
     }
 }
 
diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp
index a7f1df9..101f519 100644
--- a/libs/renderengine/benchmark/RenderEngineBench.cpp
+++ b/libs/renderengine/benchmark/RenderEngineBench.cpp
@@ -30,46 +30,6 @@
 using namespace android::renderengine;
 
 ///////////////////////////////////////////////////////////////////////////////
-//  Helpers for Benchmark::Apply
-///////////////////////////////////////////////////////////////////////////////
-
-std::string RenderEngineTypeName(RenderEngine::RenderEngineType type) {
-    switch (type) {
-        case RenderEngine::RenderEngineType::SKIA_GL_THREADED:
-            return "skiaglthreaded";
-        case RenderEngine::RenderEngineType::SKIA_GL:
-            return "skiagl";
-        case RenderEngine::RenderEngineType::SKIA_VK:
-            return "skiavk";
-        case RenderEngine::RenderEngineType::SKIA_VK_THREADED:
-            return "skiavkthreaded";
-    }
-}
-
-/**
- * Passed (indirectly - see RunSkiaGLThreaded) to Benchmark::Apply to create a
- * Benchmark which specifies which RenderEngineType it uses.
- *
- * This simplifies calling ->Arg(type)->Arg(type) and provides strings to make
- * it obvious which version is being run.
- *
- * @param b The benchmark family
- * @param type The type of RenderEngine to use.
- */
-static void AddRenderEngineType(benchmark::internal::Benchmark* b,
-                                RenderEngine::RenderEngineType type) {
-    b->Arg(static_cast<int64_t>(type));
-    b->ArgName(RenderEngineTypeName(type));
-}
-
-/**
- * Run a benchmark once using SKIA_GL_THREADED.
- */
-static void RunSkiaGLThreaded(benchmark::internal::Benchmark* b) {
-    AddRenderEngineType(b, RenderEngine::RenderEngineType::SKIA_GL_THREADED);
-}
-
-///////////////////////////////////////////////////////////////////////////////
 //  Helpers for calling drawLayers
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -104,7 +64,8 @@
     return std::pair<uint32_t, uint32_t>(width, height);
 }
 
-static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::RenderEngineType type) {
+static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::Threaded threaded,
+                                                        RenderEngine::GraphicsApi graphicsApi) {
     auto args = RenderEngineCreationArgs::Builder()
                         .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
                         .setImageCacheSize(1)
@@ -112,7 +73,8 @@
                         .setPrecacheToneMapperShaderOnly(false)
                         .setSupportsBackgroundBlur(true)
                         .setContextPriority(RenderEngine::ContextPriority::REALTIME)
-                        .setRenderEngineType(type)
+                        .setThreaded(threaded)
+                        .setGraphicsApi(graphicsApi)
                         .build();
     return RenderEngine::create(args);
 }
@@ -214,8 +176,11 @@
 //  Benchmarks
 ///////////////////////////////////////////////////////////////////////////////
 
-void BM_blur(benchmark::State& benchState) {
-    auto re = createRenderEngine(static_cast<RenderEngine::RenderEngineType>(benchState.range()));
+template <class... Args>
+void BM_blur(benchmark::State& benchState, Args&&... args) {
+    auto args_tuple = std::make_tuple(std::move(args)...);
+    auto re = createRenderEngine(static_cast<RenderEngine::Threaded>(std::get<0>(args_tuple)),
+                                 static_cast<RenderEngine::GraphicsApi>(std::get<1>(args_tuple)));
 
     // Initially use cpu access so we can decode into it with AImageDecoder.
     auto [width, height] = getDisplaySize();
@@ -259,4 +224,5 @@
     benchDrawLayers(*re, layers, benchState, "blurred");
 }
 
-BENCHMARK(BM_blur)->Apply(RunSkiaGLThreaded);
+BENCHMARK_CAPTURE(BM_blur, SkiaGLThreaded, RenderEngine::Threaded::YES,
+                  RenderEngine::GraphicsApi::GL);
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 818d035..7047358 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -33,7 +33,7 @@
 #include <memory>
 
 /**
- * Allows to set RenderEngine backend to GLES (default) or SkiaGL (NOT yet supported).
+ * Allows to override the RenderEngine backend.
  */
 #define PROPERTY_DEBUG_RENDERENGINE_BACKEND "debug.renderengine.backend"
 
@@ -92,11 +92,14 @@
         REALTIME = 4,
     };
 
-    enum class RenderEngineType {
-        SKIA_GL = 3,
-        SKIA_GL_THREADED = 4,
-        SKIA_VK = 5,
-        SKIA_VK_THREADED = 6,
+    enum class Threaded {
+        NO,
+        YES,
+    };
+
+    enum class GraphicsApi {
+        GL,
+        VK,
     };
 
     static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
@@ -176,10 +179,9 @@
     // query is required to be thread safe.
     virtual bool supportsBackgroundBlur() = 0;
 
-    // Returns the current type of RenderEngine instance that was created.
     // TODO(b/180767535): This is only implemented to allow for backend-specific behavior, which
     // we should not allow in general, so remove this.
-    RenderEngineType getRenderEngineType() const { return mRenderEngineType; }
+    bool isThreaded() const { return mThreaded == Threaded::YES; }
 
     static void validateInputBufferUsage(const sp<GraphicBuffer>&);
     static void validateOutputBufferUsage(const sp<GraphicBuffer>&);
@@ -191,9 +193,9 @@
     virtual void setEnableTracing(bool /*tracingEnabled*/) {}
 
 protected:
-    RenderEngine() : RenderEngine(RenderEngineType::SKIA_GL) {}
+    RenderEngine() : RenderEngine(Threaded::NO) {}
 
-    RenderEngine(RenderEngineType type) : mRenderEngineType(type) {}
+    RenderEngine(Threaded threaded) : mThreaded(threaded) {}
 
     // Maps GPU resources for this buffer.
     // Note that work may be deferred to an additional thread, i.e. this call
@@ -228,7 +230,7 @@
     friend class impl::ExternalTexture;
     friend class threaded::RenderEngineThreaded;
     friend class RenderEngineTest_cleanupPostRender_cleansUpOnce_Test;
-    const RenderEngineType mRenderEngineType;
+    const Threaded mThreaded;
 
     // Update protectedContext mode depending on whether or not any layer has a protected buffer.
     void updateProtectedContext(const std::vector<LayerSettings>&,
@@ -251,7 +253,8 @@
     bool precacheToneMapperShaderOnly;
     bool supportsBackgroundBlur;
     RenderEngine::ContextPriority contextPriority;
-    RenderEngine::RenderEngineType renderEngineType;
+    RenderEngine::Threaded threaded;
+    RenderEngine::GraphicsApi graphicsApi;
 
     struct Builder;
 
@@ -261,14 +264,16 @@
                              bool _enableProtectedContext, bool _precacheToneMapperShaderOnly,
                              bool _supportsBackgroundBlur,
                              RenderEngine::ContextPriority _contextPriority,
-                             RenderEngine::RenderEngineType _renderEngineType)
+                             RenderEngine::Threaded _threaded,
+                             RenderEngine::GraphicsApi _graphicsApi)
           : pixelFormat(_pixelFormat),
             imageCacheSize(_imageCacheSize),
             enableProtectedContext(_enableProtectedContext),
             precacheToneMapperShaderOnly(_precacheToneMapperShaderOnly),
             supportsBackgroundBlur(_supportsBackgroundBlur),
             contextPriority(_contextPriority),
-            renderEngineType(_renderEngineType) {}
+            threaded(_threaded),
+            graphicsApi(_graphicsApi) {}
     RenderEngineCreationArgs() = delete;
 };
 
@@ -299,14 +304,18 @@
         this->contextPriority = contextPriority;
         return *this;
     }
-    Builder& setRenderEngineType(RenderEngine::RenderEngineType renderEngineType) {
-        this->renderEngineType = renderEngineType;
+    Builder& setThreaded(RenderEngine::Threaded threaded) {
+        this->threaded = threaded;
+        return *this;
+    }
+    Builder& setGraphicsApi(RenderEngine::GraphicsApi graphicsApi) {
+        this->graphicsApi = graphicsApi;
         return *this;
     }
     RenderEngineCreationArgs build() const {
         return RenderEngineCreationArgs(pixelFormat, imageCacheSize, enableProtectedContext,
                                         precacheToneMapperShaderOnly, supportsBackgroundBlur,
-                                        contextPriority, renderEngineType);
+                                        contextPriority, threaded, graphicsApi);
     }
 
 private:
@@ -317,8 +326,8 @@
     bool precacheToneMapperShaderOnly = false;
     bool supportsBackgroundBlur = false;
     RenderEngine::ContextPriority contextPriority = RenderEngine::ContextPriority::MEDIUM;
-    RenderEngine::RenderEngineType renderEngineType =
-            RenderEngine::RenderEngineType::SKIA_GL_THREADED;
+    RenderEngine::Threaded threaded = RenderEngine::Threaded::YES;
+    RenderEngine::GraphicsApi graphicsApi = RenderEngine::GraphicsApi::GL;
 };
 
 } // namespace renderengine
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index d688b51..fea4129 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -268,7 +268,7 @@
 SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
                                        EGLContext ctxt, EGLSurface placeholder,
                                        EGLContext protectedContext, EGLSurface protectedPlaceholder)
-      : SkiaRenderEngine(args.renderEngineType, static_cast<PixelFormat>(args.pixelFormat),
+      : SkiaRenderEngine(args.threaded, static_cast<PixelFormat>(args.pixelFormat),
                          args.supportsBackgroundBlur),
         mEGLDisplay(display),
         mEGLContext(ctxt),
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 3729be6..330cc66 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -269,9 +269,9 @@
     SkAndroidFrameworkTraceUtil::setEnableTracing(tracingEnabled);
 }
 
-SkiaRenderEngine::SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat,
+SkiaRenderEngine::SkiaRenderEngine(Threaded threaded, PixelFormat pixelFormat,
                                    bool supportsBackgroundBlur)
-      : RenderEngine(type), mDefaultPixelFormat(pixelFormat) {
+      : RenderEngine(threaded), mDefaultPixelFormat(pixelFormat) {
     if (supportsBackgroundBlur) {
         ALOGD("Background Blurs Enabled");
         mBlurFilter = new KawaseBlurFilter();
@@ -389,10 +389,9 @@
 void SkiaRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
                                                   bool isRenderable) {
     // Only run this if RE is running on its own thread. This
-    // way the access to GL operations is guaranteed to be happening on the
+    // way the access to GL/VK operations is guaranteed to be happening on the
     // same thread.
-    if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED &&
-        mRenderEngineType != RenderEngineType::SKIA_VK_THREADED) {
+    if (!isThreaded()) {
         return;
     }
     // We don't attempt to map a buffer if the buffer contains protected content. In GL this is
@@ -1017,7 +1016,7 @@
                                                   .fakeOutputDataspace = fakeDataspace}));
 
             // Turn on dithering when dimming beyond this (arbitrary) threshold...
-            static constexpr float kDimmingThreshold = 0.2f;
+            static constexpr float kDimmingThreshold = 0.9f;
             // ...or we're rendering an HDR layer down to an 8-bit target
             // Most HDR standards require at least 10-bits of color depth for source content, so we
             // can just extract the transfer function rather than dig into precise gralloc layout.
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index ac134af..e88d44c 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -59,7 +59,7 @@
 class SkiaRenderEngine : public RenderEngine {
 public:
     static std::unique_ptr<SkiaRenderEngine> create(const RenderEngineCreationArgs& args);
-    SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat, bool supportsBackgroundBlur);
+    SkiaRenderEngine(Threaded, PixelFormat pixelFormat, bool supportsBackgroundBlur);
     ~SkiaRenderEngine() override;
 
     std::future<void> primeCache(bool shouldPrimeUltraHDR) override final;
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index ba20d1f..bff12ce 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -34,6 +34,8 @@
 
 #include <cstdint>
 #include <memory>
+#include <sstream>
+#include <string>
 #include <vector>
 
 #include <vulkan/vulkan.h>
@@ -67,6 +69,13 @@
     DestroySemaphoreInfo(VkSemaphore semaphore) : mSemaphore(semaphore) {}
 };
 
+namespace {
+void onVkDeviceFault(void* callbackContext, const std::string& description,
+                     const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+                     const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+                     const std::vector<std::byte>& vendorBinaryData);
+} // anonymous namespace
+
 struct VulkanInterface {
     bool initialized = false;
     VkInstance instance;
@@ -79,6 +88,7 @@
     VkPhysicalDeviceFeatures2* physicalDeviceFeatures2 = nullptr;
     VkPhysicalDeviceSamplerYcbcrConversionFeatures* samplerYcbcrConversionFeatures = nullptr;
     VkPhysicalDeviceProtectedMemoryFeatures* protectedMemoryFeatures = nullptr;
+    VkPhysicalDeviceFaultFeaturesEXT* deviceFaultFeatures = nullptr;
     GrVkGetProc grGetProc;
     bool isProtected;
     bool isRealtimePriority;
@@ -100,6 +110,8 @@
         backendContext.fDeviceFeatures2 = physicalDeviceFeatures2;
         backendContext.fGetProc = grGetProc;
         backendContext.fProtectedContext = isProtected ? GrProtected::kYes : GrProtected::kNo;
+        backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived
+        backendContext.fDeviceLostProc = onVkDeviceFault;
         return backendContext;
     };
 
@@ -178,6 +190,68 @@
     }
 };
 
+namespace {
+void onVkDeviceFault(void* callbackContext, const std::string& description,
+                     const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+                     const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+                     const std::vector<std::byte>& vendorBinaryData) {
+    VulkanInterface* interface = static_cast<VulkanInterface*>(callbackContext);
+    const std::string protectedStr = interface->isProtected ? "protected" : "non-protected";
+    // The final crash string should contain as much differentiating info as possible, up to 1024
+    // bytes. As this final message is constructed, the same information is also dumped to the logs
+    // but in a more verbose format. Building the crash string is unsightly, so the clearer logging
+    // statement is always placed first to give context.
+    ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", protectedStr.c_str(), description.c_str());
+    std::stringstream crashMsg;
+    crashMsg << "VK_ERROR_DEVICE_LOST (" << protectedStr;
+
+    if (!addressInfos.empty()) {
+        ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size());
+        crashMsg << ", " << addressInfos.size() << " address info (";
+        for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) {
+            ALOGE(" addressType:       %d", (int)addressInfo.addressType);
+            ALOGE("  reportedAddress:  %" PRIu64, addressInfo.reportedAddress);
+            ALOGE("  addressPrecision: %" PRIu64, addressInfo.addressPrecision);
+            crashMsg << addressInfo.addressType << ":"
+                     << addressInfo.reportedAddress << ":"
+                     << addressInfo.addressPrecision << ", ";
+        }
+        crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+        crashMsg << ")";
+    }
+
+    if (!vendorInfos.empty()) {
+        ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size());
+        crashMsg << ", " << vendorInfos.size() << " vendor info (";
+        for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) {
+            ALOGE(" description:      %s", vendorInfo.description);
+            ALOGE("  vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode);
+            ALOGE("  vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData);
+            // Omit descriptions for individual vendor info structs in the crash string, as the
+            // fault code and fault data fields should be enough for clustering, and the verbosity
+            // isn't worth it. Additionally, vendors may just set the general description field of
+            // the overall fault to the description of the first element in this list, and that
+            // overall description will be placed at the end of the crash string.
+            crashMsg << vendorInfo.vendorFaultCode << ":"
+                     << vendorInfo.vendorFaultData << ", ";
+        }
+        crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+        crashMsg << ")";
+    }
+
+    if (!vendorBinaryData.empty()) {
+        // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports
+        ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics"
+              " Stack team if you observe this message).",
+              vendorBinaryData.size());
+        crashMsg << ", " << vendorBinaryData.size() << " bytes binary";
+    }
+
+    crashMsg << "): " << description;
+    LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
+};
+} // anonymous namespace
+
 static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
     if (device != VK_NULL_HANDLE) {
         return vkGetDeviceProcAddr(device, proc_name);
@@ -429,6 +503,14 @@
         tailPnext = &interface.protectedMemoryFeatures->pNext;
     }
 
+    if (interface.grExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+        interface.deviceFaultFeatures = new VkPhysicalDeviceFaultFeaturesEXT;
+        interface.deviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
+        interface.deviceFaultFeatures->pNext = nullptr;
+        *tailPnext = interface.deviceFaultFeatures;
+        tailPnext = &interface.deviceFaultFeatures->pNext;
+    }
+
     vkGetPhysicalDeviceFeatures2(physicalDevice, interface.physicalDeviceFeatures2);
     // Looks like this would slow things down and we can't depend on it on all platforms
     interface.physicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE;
@@ -545,6 +627,10 @@
         delete interface->physicalDeviceFeatures2;
     }
 
+    if (interface->deviceFaultFeatures) {
+        delete interface->deviceFaultFeatures;
+    }
+
     interface->samplerYcbcrConversionFeatures = nullptr;
     interface->physicalDeviceFeatures2 = nullptr;
     interface->protectedMemoryFeatures = nullptr;
@@ -596,7 +682,7 @@
 }
 
 SkiaVkRenderEngine::SkiaVkRenderEngine(const RenderEngineCreationArgs& args)
-      : SkiaRenderEngine(args.renderEngineType, static_cast<PixelFormat>(args.pixelFormat),
+      : SkiaRenderEngine(args.threaded, static_cast<PixelFormat>(args.pixelFormat),
                          args.supportsBackgroundBlur) {}
 
 SkiaVkRenderEngine::~SkiaVkRenderEngine() {
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 11d4fde..4c18704 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -106,92 +106,46 @@
     virtual ~RenderEngineFactory() = default;
 
     virtual std::string name() = 0;
-    virtual renderengine::RenderEngine::RenderEngineType type() = 0;
-    virtual std::unique_ptr<renderengine::RenderEngine> createRenderEngine() = 0;
-    virtual bool typeSupported() = 0;
+    virtual renderengine::RenderEngine::GraphicsApi graphicsApi() = 0;
+    virtual bool apiSupported() = 0;
+    std::unique_ptr<renderengine::RenderEngine> createRenderEngine() {
+        renderengine::RenderEngineCreationArgs reCreationArgs =
+                renderengine::RenderEngineCreationArgs::Builder()
+                        .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
+                        .setImageCacheSize(1)
+                        .setEnableProtectedContext(false)
+                        .setPrecacheToneMapperShaderOnly(false)
+                        .setSupportsBackgroundBlur(true)
+                        .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
+                        .setThreaded(renderengine::RenderEngine::Threaded::NO)
+                        .setGraphicsApi(graphicsApi())
+                        .build();
+        return renderengine::RenderEngine::create(reCreationArgs);
+    }
 };
 
 class SkiaVkRenderEngineFactory : public RenderEngineFactory {
 public:
     std::string name() override { return "SkiaVkRenderEngineFactory"; }
 
-    renderengine::RenderEngine::RenderEngineType type() {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_VK;
+    renderengine::RenderEngine::GraphicsApi graphicsApi() override {
+        return renderengine::RenderEngine::GraphicsApi::VK;
     }
 
-    std::unique_ptr<renderengine::RenderEngine> createRenderEngine() override {
-        std::unique_ptr<renderengine::RenderEngine> re = createSkiaVkRenderEngine();
-        return re;
-    }
-
-    std::unique_ptr<renderengine::skia::SkiaVkRenderEngine> createSkiaVkRenderEngine() {
-        renderengine::RenderEngineCreationArgs reCreationArgs =
-                renderengine::RenderEngineCreationArgs::Builder()
-                        .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
-                        .setImageCacheSize(1)
-                        .setEnableProtectedContext(false)
-                        .setPrecacheToneMapperShaderOnly(false)
-                        .setSupportsBackgroundBlur(true)
-                        .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
-                        .setRenderEngineType(type())
-                        .build();
-        return renderengine::skia::SkiaVkRenderEngine::create(reCreationArgs);
-    }
-
-    bool typeSupported() override {
+    bool apiSupported() override {
         return skia::SkiaVkRenderEngine::canSupportSkiaVkRenderEngine();
     }
-    void skip() { GTEST_SKIP(); }
 };
 
 class SkiaGLESRenderEngineFactory : public RenderEngineFactory {
 public:
     std::string name() override { return "SkiaGLRenderEngineFactory"; }
 
-    renderengine::RenderEngine::RenderEngineType type() {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_GL;
+    renderengine::RenderEngine::GraphicsApi graphicsApi() {
+        return renderengine::RenderEngine::GraphicsApi::GL;
     }
 
-    std::unique_ptr<renderengine::RenderEngine> createRenderEngine() override {
-        renderengine::RenderEngineCreationArgs reCreationArgs =
-                renderengine::RenderEngineCreationArgs::Builder()
-                        .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
-                        .setImageCacheSize(1)
-                        .setEnableProtectedContext(false)
-                        .setPrecacheToneMapperShaderOnly(false)
-                        .setSupportsBackgroundBlur(true)
-                        .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
-                        .setRenderEngineType(type())
-                        .build();
-        return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs);
-    }
-
-    bool typeSupported() override { return true; }
-};
-
-class SkiaGLESCMRenderEngineFactory : public RenderEngineFactory {
-public:
-    std::string name() override { return "SkiaGLCMRenderEngineFactory"; }
-
-    renderengine::RenderEngine::RenderEngineType type() {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_GL;
-    }
-
-    std::unique_ptr<renderengine::RenderEngine> createRenderEngine() override {
-        renderengine::RenderEngineCreationArgs reCreationArgs =
-                renderengine::RenderEngineCreationArgs::Builder()
-                        .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
-                        .setImageCacheSize(1)
-                        .setEnableProtectedContext(false)
-                        .setPrecacheToneMapperShaderOnly(false)
-                        .setSupportsBackgroundBlur(true)
-                        .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
-                        .setRenderEngineType(type())
-                        .build();
-        return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs);
-    }
-
-    bool typeSupported() override { return true; }
+    bool apiSupported() override { return true; }
 };
 
 class RenderEngineTest : public ::testing::TestWithParam<std::shared_ptr<RenderEngineFactory>> {
@@ -1526,7 +1480,7 @@
                                          std::make_shared<SkiaVkRenderEngineFactory>()));
 
 TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1534,7 +1488,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedBufferAndEmptyBuffer) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1561,7 +1515,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1595,7 +1549,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1616,7 +1570,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1624,7 +1578,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1632,7 +1586,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1640,7 +1594,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1648,7 +1602,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1656,7 +1610,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1664,7 +1618,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1672,7 +1626,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1680,7 +1634,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1688,7 +1642,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1696,7 +1650,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1706,7 +1660,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported()) {
+    if (!renderEngineFactory->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -1717,7 +1671,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported()) {
+    if (!renderEngineFactory->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -1726,7 +1680,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1734,7 +1688,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1742,7 +1696,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1750,7 +1704,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1758,7 +1712,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_overlayCorners_colorSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1766,7 +1720,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1774,7 +1728,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1782,7 +1736,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1790,7 +1744,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1798,7 +1752,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1806,7 +1760,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1814,7 +1768,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1822,7 +1776,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1830,7 +1784,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1838,7 +1792,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1846,7 +1800,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1856,7 +1810,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_opaqueBufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported()) {
+    if (!renderEngineFactory->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -1867,7 +1821,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported()) {
+    if (!renderEngineFactory->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -1876,7 +1830,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1884,7 +1838,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1892,7 +1846,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1900,7 +1854,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1908,7 +1862,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_overlayCorners_opaqueBufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1916,7 +1870,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1924,7 +1878,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1932,7 +1886,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1940,7 +1894,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1948,7 +1902,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1956,7 +1910,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1964,7 +1918,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1972,7 +1926,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1980,7 +1934,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1988,7 +1942,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -1996,7 +1950,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2006,7 +1960,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_bufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported()) {
+    if (!renderEngineFactory->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -2017,7 +1971,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->typeSupported()) {
+    if (!renderEngineFactory->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -2026,7 +1980,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2034,7 +1988,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2042,7 +1996,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2050,7 +2004,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2058,7 +2012,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_overlayCorners_bufferSource) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2066,7 +2020,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferTextureTransform) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2074,7 +2028,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBuffer_premultipliesAlpha) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2082,7 +2036,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBuffer_withoutPremultiplyingAlpha) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2090,7 +2044,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2108,7 +2062,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2131,7 +2085,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2155,7 +2109,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2180,7 +2134,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2206,7 +2160,7 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2235,7 +2189,7 @@
 }
 
 TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2271,7 +2225,7 @@
     if (mRE->canSkipPostRenderCleanup()) {
         // Skia's Vk backend may keep the texture alive beyond drawLayersInternal, so
         // it never gets added to the cleanup list. In those cases, we can skip.
-        EXPECT_TRUE(GetParam()->type() == renderengine::RenderEngine::RenderEngineType::SKIA_VK);
+        EXPECT_TRUE(GetParam()->graphicsApi() == renderengine::RenderEngine::GraphicsApi::VK);
     } else {
         mRE->cleanupPostRender();
         EXPECT_TRUE(mRE->canSkipPostRenderCleanup());
@@ -2279,7 +2233,7 @@
 }
 
 TEST_P(RenderEngineTest, testRoundedCornersCrop) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2332,7 +2286,7 @@
 }
 
 TEST_P(RenderEngineTest, testRoundedCornersParentCrop) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2380,7 +2334,7 @@
 }
 
 TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2417,7 +2371,7 @@
 }
 
 TEST_P(RenderEngineTest, testRoundedCornersXY) {
-    if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -2460,7 +2414,7 @@
 }
 
 TEST_P(RenderEngineTest, testClear) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2492,7 +2446,7 @@
 }
 
 TEST_P(RenderEngineTest, testDisableBlendingBuffer) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2543,7 +2497,7 @@
 }
 
 TEST_P(RenderEngineTest, testBorder) {
-    if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -2588,7 +2542,7 @@
 }
 
 TEST_P(RenderEngineTest, testDimming) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2663,7 +2617,7 @@
 }
 
 TEST_P(RenderEngineTest, testDimming_inGammaSpace) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2741,7 +2695,7 @@
 }
 
 TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2804,7 +2758,7 @@
 }
 
 TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform_deviceHandles) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2868,7 +2822,7 @@
 }
 
 TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2922,7 +2876,7 @@
 }
 
 TEST_P(RenderEngineTest, test_isOpaque) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -2972,7 +2926,7 @@
 }
 
 TEST_P(RenderEngineTest, test_tonemapPQMatches) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -2989,7 +2943,7 @@
 }
 
 TEST_P(RenderEngineTest, test_tonemapHLGMatches) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
 
@@ -3006,7 +2960,7 @@
 }
 
 TEST_P(RenderEngineTest, r8_behaves_as_mask) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -3066,7 +3020,7 @@
 }
 
 TEST_P(RenderEngineTest, r8_respects_color_transform) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -3131,7 +3085,7 @@
 }
 
 TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
@@ -3199,7 +3153,7 @@
 }
 
 TEST_P(RenderEngineTest, primeShaderCache) {
-    if (!GetParam()->typeSupported()) {
+    if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index 1b9adba..d56dbb2 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -35,8 +35,7 @@
 
     void SetUp() override {
         mThreadedRE = renderengine::threaded::RenderEngineThreaded::create(
-                [this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); },
-                renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED);
+                [this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); });
     }
 
     std::unique_ptr<renderengine::threaded::RenderEngineThreaded> mThreadedRE;
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index f58f543..f4cebc0 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -33,13 +33,12 @@
 namespace renderengine {
 namespace threaded {
 
-std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(CreateInstanceFactory factory,
-                                                                   RenderEngineType type) {
-    return std::make_unique<RenderEngineThreaded>(std::move(factory), type);
+std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(CreateInstanceFactory factory) {
+    return std::make_unique<RenderEngineThreaded>(std::move(factory));
 }
 
-RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type)
-      : RenderEngine(type) {
+RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory)
+      : RenderEngine(Threaded::YES) {
     ATRACE_CALL();
 
     std::lock_guard lockThread(mThreadMutex);
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index 3f1e67f..d440c96 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -37,10 +37,9 @@
  */
 class RenderEngineThreaded : public RenderEngine {
 public:
-    static std::unique_ptr<RenderEngineThreaded> create(CreateInstanceFactory factory,
-                                                        RenderEngineType type);
+    static std::unique_ptr<RenderEngineThreaded> create(CreateInstanceFactory factory);
 
-    RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type);
+    RenderEngineThreaded(CreateInstanceFactory factory);
     ~RenderEngineThreaded() override;
     std::future<void> primeCache(bool shouldPrimeUltraHDR) override;
 
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index d992aa5..cc92bc3 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -21,6 +21,18 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+aconfig_declarations {
+    name: "libsensor_flags",
+    package: "com.android.hardware.libsensor.flags",
+    srcs: ["libsensor_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "libsensor_flags_c_lib",
+    host_supported: true,
+    aconfig_declarations: "libsensor_flags",
+}
+
 cc_library {
     name: "libsensor",
 
@@ -52,6 +64,10 @@
         "android.companion.virtual.virtualdevice_aidl-cpp",
     ],
 
+    static_libs: [
+        "libsensor_flags_c_lib",
+    ],
+
     export_include_dirs: ["include"],
 
     export_shared_lib_headers: [
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index b82a79f..9411e20 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -37,6 +37,8 @@
 #include <sensor/Sensor.h>
 #include <sensor/SensorEventQueue.h>
 
+#include <com_android_hardware_libsensor_flags.h>
+
 // ----------------------------------------------------------------------------
 namespace android {
 // ----------------------------------------------------------------------------
@@ -192,6 +194,9 @@
 }
 
 status_t SensorManager::assertStateLocked() {
+#if COM_ANDROID_HARDWARE_LIBSENSOR_FLAGS(SENSORMANAGER_PING_BINDER)
+    if (mSensorServer == nullptr) {
+#else
     bool initSensorManager = false;
     if (mSensorServer == nullptr) {
         initSensorManager = true;
@@ -203,6 +208,7 @@
         }
     }
     if (initSensorManager) {
+#endif
         waitForSensorService(&mSensorServer);
         LOG_ALWAYS_FATAL_IF(mSensorServer == nullptr, "getService(SensorService) NULL");
 
diff --git a/libs/sensor/libsensor_flags.aconfig b/libs/sensor/libsensor_flags.aconfig
new file mode 100644
index 0000000..ef4d737
--- /dev/null
+++ b/libs/sensor/libsensor_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.hardware.libsensor.flags"
+
+flag {
+  name: "sensormanager_ping_binder"
+  namespace: "sensors"
+  description: "Whether to pingBinder on SensorManager init"
+  bug: "322228259"
+  is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/opengl/tools/glgen/gen b/opengl/tools/glgen/gen
index 7fd9c3a..f73fa5d 100755
--- a/opengl/tools/glgen/gen
+++ b/opengl/tools/glgen/gen
@@ -22,8 +22,10 @@
 
 mkdir -p out/javax/microedition/khronos/opengles
 mkdir -p out/com/google/android/gles_jni
+mkdir -p out/android/annotation
 mkdir -p out/android/app
 mkdir -p out/android/graphics
+mkdir -p out/android/hardware
 mkdir -p out/android/view
 mkdir -p out/android/opengl
 mkdir -p out/android/content
@@ -34,18 +36,20 @@
 echo "package android.graphics;" > out/android/graphics/Canvas.java
 echo "public interface Canvas {}" >> out/android/graphics/Canvas.java
 
+echo "package android.annotation; public @interface NonNull {}" > out/android/annotation/NonNull.java
 echo "package android.app; import android.content.pm.IPackageManager; public class AppGlobals { public static IPackageManager getPackageManager() { return null;} }" > out/android/app/AppGlobals.java
 # echo "package android.content; import android.content.pm.PackageManager; public interface Context { public PackageManager getPackageManager(); }" > out/android/content/Context.java
 echo "package android.content.pm; public class ApplicationInfo {public int targetSdkVersion;}" > out/android/content/pm/ApplicationInfo.java
 echo "package android.content.pm; public interface IPackageManager {ApplicationInfo getApplicationInfo(java.lang.String packageName, int flags, java.lang.String userId) throws android.os.RemoteException;}" > out/android/content/pm/IPackageManager.java
-echo "package android.os; public class Build {public static class VERSION_CODES { public static final int CUPCAKE = 3;};	}" > out/android/os/Build.java
+echo "package android.hardware; import android.os.ParcelFileDescriptor; public class SyncFence { public static SyncFence create(ParcelFileDescriptor w) { return null; } public static SyncFence createEmpty() { return null; } }" > out/android/hardware/SyncFence.java
+echo "package android.os; public class Build {public static class VERSION_CODES { public static final int CUPCAKE = 0; public static final int R = 0; }; }" > out/android/os/Build.java
+echo "package android.os; public class ParcelFileDescriptor { public static ParcelFileDescriptor adoptFd(int fd) { return null; } }" > out/android/os/ParcelFileDescriptor.java
 echo "package android.os; public class UserHandle {public static String myUserId() { return \"\"; } }" > out/android/os/UserHandle.java
 echo "package android.os; public class RemoteException extends Exception {}" > out/android/os/RemoteException.java
-echo "package android.util; public class Log {public static void w(String a, String b) {} public static void e(String a, String b) {}}" > out/android/util/Log.java
+echo "package android.util; public class Log {public static void d(String a, String b) {} public static void w(String a, String b) {} public static void e(String a, String b) {}}" > out/android/util/Log.java
 
 echo "package android.opengl; public abstract class EGLObjectHandle { public int getHandle() { return 0; } }" > out/android/opengl/EGLObjectHandle.java
 
-
 echo "package android.graphics;" > out/android/graphics/SurfaceTexture.java
 echo "public interface SurfaceTexture {}" >> out/android/graphics/SurfaceTexture.java
 echo "package android.view;" > out/android/view/SurfaceView.java
diff --git a/opengl/tools/glgen/stubs/egl/EGL14Header.java-if b/opengl/tools/glgen/stubs/egl/EGL14Header.java-if
index 951ecff..695b571 100644
--- a/opengl/tools/glgen/stubs/egl/EGL14Header.java-if
+++ b/opengl/tools/glgen/stubs/egl/EGL14Header.java-if
@@ -20,6 +20,7 @@
 
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.SurfaceTexture;
+import android.os.Build;
 import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
diff --git a/opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp b/opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp
index b2ea041..ea55179 100644
--- a/opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp
+++ b/opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
diff --git a/opengl/tools/glgen/stubs/egl/EGL15cHeader.cpp b/opengl/tools/glgen/stubs/egl/EGL15cHeader.cpp
index 6dffac5..8e452fb 100644
--- a/opengl/tools/glgen/stubs/egl/EGL15cHeader.cpp
+++ b/opengl/tools/glgen/stubs/egl/EGL15cHeader.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
diff --git a/opengl/tools/glgen/stubs/egl/EGLExtHeader.java-if b/opengl/tools/glgen/stubs/egl/EGLExtHeader.java-if
index 523bc57..75e1704 100644
--- a/opengl/tools/glgen/stubs/egl/EGLExtHeader.java-if
+++ b/opengl/tools/glgen/stubs/egl/EGLExtHeader.java-if
@@ -18,6 +18,11 @@
 
 package android.opengl;
 
+import android.annotation.NonNull;
+import android.hardware.SyncFence;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
 /**
  * EGL Extensions
  */
@@ -30,8 +35,44 @@
     public static final int EGL_OPENGL_ES3_BIT_KHR          = 0x0040;
     public static final int EGL_RECORDABLE_ANDROID          = 0x3142;
 
+    // EGL_ANDROID_native_fence_sync
+    public static final int EGL_SYNC_NATIVE_FENCE_ANDROID     = 0x3144;
+    public static final int EGL_SYNC_NATIVE_FENCE_FD_ANDROID  = 0x3145;
+    public static final int EGL_SYNC_NATIVE_FENCE_SIGNALED_ANDROID = 0x3146;
+    public static final int EGL_NO_NATIVE_FENCE_FD_ANDROID    = -1;
+
     native private static void _nativeClassInit();
     static {
         _nativeClassInit();
     }
 
+    /**
+     * Retrieves the SyncFence for an EGLSync created with EGL_SYNC_NATIVE_FENCE_ANDROID
+     *
+     * See <a href="https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_native_fence_sync.txt">
+     *     EGL_ANDROID_native_fence_sync</a> extension for more details
+     * @param display The EGLDisplay connection
+     * @param sync The EGLSync to fetch the SyncFence from
+     * @return A SyncFence representing the native fence.
+     *       * If <sync> is not a valid sync object for <display>,
+     *         an {@link SyncFence#isValid() invalid} SyncFence is returned and an EGL_BAD_PARAMETER
+     *         error is generated.
+     *       * If the EGL_SYNC_NATIVE_FENCE_FD_ANDROID attribute of <sync> is
+     *         EGL_NO_NATIVE_FENCE_FD_ANDROID, an {@link SyncFence#isValid() invalid} SyncFence is
+     *         returned and an EGL_BAD_PARAMETER error is generated.
+     *       * If <display> does not match the display passed to eglCreateSync
+     *         when <sync> was created, the behaviour is undefined.
+     */
+    public static @NonNull SyncFence eglDupNativeFenceFDANDROID(@NonNull EGLDisplay display,
+            @NonNull EGLSync sync) {
+        int fd = eglDupNativeFenceFDANDROIDImpl(display, sync);
+        Log.d("EGL", "eglDupNativeFence returned " + fd);
+        if (fd >= 0) {
+            return SyncFence.create(ParcelFileDescriptor.adoptFd(fd));
+        } else {
+            return SyncFence.createEmpty();
+        }
+    }
+
+    private static native int eglDupNativeFenceFDANDROIDImpl(EGLDisplay display, EGLSync sync);
+
diff --git a/opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp b/opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp
index be8b3e3..f1f0ac5 100644
--- a/opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp
+++ b/opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
@@ -37,25 +38,12 @@
 #include <ui/ANativeObjectBase.h>
 
 static jclass egldisplayClass;
-static jclass eglcontextClass;
 static jclass eglsurfaceClass;
-static jclass eglconfigClass;
+static jclass eglsyncClass;
 
 static jmethodID egldisplayGetHandleID;
-static jmethodID eglcontextGetHandleID;
 static jmethodID eglsurfaceGetHandleID;
-static jmethodID eglconfigGetHandleID;
-
-static jmethodID egldisplayConstructor;
-static jmethodID eglcontextConstructor;
-static jmethodID eglsurfaceConstructor;
-static jmethodID eglconfigConstructor;
-
-static jobject eglNoContextObject;
-static jobject eglNoDisplayObject;
-static jobject eglNoSurfaceObject;
-
-
+static jmethodID eglsyncGetHandleID;
 
 /* Cache method IDs each time the class is loaded. */
 
@@ -64,37 +52,14 @@
 {
     jclass egldisplayClassLocal = _env->FindClass("android/opengl/EGLDisplay");
     egldisplayClass = (jclass) _env->NewGlobalRef(egldisplayClassLocal);
-    jclass eglcontextClassLocal = _env->FindClass("android/opengl/EGLContext");
-    eglcontextClass = (jclass) _env->NewGlobalRef(eglcontextClassLocal);
     jclass eglsurfaceClassLocal = _env->FindClass("android/opengl/EGLSurface");
     eglsurfaceClass = (jclass) _env->NewGlobalRef(eglsurfaceClassLocal);
-    jclass eglconfigClassLocal = _env->FindClass("android/opengl/EGLConfig");
-    eglconfigClass = (jclass) _env->NewGlobalRef(eglconfigClassLocal);
+    jclass eglsyncClassLocal = _env->FindClass("android/opengl/EGLSync");
+    eglsyncClass = (jclass) _env->NewGlobalRef(eglsyncClassLocal);
 
     egldisplayGetHandleID = _env->GetMethodID(egldisplayClass, "getNativeHandle", "()J");
-    eglcontextGetHandleID = _env->GetMethodID(eglcontextClass, "getNativeHandle", "()J");
     eglsurfaceGetHandleID = _env->GetMethodID(eglsurfaceClass, "getNativeHandle", "()J");
-    eglconfigGetHandleID = _env->GetMethodID(eglconfigClass, "getNativeHandle", "()J");
-
-
-    egldisplayConstructor = _env->GetMethodID(egldisplayClass, "<init>", "(J)V");
-    eglcontextConstructor = _env->GetMethodID(eglcontextClass, "<init>", "(J)V");
-    eglsurfaceConstructor = _env->GetMethodID(eglsurfaceClass, "<init>", "(J)V");
-    eglconfigConstructor = _env->GetMethodID(eglconfigClass, "<init>", "(J)V");
-
-
-    jclass eglClass = _env->FindClass("android/opengl/EGL14");
-    jfieldID noContextFieldID = _env->GetStaticFieldID(eglClass, "EGL_NO_CONTEXT", "Landroid/opengl/EGLContext;");
-    jobject localeglNoContextObject = _env->GetStaticObjectField(eglClass, noContextFieldID);
-    eglNoContextObject = _env->NewGlobalRef(localeglNoContextObject);
-
-    jfieldID noDisplayFieldID = _env->GetStaticFieldID(eglClass, "EGL_NO_DISPLAY", "Landroid/opengl/EGLDisplay;");
-    jobject localeglNoDisplayObject = _env->GetStaticObjectField(eglClass, noDisplayFieldID);
-    eglNoDisplayObject = _env->NewGlobalRef(localeglNoDisplayObject);
-
-    jfieldID noSurfaceFieldID = _env->GetStaticFieldID(eglClass, "EGL_NO_SURFACE", "Landroid/opengl/EGLSurface;");
-    jobject localeglNoSurfaceObject = _env->GetStaticObjectField(eglClass, noSurfaceFieldID);
-    eglNoSurfaceObject = _env->NewGlobalRef(localeglNoSurfaceObject);
+    eglsyncGetHandleID = _env->GetMethodID(eglsyncClass, "getNativeHandle", "()J");
 }
 
 static void *
@@ -108,24 +73,12 @@
     return reinterpret_cast<void*>(_env->CallLongMethod(obj, mid));
 }
 
-static jobject
-toEGLHandle(JNIEnv *_env, jclass cls, jmethodID con, void * handle) {
-    if (cls == eglcontextClass &&
-       (EGLContext)handle == EGL_NO_CONTEXT) {
-           return eglNoContextObject;
-    }
+// TODO: this should be generated from the .spec file, but needs to be renamed and made private
+static jint android_eglDupNativeFenceFDANDROID(JNIEnv *env, jobject, jobject dpy, jobject sync) {
+    EGLDisplay dpy_native = (EGLDisplay)fromEGLHandle(env, egldisplayGetHandleID, dpy);
+    EGLSync sync_native = (EGLSync)fromEGLHandle(env, eglsyncGetHandleID, sync);
 
-    if (cls == egldisplayClass &&
-       (EGLDisplay)handle == EGL_NO_DISPLAY) {
-           return eglNoDisplayObject;
-    }
-
-    if (cls == eglsurfaceClass &&
-       (EGLSurface)handle == EGL_NO_SURFACE) {
-           return eglNoSurfaceObject;
-    }
-
-    return _env->NewObject(cls, con, reinterpret_cast<jlong>(handle));
+    return eglDupNativeFenceFDANDROID(dpy_native, sync_native);
 }
 
 // --------------------------------------------------------------------------
diff --git a/opengl/tools/glgen/stubs/egl/eglGetDisplay.java b/opengl/tools/glgen/stubs/egl/eglGetDisplay.java
index 85f743d..78b0819 100755
--- a/opengl/tools/glgen/stubs/egl/eglGetDisplay.java
+++ b/opengl/tools/glgen/stubs/egl/eglGetDisplay.java
@@ -7,7 +7,7 @@
     /**
      * {@hide}
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static native EGLDisplay eglGetDisplay(
         long display_id
     );
diff --git a/opengl/tools/glgen/stubs/gles11/GLES10ExtcHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES10ExtcHeader.cpp
index dd17ca4..1fa9275 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES10ExtcHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES10ExtcHeader.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES10cHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES10cHeader.cpp
index dd17ca4..1fa9275 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES10cHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES10cHeader.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES11ExtcHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES11ExtcHeader.cpp
index dd17ca4..1fa9275 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES11ExtcHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES11ExtcHeader.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES11cHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES11cHeader.cpp
index dd17ca4..1fa9275 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES11cHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES11cHeader.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES20cHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES20cHeader.cpp
index b2bbdf6..4004a7d 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES20cHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES20cHeader.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES2/gl2.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES30cHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES30cHeader.cpp
index b039bc9..c5bdf32 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES30cHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES30cHeader.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES3/gl3.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES31ExtcHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES31ExtcHeader.cpp
index dd00e92..2260a80 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES31ExtcHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES31ExtcHeader.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES3/gl31.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES31cHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES31cHeader.cpp
index 88e00be..130612d 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES31cHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES31cHeader.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <stdint.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES32cHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES32cHeader.cpp
index 3e7ec8b..5446fc2 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES32cHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES32cHeader.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <stdint.h>
diff --git a/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp b/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp
index 9cab1d6..c3534bf 100644
--- a/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp
+++ b/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index a7955cf..d244b1a 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -14,6 +14,7 @@
 
 // Default flags to be used throughout all libraries in inputflinger.
 package {
+    default_team: "trendy_team_input_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_native_license"
@@ -110,6 +111,7 @@
     ],
     static_libs: [
         "libattestation",
+        "libperfetto_client_experimental",
         "libpalmrejection",
         "libui-types",
     ],
diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp
index 72c6f1a..1ada5e5 100644
--- a/services/inputflinger/InputFilter.cpp
+++ b/services/inputflinger/InputFilter.cpp
@@ -118,6 +118,15 @@
     }
 }
 
+void InputFilter::setAccessibilitySlowKeysThreshold(nsecs_t threshold) {
+    std::scoped_lock _l(mLock);
+
+    if (mConfig.slowKeysThresholdNs != threshold) {
+        mConfig.slowKeysThresholdNs = threshold;
+        notifyConfigurationChangedLocked();
+    }
+}
+
 void InputFilter::setAccessibilityStickyKeysEnabled(bool enabled) {
     std::scoped_lock _l(mLock);
 
diff --git a/services/inputflinger/InputFilter.h b/services/inputflinger/InputFilter.h
index 153d29d..4ddc9f4 100644
--- a/services/inputflinger/InputFilter.h
+++ b/services/inputflinger/InputFilter.h
@@ -35,6 +35,7 @@
      */
     virtual void dump(std::string& dump) = 0;
     virtual void setAccessibilityBounceKeysThreshold(nsecs_t threshold) = 0;
+    virtual void setAccessibilitySlowKeysThreshold(nsecs_t threshold) = 0;
     virtual void setAccessibilityStickyKeysEnabled(bool enabled) = 0;
 };
 
@@ -61,6 +62,7 @@
     void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
     void setAccessibilityBounceKeysThreshold(nsecs_t threshold) override;
+    void setAccessibilitySlowKeysThreshold(nsecs_t threshold) override;
     void setAccessibilityStickyKeysEnabled(bool enabled) override;
     void dump(std::string& dump) override;
 
diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp
index a8759b7..6c31442 100644
--- a/services/inputflinger/InputFilterCallbacks.cpp
+++ b/services/inputflinger/InputFilterCallbacks.cpp
@@ -17,6 +17,11 @@
 #define LOG_TAG "InputFilterCallbacks"
 
 #include "InputFilterCallbacks.h"
+#include <aidl/com/android/server/inputflinger/BnInputThread.h>
+#include <android/binder_auto_utils.h>
+#include <utils/StrongPointer.h>
+#include <utils/Thread.h>
+#include <functional>
 
 namespace android {
 
@@ -29,6 +34,47 @@
                          event.scanCode, event.metaState, event.downTime);
 }
 
+namespace {
+
+using namespace aidl::com::android::server::inputflinger;
+
+class InputFilterThreadImpl : public Thread {
+public:
+    explicit InputFilterThreadImpl(std::function<void()> loop)
+          : Thread(/*canCallJava=*/true), mThreadLoop(loop) {}
+
+    ~InputFilterThreadImpl() {}
+
+private:
+    std::function<void()> mThreadLoop;
+
+    bool threadLoop() override {
+        mThreadLoop();
+        return true;
+    }
+};
+
+class InputFilterThread : public BnInputThread {
+public:
+    InputFilterThread(std::shared_ptr<IInputThreadCallback> callback) : mCallback(callback) {
+        mThread = sp<InputFilterThreadImpl>::make([this]() { loopOnce(); });
+        mThread->run("InputFilterThread", ANDROID_PRIORITY_URGENT_DISPLAY);
+    }
+
+    ndk::ScopedAStatus finish() override {
+        mThread->requestExit();
+        return ndk::ScopedAStatus::ok();
+    }
+
+private:
+    sp<Thread> mThread;
+    std::shared_ptr<IInputThreadCallback> mCallback;
+
+    void loopOnce() { LOG_ALWAYS_FATAL_IF(!mCallback->loopOnce().isOk()); }
+};
+
+} // namespace
+
 InputFilterCallbacks::InputFilterCallbacks(InputListenerInterface& listener,
                                            InputFilterPolicyInterface& policy)
       : mNextListener(listener), mPolicy(policy) {}
@@ -49,6 +95,13 @@
     return ndk::ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus InputFilterCallbacks::createInputFilterThread(
+        const std::shared_ptr<IInputThreadCallback>& callback,
+        std::shared_ptr<IInputThread>* aidl_return) {
+    *aidl_return = ndk::SharedRefBase::make<InputFilterThread>(callback);
+    return ndk::ScopedAStatus::ok();
+}
+
 uint32_t InputFilterCallbacks::getModifierState() {
     std::scoped_lock _l(mLock);
     return mStickyModifierState.modifierState;
diff --git a/services/inputflinger/InputFilterCallbacks.h b/services/inputflinger/InputFilterCallbacks.h
index 31c160a..a74955b 100644
--- a/services/inputflinger/InputFilterCallbacks.h
+++ b/services/inputflinger/InputFilterCallbacks.h
@@ -19,6 +19,7 @@
 #include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
 #include <android/binder_auto_utils.h>
 #include <utils/Mutex.h>
+#include <memory>
 #include <mutex>
 #include "InputFilterPolicyInterface.h"
 #include "InputListener.h"
@@ -31,6 +32,9 @@
 
 using IInputFilter = aidl::com::android::server::inputflinger::IInputFilter;
 using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+using aidl::com::android::server::inputflinger::IInputThread;
+using IInputThreadCallback =
+        aidl::com::android::server::inputflinger::IInputThread::IInputThreadCallback;
 
 class InputFilterCallbacks : public IInputFilter::BnInputFilterCallbacks {
 public:
@@ -53,6 +57,9 @@
     ndk::ScopedAStatus sendKeyEvent(const AidlKeyEvent& event) override;
     ndk::ScopedAStatus onModifierStateChanged(int32_t modifierState,
                                               int32_t lockedModifierState) override;
+    ndk::ScopedAStatus createInputFilterThread(
+            const std::shared_ptr<IInputThreadCallback>& callback,
+            std::shared_ptr<IInputThread>* aidl_return) override;
 };
 
 } // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 4863513..823df67 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -260,13 +260,16 @@
 }
 
 // Used by tests only.
-binder::Status InputManager::createInputChannel(const std::string& name, InputChannel* outChannel) {
+binder::Status InputManager::createInputChannel(const std::string& name,
+                                                android::os::InputChannelCore* outChannel) {
     IPCThreadState* ipc = IPCThreadState::self();
-    const int uid = ipc->getCallingUid();
+    const uid_t uid = ipc->getCallingUid();
     if (uid != AID_SHELL && uid != AID_ROOT) {
-        ALOGE("Invalid attempt to register input channel over IPC"
-                "from non shell/root entity (PID: %d)", ipc->getCallingPid());
-        return binder::Status::ok();
+        LOG(ERROR) << __func__ << " can only be called by SHELL or ROOT users, "
+                   << "but was called from UID " << uid;
+        return binder::Status::
+                fromExceptionCode(EX_SECURITY,
+                                  "This uid is not allowed to call createInputChannel");
     }
 
     base::Result<std::unique_ptr<InputChannel>> channel = mDispatcher->createInputChannel(name);
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index df944ef..c479aaf 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -136,7 +136,8 @@
     void dump(std::string& dump) override;
 
     status_t dump(int fd, const Vector<String16>& args) override;
-    binder::Status createInputChannel(const std::string& name, InputChannel* outChannel) override;
+    binder::Status createInputChannel(const std::string& name,
+                                      android::os::InputChannelCore* outChannel) override;
     binder::Status removeInputChannel(const sp<IBinder>& connectionToken) override;
     binder::Status setFocusedWindow(const gui::FocusRequest&) override;
 
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
index 2921d30..994d1c4 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
@@ -17,6 +17,8 @@
 package com.android.server.inputflinger;
 
 import com.android.server.inputflinger.DeviceInfo;
+import com.android.server.inputflinger.IInputThread;
+import com.android.server.inputflinger.IInputThread.IInputThreadCallback;
 import com.android.server.inputflinger.InputFilterConfiguration;
 import com.android.server.inputflinger.KeyEvent;
 
@@ -36,6 +38,9 @@
 
         /** Sends back modifier state */
         void onModifierStateChanged(int modifierState, int lockedModifierState);
+
+        /** Creates an Input filter thread */
+        IInputThread createInputFilterThread(in IInputThreadCallback callback);
     }
 
     /** Returns if InputFilter is enabled */
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
new file mode 100644
index 0000000..2f6b8fc
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputflinger;
+
+/** Interface to handle and run things on an InputThread
+  * Exposes main functionality of InputThread.h to rust which internally used system/core/libutils
+  * infrastructure.
+  *
+  * <p>
+  * NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't
+  * have JNI support and can't call into Java policy that we use currently. libutils provided
+  * Thread.h also recommends against using std::thread and using the provided infrastructure that
+  * already provides way of attaching JniEnv to the created thread. So, we are using this interface
+  * to expose the InputThread infrastructure to rust.
+  * </p>
+  * TODO(b/321769871): Implement the threading infrastructure with JniEnv support in rust
+  */
+interface IInputThread {
+    /** Finish input thread (if not running, this call does nothing) */
+    void finish();
+
+    /** Callbacks from C++ to call into inputflinger rust components */
+    interface IInputThreadCallback {
+        /**
+          * The created thread will keep looping and calling this function.
+          * It's the responsibility of RUST component to appropriately put the thread to sleep and
+          * wake according to the use case.
+          */
+        void loopOnce();
+    }
+}
\ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
index 38b1612..9984a6a 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
@@ -22,6 +22,8 @@
 parcelable InputFilterConfiguration {
     // Threshold value for Bounce keys filter (check bounce_keys_filter.rs)
     long bounceKeysThresholdNs;
-    // If sticky keys filter is enabled
+    // If sticky keys filter is enabled (check sticky_keys_filter.rs)
     boolean stickyKeysEnabled;
+    // Threshold value for Slow keys filter (check slow_keys_filter.rs)
+    long slowKeysThresholdNs;
 }
\ No newline at end of file
diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp
index e200f8b..2d12574 100644
--- a/services/inputflinger/benchmarks/Android.bp
+++ b/services/inputflinger/benchmarks/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_input_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_native_license"
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index c7bacee..6d71acc 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_native_license"
@@ -49,6 +50,7 @@
         "Monitor.cpp",
         "TouchedWindow.cpp",
         "TouchState.cpp",
+        "trace/*.cpp",
     ],
 }
 
@@ -72,6 +74,7 @@
     static_libs: [
         "libattestation",
         "libgui_window_info_static",
+        "libperfetto_client_experimental",
     ],
     target: {
         android: {
diff --git a/services/inputflinger/dispatcher/Connection.cpp b/services/inputflinger/dispatcher/Connection.cpp
index f304712..c7963c0 100644
--- a/services/inputflinger/dispatcher/Connection.cpp
+++ b/services/inputflinger/dispatcher/Connection.cpp
@@ -23,19 +23,12 @@
 Connection::Connection(const std::shared_ptr<InputChannel>& inputChannel, bool monitor,
                        const IdGenerator& idGenerator)
       : status(Status::NORMAL),
-        inputChannel(inputChannel),
         monitor(monitor),
         inputPublisher(inputChannel),
         inputState(idGenerator) {}
 
-const std::string Connection::getWindowName() const {
-    if (inputChannel != nullptr) {
-        return inputChannel->getName();
-    }
-    if (monitor) {
-        return "monitor";
-    }
-    return "?";
-}
+sp<IBinder> Connection::getToken() const {
+    return inputPublisher.getChannel()->getConnectionToken();
+};
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Connection.h b/services/inputflinger/dispatcher/Connection.h
index c17baea..8d7f182 100644
--- a/services/inputflinger/dispatcher/Connection.h
+++ b/services/inputflinger/dispatcher/Connection.h
@@ -42,7 +42,6 @@
     };
 
     Status status;
-    std::shared_ptr<InputChannel> inputChannel; // never null
     bool monitor;
     InputPublisher inputPublisher;
     InputState inputState;
@@ -62,9 +61,11 @@
     Connection(const std::shared_ptr<InputChannel>& inputChannel, bool monitor,
                const IdGenerator& idGenerator);
 
-    inline const std::string getInputChannelName() const { return inputChannel->getName(); }
+    inline const std::string getInputChannelName() const {
+        return inputPublisher.getChannel()->getName();
+    }
 
-    const std::string getWindowName() const;
+    sp<IBinder> getToken() const;
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 2153d8a..264dc03 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -282,9 +282,10 @@
 volatile int32_t DispatchEntry::sNextSeqAtomic;
 
 DispatchEntry::DispatchEntry(std::shared_ptr<const EventEntry> eventEntry,
-                             ftl::Flags<InputTarget::Flags> targetFlags,
+                             ftl::Flags<InputTargetFlags> targetFlags,
                              const ui::Transform& transform, const ui::Transform& rawTransform,
-                             float globalScaleFactor)
+                             float globalScaleFactor, gui::Uid targetUid, int64_t vsyncId,
+                             std::optional<int32_t> windowId)
       : seq(nextSeq()),
         eventEntry(std::move(eventEntry)),
         targetFlags(targetFlags),
@@ -292,7 +293,10 @@
         rawTransform(rawTransform),
         globalScaleFactor(globalScaleFactor),
         deliveryTime(0),
-        resolvedFlags(0) {
+        resolvedFlags(0),
+        targetUid(targetUid),
+        vsyncId(vsyncId),
+        windowId(windowId) {
     switch (this->eventEntry->type) {
         case EventEntry::Type::KEY: {
             const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*this->eventEntry);
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index a915805..1298b5d 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -17,7 +17,7 @@
 #pragma once
 
 #include "InjectionState.h"
-#include "InputTarget.h"
+#include "InputTargetFlags.h"
 #include "trace/EventTrackerInterface.h"
 
 #include <gui/InputApplication.h>
@@ -215,7 +215,7 @@
     const uint32_t seq; // unique sequence number, never 0
 
     std::shared_ptr<const EventEntry> eventEntry; // the event to dispatch
-    const ftl::Flags<InputTarget::Flags> targetFlags;
+    const ftl::Flags<InputTargetFlags> targetFlags;
     ui::Transform transform;
     ui::Transform rawTransform;
     float globalScaleFactor;
@@ -227,17 +227,27 @@
 
     int32_t resolvedFlags;
 
+    // Information about the dispatch window used for tracing. We avoid holding a window handle
+    // here because information in a window handle may be dynamically updated within the lifespan
+    // of this dispatch entry.
+    gui::Uid targetUid;
+    int64_t vsyncId;
+    // The window that this event is targeting. The only case when this windowId is not populated
+    // is when dispatching an event to a global monitor.
+    std::optional<int32_t> windowId;
+
     DispatchEntry(std::shared_ptr<const EventEntry> eventEntry,
-                  ftl::Flags<InputTarget::Flags> targetFlags, const ui::Transform& transform,
-                  const ui::Transform& rawTransform, float globalScaleFactor);
+                  ftl::Flags<InputTargetFlags> targetFlags, const ui::Transform& transform,
+                  const ui::Transform& rawTransform, float globalScaleFactor, gui::Uid targetUid,
+                  int64_t vsyncId, std::optional<int32_t> windowId);
     DispatchEntry(const DispatchEntry&) = delete;
     DispatchEntry& operator=(const DispatchEntry&) = delete;
 
     inline bool hasForegroundTarget() const {
-        return targetFlags.test(InputTarget::Flags::FOREGROUND);
+        return targetFlags.test(InputTargetFlags::FOREGROUND);
     }
 
-    inline bool isSplit() const { return targetFlags.test(InputTarget::Flags::SPLIT); }
+    inline bool isSplit() const { return targetFlags.test(InputTargetFlags::SPLIT); }
 
 private:
     static volatile int32_t sNextSeqAtomic;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index c349a58..a62e23a 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -35,6 +35,7 @@
 #include <input/PrintTools.h>
 #include <input/TraceTools.h>
 #include <openssl/mem.h>
+#include <private/android_filesystem_config.h>
 #include <unistd.h>
 #include <utils/Trace.h>
 
@@ -51,6 +52,8 @@
 #include "Connection.h"
 #include "DebugConfig.h"
 #include "InputDispatcher.h"
+#include "trace/InputTracer.h"
+#include "trace/InputTracingPerfettoBackend.h"
 
 #define INDENT "  "
 #define INDENT2 "    "
@@ -75,6 +78,14 @@
 
 namespace {
 
+// Input tracing is only available on debuggable builds (userdebug and eng) when the feature
+// flag is enabled. When the flag is changed, tracing will only be available after reboot.
+bool isInputTracingEnabled() {
+    static const std::string buildType = base::GetProperty("ro.build.type", "user");
+    static const bool isUserdebugOrEng = buildType == "userdebug" || buildType == "eng";
+    return input_flags::enable_input_event_tracing() && isUserdebugOrEng;
+}
+
 template <class Entry>
 void ensureEventTraced(const Entry& entry) {
     if (!entry.traceTracker) {
@@ -359,14 +370,22 @@
     return i;
 }
 
-std::unique_ptr<DispatchEntry> createDispatchEntry(
-        const InputTarget& inputTarget, std::shared_ptr<const EventEntry> eventEntry,
-        ftl::Flags<InputTarget::Flags> inputTargetFlags) {
+std::unique_ptr<DispatchEntry> createDispatchEntry(const InputTarget& inputTarget,
+                                                   std::shared_ptr<const EventEntry> eventEntry,
+                                                   ftl::Flags<InputTarget::Flags> inputTargetFlags,
+                                                   int64_t vsyncId) {
+    const sp<WindowInfoHandle> win = inputTarget.windowHandle;
+    const std::optional<int32_t> windowId =
+            win ? std::make_optional(win->getInfo()->id) : std::nullopt;
+    // Assume the only targets that are not associated with a window are global monitors, and use
+    // the system UID for global monitors for tracing purposes.
+    const gui::Uid uid = win ? win->getInfo()->ownerUid : gui::Uid(AID_SYSTEM);
     if (inputTarget.useDefaultPointerTransform()) {
         const ui::Transform& transform = inputTarget.getDefaultPointerTransform();
         return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, transform,
                                                inputTarget.displayTransform,
-                                               inputTarget.globalScaleFactor);
+                                               inputTarget.globalScaleFactor, uid, vsyncId,
+                                               windowId);
     }
 
     ALOG_ASSERT(eventEntry->type == EventEntry::Type::MOTION);
@@ -413,7 +432,7 @@
     std::unique_ptr<DispatchEntry> dispatchEntry =
             std::make_unique<DispatchEntry>(std::move(combinedMotionEntry), inputTargetFlags,
                                             firstPointerTransform, inputTarget.displayTransform,
-                                            inputTarget.globalScaleFactor);
+                                            inputTarget.globalScaleFactor, uid, vsyncId, windowId);
     return dispatchEntry;
 }
 
@@ -709,7 +728,7 @@
             // TODO(b/282025641): simplify this code once InputTargets are being identified
             // separately from TouchedWindows.
             std::erase_if(targets, [&](const InputTarget& target) {
-                return target.inputChannel->getConnectionToken() == window.windowHandle->getToken();
+                return target.connection->getToken() == window.windowHandle->getToken();
             });
             return true;
         }
@@ -804,7 +823,9 @@
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy)
-      : InputDispatcher(policy, nullptr) {}
+      : InputDispatcher(policy,
+                        isInputTracingEnabled() ? std::make_unique<trace::impl::PerfettoBackend>()
+                                                : nullptr) {}
 
 InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy,
                                  std::unique_ptr<trace::InputTracingBackendInterface> traceBackend)
@@ -833,7 +854,7 @@
     mKeyRepeatState.lastKeyEntry = nullptr;
 
     if (traceBackend) {
-        // TODO: Create input tracer instance.
+        mTracer = std::make_unique<trace::impl::InputTracer>(std::move(traceBackend));
     }
 
     mLastUserActivityTimes.fill(0);
@@ -849,7 +870,7 @@
 
     while (!mConnectionsByToken.empty()) {
         std::shared_ptr<Connection> connection = mConnectionsByToken.begin()->second;
-        removeInputChannelLocked(connection->inputChannel->getConnectionToken(), /*notify=*/false);
+        removeInputChannelLocked(connection->getToken(), /*notify=*/false);
     }
 }
 
@@ -969,7 +990,7 @@
     }
     connection->responsive = false;
     // Stop waking up for this unresponsive connection
-    mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
+    mAnrTracker.eraseToken(connection->getToken());
     onAnrLocked(connection);
     return LLONG_MIN;
 }
@@ -979,8 +1000,7 @@
     if (connection->monitor) {
         return mMonitorDispatchingTimeout;
     }
-    const sp<WindowInfoHandle> window =
-            getWindowHandleLocked(connection->inputChannel->getConnectionToken());
+    const sp<WindowInfoHandle> window = getWindowHandleLocked(connection->getToken());
     if (window != nullptr) {
         return window->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
     }
@@ -1602,15 +1622,15 @@
 
 void InputDispatcher::dispatchFocusLocked(nsecs_t currentTime,
                                           std::shared_ptr<const FocusEntry> entry) {
-    std::shared_ptr<InputChannel> channel = getInputChannelLocked(entry->connectionToken);
-    if (channel == nullptr) {
-        return; // Window has gone away
+    std::shared_ptr<Connection> connection = getConnectionLocked(entry->connectionToken);
+    if (connection == nullptr) {
+        return; // Connection has gone away
     }
     InputTarget target;
-    target.inputChannel = channel;
+    target.connection = connection;
     entry->dispatchInProgress = true;
     std::string message = std::string("Focus ") + (entry->hasFocus ? "entering " : "leaving ") +
-            channel->getName();
+            connection->getInputChannelName();
     std::string reason = std::string("reason=").append(entry->reason);
     android_log_event_list(LOGTAG_INPUT_FOCUS) << message << reason << LOG_ID_EVENTS;
     dispatchEventLocked(currentTime, entry, {target});
@@ -1670,8 +1690,8 @@
         }
     }
 
-    auto channel = getInputChannelLocked(token);
-    if (channel == nullptr) {
+    auto connection = getConnectionLocked(token);
+    if (connection == nullptr) {
         // Window has gone away, clean up Pointer Capture state.
         mWindowTokenWithPointerCapture = nullptr;
         if (mCurrentPointerCaptureRequest.enable) {
@@ -1680,7 +1700,7 @@
         return;
     }
     InputTarget target;
-    target.inputChannel = channel;
+    target.connection = connection;
     entry->dispatchInProgress = true;
     dispatchEventLocked(currentTime, entry, {target});
 
@@ -1711,12 +1731,12 @@
         if (token == nullptr) {
             continue;
         }
-        std::shared_ptr<InputChannel> channel = getInputChannelLocked(token);
-        if (channel == nullptr) {
-            continue; // Window has gone away
+        std::shared_ptr<Connection> connection = getConnectionLocked(token);
+        if (connection == nullptr) {
+            continue; // Connection has gone away
         }
         InputTarget target;
-        target.inputChannel = channel;
+        target.connection = connection;
         inputTargets.push_back(target);
     }
     return inputTargets;
@@ -1961,6 +1981,7 @@
                 isPointerEvent ? CancelationOptions::Mode::CANCEL_POINTER_EVENTS
                                : CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS);
         CancelationOptions options(mode, "input event injection failed");
+        options.displayId = entry->displayId;
         synthesizeCancelationEventsForMonitorsLocked(options);
         return true;
     }
@@ -1993,12 +2014,12 @@
 
 void InputDispatcher::dispatchDragLocked(nsecs_t currentTime,
                                          std::shared_ptr<const DragEntry> entry) {
-    std::shared_ptr<InputChannel> channel = getInputChannelLocked(entry->connectionToken);
-    if (channel == nullptr) {
-        return; // Window has gone away
+    std::shared_ptr<Connection> connection = getConnectionLocked(entry->connectionToken);
+    if (connection == nullptr) {
+        return; // Connection has gone away
     }
     InputTarget target;
-    target.inputChannel = channel;
+    target.connection = connection;
     entry->dispatchInProgress = true;
     dispatchEventLocked(currentTime, entry, {target});
 }
@@ -2051,17 +2072,8 @@
     pokeUserActivityLocked(*eventEntry);
 
     for (const InputTarget& inputTarget : inputTargets) {
-        std::shared_ptr<Connection> connection =
-                getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
-        if (connection != nullptr) {
-            prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
-        } else {
-            if (DEBUG_DROPPED_EVENTS_VERBOSE) {
-                LOG(INFO) << "Dropping event delivery to target with channel "
-                          << inputTarget.inputChannel->getName()
-                          << " because it is no longer registered with the input dispatcher.";
-            }
-        }
+        std::shared_ptr<Connection> connection = inputTarget.connection;
+        prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
     }
 }
 
@@ -2072,7 +2084,7 @@
     // sending new pointers to the connection when it blocked, but focused events will continue to
     // pile up.
     ALOGW("Canceling events for %s because it is unresponsive",
-          connection->inputChannel->getName().c_str());
+          connection->getInputChannelName().c_str());
     if (connection->status == Connection::Status::NORMAL) {
         CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS,
                                    "application not responding");
@@ -2255,17 +2267,11 @@
         const std::vector<Monitor>& monitors) const {
     std::vector<Monitor> responsiveMonitors;
     std::copy_if(monitors.begin(), monitors.end(), std::back_inserter(responsiveMonitors),
-                 [this](const Monitor& monitor) REQUIRES(mLock) {
-                     std::shared_ptr<Connection> connection =
-                             getConnectionLocked(monitor.inputChannel->getConnectionToken());
-                     if (connection == nullptr) {
-                         ALOGE("Could not find connection for monitor %s",
-                               monitor.inputChannel->getName().c_str());
-                         return false;
-                     }
+                 [](const Monitor& monitor) REQUIRES(mLock) {
+                     std::shared_ptr<Connection> connection = monitor.connection;
                      if (!connection->responsive) {
                          ALOGW("Unresponsive monitor %s will not get the new gesture",
-                               connection->inputChannel->getName().c_str());
+                               connection->getInputChannelName().c_str());
                          return false;
                      }
                      return true;
@@ -2642,7 +2648,7 @@
             for (InputTarget& target : targets) {
                 if (target.dispatchMode == InputTarget::DispatchMode::OUTSIDE) {
                     sp<WindowInfoHandle> targetWindow =
-                            getWindowHandleLocked(target.inputChannel->getConnectionToken());
+                            getWindowHandleLocked(target.connection->getToken());
                     if (targetWindow->getInfo()->ownerUid != foregroundWindowUid) {
                         target.flags |= InputTarget::Flags::ZERO_COORDS;
                     }
@@ -2840,13 +2846,13 @@
         const sp<android::gui::WindowInfoHandle>& windowHandle,
         InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
         std::optional<nsecs_t> firstDownTimeInTarget) const {
-    std::shared_ptr<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken());
-    if (inputChannel == nullptr) {
+    std::shared_ptr<Connection> connection = getConnectionLocked(windowHandle->getToken());
+    if (connection == nullptr) {
         ALOGW("Not creating InputTarget for %s, no input channel", windowHandle->getName().c_str());
         return {};
     }
     InputTarget inputTarget;
-    inputTarget.inputChannel = inputChannel;
+    inputTarget.connection = connection;
     inputTarget.windowHandle = windowHandle;
     inputTarget.dispatchMode = dispatchMode;
     inputTarget.flags = targetFlags;
@@ -2870,8 +2876,7 @@
     std::vector<InputTarget>::iterator it =
             std::find_if(inputTargets.begin(), inputTargets.end(),
                          [&windowHandle](const InputTarget& inputTarget) {
-                             return inputTarget.inputChannel->getConnectionToken() ==
-                                     windowHandle->getToken();
+                             return inputTarget.connection->getToken() == windowHandle->getToken();
                          });
 
     const WindowInfo* windowInfo = windowHandle->getInfo();
@@ -2911,8 +2916,7 @@
     std::vector<InputTarget>::iterator it =
             std::find_if(inputTargets.begin(), inputTargets.end(),
                          [&windowHandle](const InputTarget& inputTarget) {
-                             return inputTarget.inputChannel->getConnectionToken() ==
-                                     windowHandle->getToken();
+                             return inputTarget.connection->getToken() == windowHandle->getToken();
                          });
 
     // This is a hack, because the actual entry could potentially be an ACTION_DOWN event that
@@ -2962,7 +2966,7 @@
 
     for (const Monitor& monitor : selectResponsiveMonitorsLocked(monitorsIt->second)) {
         InputTarget target;
-        target.inputChannel = monitor.inputChannel;
+        target.connection = monitor.connection;
         // target.firstDownTimeInTarget is not set for global monitors. It is only required in split
         // touch and global monitoring works as intended even without setting firstDownTimeInTarget
         if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
@@ -3333,10 +3337,11 @@
 void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr<Connection>& connection,
                                                  std::shared_ptr<const EventEntry> eventEntry,
                                                  const InputTarget& inputTarget) {
+    // TODO(b/210460522): Verify all targets excluding global monitors are associated with a window.
     // This is a new event.
     // Enqueue a new dispatch entry onto the outbound queue for this connection.
     std::unique_ptr<DispatchEntry> dispatchEntry =
-            createDispatchEntry(inputTarget, eventEntry, inputTarget.flags);
+            createDispatchEntry(inputTarget, eventEntry, inputTarget.flags, mWindowInfosVsyncId);
 
     // Use the eventEntry from dispatchEntry since the entry may have changed and can now be a
     // different EventEntry than what was passed in.
@@ -3455,7 +3460,7 @@
                           << cancelEvent->getDescription();
                 std::unique_ptr<DispatchEntry> cancelDispatchEntry =
                         createDispatchEntry(inputTarget, std::move(cancelEvent),
-                                            ftl::Flags<InputTarget::Flags>());
+                                            ftl::Flags<InputTarget::Flags>(), mWindowInfosVsyncId);
 
                 // Send these cancel events to the queue before sending the event from the new
                 // device.
@@ -3476,7 +3481,7 @@
             }
 
             dispatchPointerDownOutsideFocus(resolvedMotion->source, resolvedMotion->action,
-                                            inputTarget.inputChannel->getConnectionToken());
+                                            inputTarget.connection->getToken());
 
             break;
         }
@@ -3559,13 +3564,9 @@
             continue; // Skip windows that receive ACTION_OUTSIDE
         }
 
-        sp<IBinder> token = target.inputChannel->getConnectionToken();
-        std::shared_ptr<Connection> connection = getConnectionLocked(token);
-        if (connection == nullptr) {
-            continue;
-        }
+        sp<IBinder> token = target.connection->getToken();
         newConnectionTokens.insert(std::move(token));
-        newConnections.emplace_back(connection);
+        newConnections.emplace_back(target.connection);
         if (target.windowHandle) {
             interactionUids.emplace(target.windowHandle->getInfo()->ownerUid);
         }
@@ -3585,7 +3586,7 @@
 
     std::string targetList;
     for (const std::shared_ptr<Connection>& connection : newConnections) {
-        targetList += connection->getWindowName() + ", ";
+        targetList += connection->getInputChannelName() + ", ";
     }
     std::string message = "Interaction with: " + targetList;
     if (targetList.empty()) {
@@ -3799,7 +3800,7 @@
         connection->outboundQueue.erase(connection->outboundQueue.begin());
         traceOutboundQueueLength(*connection);
         if (connection->responsive) {
-            mAnrTracker.insert(timeoutTime, connection->inputChannel->getConnectionToken());
+            mAnrTracker.insert(timeoutTime, connection->getToken());
         }
         traceWaitQueueLength(*connection);
     }
@@ -3889,7 +3890,7 @@
 
             auto command = [this, connection]() REQUIRES(mLock) {
                 scoped_unlock unlock(mLock);
-                mPolicy.notifyInputChannelBroken(connection->inputChannel->getConnectionToken());
+                mPolicy.notifyInputChannelBroken(connection->getToken());
             };
             postCommandLocked(std::move(command));
         }
@@ -3947,10 +3948,9 @@
                 if (shouldReportMetricsForConnection(*connection)) {
                     const InputPublisher::Timeline& timeline =
                             std::get<InputPublisher::Timeline>(*result);
-                    mLatencyTracker
-                            .trackGraphicsLatency(timeline.inputEventId,
-                                                  connection->inputChannel->getConnectionToken(),
-                                                  std::move(timeline.graphicsTimeline));
+                    mLatencyTracker.trackGraphicsLatency(timeline.inputEventId,
+                                                         connection->getToken(),
+                                                         std::move(timeline.graphicsTimeline));
                 }
             }
             gotOne = true;
@@ -3971,8 +3971,7 @@
     } else {
         // Monitor channels are never explicitly unregistered.
         // We do it automatically when the remote endpoint is closed so don't warn about them.
-        const bool stillHaveWindowHandle =
-                getWindowHandleLocked(connection->inputChannel->getConnectionToken()) != nullptr;
+        const bool stillHaveWindowHandle = getWindowHandleLocked(connection->getToken()) != nullptr;
         notify = !connection->monitor && stillHaveWindowHandle;
         if (notify) {
             ALOGW("channel '%s' ~ Consumer closed input channel or an error occurred.  events=0x%x",
@@ -3981,7 +3980,7 @@
     }
 
     // Remove the channel.
-    removeInputChannelLocked(connection->inputChannel->getConnectionToken(), notify);
+    removeInputChannelLocked(connection->getToken(), notify);
     return 0; // remove the callback
 }
 
@@ -3996,21 +3995,11 @@
         const CancelationOptions& options) {
     for (const auto& [_, monitors] : mGlobalMonitorsByDisplay) {
         for (const Monitor& monitor : monitors) {
-            synthesizeCancelationEventsForInputChannelLocked(monitor.inputChannel, options);
+            synthesizeCancelationEventsForConnectionLocked(monitor.connection, options);
         }
     }
 }
 
-void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked(
-        const std::shared_ptr<InputChannel>& channel, const CancelationOptions& options) {
-    std::shared_ptr<Connection> connection = getConnectionLocked(channel->getConnectionToken());
-    if (connection == nullptr) {
-        return;
-    }
-
-    synthesizeCancelationEventsForConnectionLocked(connection, options);
-}
-
 void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
         const std::shared_ptr<Connection>& connection, const CancelationOptions& options) {
     if (connection->status != Connection::Status::NORMAL) {
@@ -4039,8 +4028,8 @@
 
     const bool wasEmpty = connection->outboundQueue.empty();
     // The target to use if we don't find a window associated with the channel.
-    const InputTarget fallbackTarget{.inputChannel = connection->inputChannel};
-    const auto& token = connection->inputChannel->getConnectionToken();
+    const InputTarget fallbackTarget{.connection = connection};
+    const auto& token = connection->getToken();
 
     for (size_t i = 0; i < cancelationEvents.size(); i++) {
         std::unique_ptr<EventEntry> cancelationEventEntry = std::move(cancelationEvents[i]);
@@ -4141,8 +4130,7 @@
               connection->getInputChannelName().c_str(), downEvents.size());
     }
 
-    sp<WindowInfoHandle> windowHandle =
-            getWindowHandleLocked(connection->inputChannel->getConnectionToken());
+    sp<WindowInfoHandle> windowHandle = getWindowHandleLocked(connection->getToken());
 
     const bool wasEmpty = connection->outboundQueue.empty();
     for (std::unique_ptr<EventEntry>& downEventEntry : downEvents) {
@@ -4160,8 +4148,8 @@
                                                  targetFlags, pointerIds, motionEntry.downTime,
                                                  targets);
                 } else {
-                    targets.emplace_back(InputTarget{.inputChannel = connection->inputChannel,
-                                                     .flags = targetFlags});
+                    targets.emplace_back(
+                            InputTarget{.connection = connection, .flags = targetFlags});
                     const auto it = mDisplayInfos.find(motionEntry.displayId);
                     if (it != mDisplayInfos.end()) {
                         targets.back().displayTransform = it->second.transform;
@@ -5118,15 +5106,6 @@
     return true;
 }
 
-std::shared_ptr<InputChannel> InputDispatcher::getInputChannelLocked(
-        const sp<IBinder>& token) const {
-    auto connectionIt = mConnectionsByToken.find(token);
-    if (connectionIt == mConnectionsByToken.end()) {
-        return nullptr;
-    }
-    return connectionIt->second->inputChannel;
-}
-
 void InputDispatcher::updateWindowHandlesForDisplayLocked(
         const std::vector<sp<WindowInfoHandle>>& windowInfoHandles, int32_t displayId) {
     if (windowInfoHandles.empty()) {
@@ -5146,7 +5125,7 @@
     std::vector<sp<WindowInfoHandle>> newHandles;
     for (const sp<WindowInfoHandle>& handle : windowInfoHandles) {
         const WindowInfo* info = handle->getInfo();
-        if (getInputChannelLocked(handle->getToken()) == nullptr) {
+        if (getConnectionLocked(handle->getToken()) == nullptr) {
             const bool noInputChannel =
                     info->inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL);
             const bool canReceiveInput =
@@ -5245,12 +5224,12 @@
             if (getWindowHandleLocked(touchedWindow.windowHandle) == nullptr) {
                 LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName()
                           << " in display %" << displayId;
-                std::shared_ptr<InputChannel> touchedInputChannel =
-                        getInputChannelLocked(touchedWindow.windowHandle->getToken());
-                if (touchedInputChannel != nullptr) {
+                std::shared_ptr<Connection> touchedConnection =
+                        getConnectionLocked(touchedWindow.windowHandle->getToken());
+                if (touchedConnection != nullptr) {
                     CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
                                                "touched window was removed");
-                    synthesizeCancelationEventsForInputChannelLocked(touchedInputChannel, options);
+                    synthesizeCancelationEventsForConnectionLocked(touchedConnection, options);
                     // Since we are about to drop the touch, cancel the events for the wallpaper as
                     // well.
                     if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) &&
@@ -5355,14 +5334,13 @@
             sp<IBinder> oldFocusedWindowToken =
                     mFocusResolver.getFocusedWindowToken(mFocusedDisplayId);
             if (oldFocusedWindowToken != nullptr) {
-                std::shared_ptr<InputChannel> inputChannel =
-                        getInputChannelLocked(oldFocusedWindowToken);
-                if (inputChannel != nullptr) {
+                std::shared_ptr<Connection> connection = getConnectionLocked(oldFocusedWindowToken);
+                if (connection != nullptr) {
                     CancelationOptions
                             options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
                                     "The display which contains this window no longer has focus.");
                     options.displayId = ADISPLAY_ID_NONE;
-                    synthesizeCancelationEventsForInputChannelLocked(inputChannel, options);
+                    synthesizeCancelationEventsForConnectionLocked(connection, options);
                 }
             }
             mFocusedDisplayId = displayId;
@@ -5825,11 +5803,10 @@
     if (!mConnectionsByToken.empty()) {
         dump += INDENT "Connections:\n";
         for (const auto& [token, connection] : mConnectionsByToken) {
-            dump += StringPrintf(INDENT2 "%i: channelName='%s', windowName='%s', "
+            dump += StringPrintf(INDENT2 "%i: channelName='%s', "
                                          "status=%s, monitor=%s, responsive=%s\n",
-                                 connection->inputChannel->getFd().get(),
+                                 connection->inputPublisher.getChannel()->getFd(),
                                  connection->getInputChannelName().c_str(),
-                                 connection->getWindowName().c_str(),
                                  ftl::enum_string(connection->status).c_str(),
                                  toString(connection->monitor), toString(connection->responsive));
 
@@ -5883,8 +5860,8 @@
     const size_t numMonitors = monitors.size();
     for (size_t i = 0; i < numMonitors; i++) {
         const Monitor& monitor = monitors[i];
-        const std::shared_ptr<InputChannel>& channel = monitor.inputChannel;
-        dump += StringPrintf(INDENT2 "%zu: '%s', ", i, channel->getName().c_str());
+        const std::shared_ptr<Connection>& connection = monitor.connection;
+        dump += StringPrintf(INDENT2 "%zu: '%s', ", i, connection->getInputChannelName().c_str());
         dump += "\n";
     }
 }
@@ -5914,20 +5891,20 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
         const sp<IBinder>& token = serverChannel->getConnectionToken();
-        auto&& fd = serverChannel->getFd();
+        const int fd = serverChannel->getFd();
         std::shared_ptr<Connection> connection =
                 std::make_shared<Connection>(std::move(serverChannel), /*monitor=*/false,
                                              mIdGenerator);
 
-        if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) {
+        auto [_, inserted] = mConnectionsByToken.try_emplace(token, connection);
+        if (!inserted) {
             ALOGE("Created a new connection, but the token %p is already known", token.get());
         }
-        mConnectionsByToken.emplace(token, connection);
 
         std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,
                                                             this, std::placeholders::_1, token);
 
-        mLooper->addFd(fd.get(), 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
+        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
                        nullptr);
     } // release lock
 
@@ -5957,18 +5934,19 @@
         std::shared_ptr<Connection> connection =
                 std::make_shared<Connection>(serverChannel, /*monitor=*/true, mIdGenerator);
         const sp<IBinder>& token = serverChannel->getConnectionToken();
-        auto&& fd = serverChannel->getFd();
+        const int fd = serverChannel->getFd();
 
-        if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) {
+        auto [_, inserted] = mConnectionsByToken.emplace(token, connection);
+        if (!inserted) {
             ALOGE("Created a new connection, but the token %p is already known", token.get());
         }
-        mConnectionsByToken.emplace(token, connection);
+
         std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,
                                                             this, std::placeholders::_1, token);
 
-        mGlobalMonitorsByDisplay[displayId].emplace_back(serverChannel, pid);
+        mGlobalMonitorsByDisplay[displayId].emplace_back(connection, pid);
 
-        mLooper->addFd(fd.get(), 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
+        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
                        nullptr);
     }
 
@@ -6007,7 +5985,7 @@
         removeMonitorChannelLocked(connectionToken);
     }
 
-    mLooper->removeFd(connection->inputChannel->getFd().get());
+    mLooper->removeFd(connection->inputPublisher.getChannel()->getFd());
 
     nsecs_t currentTime = now();
     abortBrokenDispatchCycleLocked(currentTime, connection, notify);
@@ -6020,7 +5998,7 @@
     for (auto it = mGlobalMonitorsByDisplay.begin(); it != mGlobalMonitorsByDisplay.end();) {
         auto& [displayId, monitors] = *it;
         std::erase_if(monitors, [connectionToken](const Monitor& monitor) {
-            return monitor.inputChannel->getConnectionToken() == connectionToken;
+            return monitor.connection->getToken() == connectionToken;
         });
 
         if (monitors.empty()) {
@@ -6037,8 +6015,8 @@
 }
 
 status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) {
-    const std::shared_ptr<InputChannel> requestingChannel = getInputChannelLocked(token);
-    if (!requestingChannel) {
+    const std::shared_ptr<Connection> requestingConnection = getConnectionLocked(token);
+    if (!requestingConnection) {
         LOG(WARNING)
                 << "Attempted to pilfer pointers from an un-registered channel or invalid token";
         return BAD_VALUE;
@@ -6071,16 +6049,16 @@
 
         std::string canceledWindows;
         for (const TouchedWindow& w : state.windows) {
-            const std::shared_ptr<InputChannel> channel =
-                    getInputChannelLocked(w.windowHandle->getToken());
-            if (channel != nullptr && channel->getConnectionToken() != token) {
-                synthesizeCancelationEventsForInputChannelLocked(channel, options);
+            const std::shared_ptr<Connection> connection =
+                    getConnectionLocked(w.windowHandle->getToken());
+            if (connection != nullptr && connection->getToken() != token) {
+                synthesizeCancelationEventsForConnectionLocked(connection, options);
                 canceledWindows += canceledWindows.empty() ? "[" : ", ";
-                canceledWindows += channel->getName();
+                canceledWindows += connection->getInputChannelName();
             }
         }
         canceledWindows += canceledWindows.empty() ? "[]" : "]";
-        LOG(INFO) << "Channel " << requestingChannel->getName()
+        LOG(INFO) << "Channel " << requestingConnection->getInputChannelName()
                   << " is stealing input gesture for device " << deviceId << " from "
                   << canceledWindows;
 
@@ -6146,7 +6124,7 @@
 std::optional<gui::Pid> InputDispatcher::findMonitorPidByTokenLocked(const sp<IBinder>& token) {
     for (const auto& [_, monitors] : mGlobalMonitorsByDisplay) {
         for (const Monitor& monitor : monitors) {
-            if (monitor.inputChannel->getConnectionToken() == token) {
+            if (monitor.connection->getToken() == token) {
                 return monitor.pid;
             }
         }
@@ -6178,8 +6156,8 @@
 }
 
 void InputDispatcher::removeConnectionLocked(const std::shared_ptr<Connection>& connection) {
-    mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
-    mConnectionsByToken.erase(connection->inputChannel->getConnectionToken());
+    mAnrTracker.eraseToken(connection->getToken());
+    mConnectionsByToken.erase(connection->getToken());
 }
 
 void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime,
@@ -6201,12 +6179,11 @@
 
         const nsecs_t eventDuration = finishTime - dispatchEntry.deliveryTime;
         if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
-            ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
+            ALOGI("%s spent %" PRId64 "ms processing %s", connection->getInputChannelName().c_str(),
                   ns2ms(eventDuration), dispatchEntry.eventEntry->getDescription().c_str());
         }
         if (shouldReportFinishedEvent(dispatchEntry, *connection)) {
-            mLatencyTracker.trackFinishedEvent(dispatchEntry.eventEntry->id,
-                                               connection->inputChannel->getConnectionToken(),
+            mLatencyTracker.trackFinishedEvent(dispatchEntry.eventEntry->id, connection->getToken(),
                                                dispatchEntry.deliveryTime, consumeTime, finishTime);
         }
 
@@ -6227,7 +6204,7 @@
         std::unique_ptr<DispatchEntry> dispatchEntry = std::move(*entryIt);
         connection->waitQueue.erase(entryIt);
 
-        const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken();
+        const sp<IBinder>& connectionToken = connection->getToken();
         mAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken);
         if (!connection->responsive) {
             connection->responsive = isConnectionResponsive(*connection);
@@ -6238,8 +6215,7 @@
         }
         traceWaitQueueLength(*connection);
         if (fallbackKeyEntry && connection->status == Connection::Status::NORMAL) {
-            const InputTarget target{.inputChannel = connection->inputChannel,
-                                     .flags = dispatchEntry->targetFlags};
+            const InputTarget target{.connection = connection, .flags = dispatchEntry->targetFlags};
             enqueueDispatchEntryLocked(connection, std::move(fallbackKeyEntry), target);
         }
         releaseDispatchEntry(std::move(dispatchEntry));
@@ -6274,7 +6250,7 @@
     // is already healthy again. Don't raise ANR in this situation
     if (connection->waitQueue.empty()) {
         ALOGI("Not raising ANR because the connection %s has recovered",
-              connection->inputChannel->getName().c_str());
+              connection->getInputChannelName().c_str());
         return;
     }
     /**
@@ -6289,10 +6265,10 @@
     const nsecs_t currentWait = now() - oldestEntry.deliveryTime;
     std::string reason =
             android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",
-                                        connection->inputChannel->getName().c_str(),
+                                        connection->getInputChannelName().c_str(),
                                         ns2ms(currentWait),
                                         oldestEntry.eventEntry->getDescription().c_str());
-    sp<IBinder> connectionToken = connection->inputChannel->getConnectionToken();
+    sp<IBinder> connectionToken = connection->getToken();
     updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason);
 
     processConnectionUnresponsiveLocked(*connection, std::move(reason));
@@ -6391,15 +6367,15 @@
  */
 void InputDispatcher::processConnectionUnresponsiveLocked(const Connection& connection,
                                                           std::string reason) {
-    const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken();
+    const sp<IBinder>& connectionToken = connection.getToken();
     std::optional<gui::Pid> pid;
     if (connection.monitor) {
-        ALOGW("Monitor %s is unresponsive: %s", connection.inputChannel->getName().c_str(),
+        ALOGW("Monitor %s is unresponsive: %s", connection.getInputChannelName().c_str(),
               reason.c_str());
         pid = findMonitorPidByTokenLocked(connectionToken);
     } else {
         // The connection is a window
-        ALOGW("Window %s is unresponsive: %s", connection.inputChannel->getName().c_str(),
+        ALOGW("Window %s is unresponsive: %s", connection.getInputChannelName().c_str(),
               reason.c_str());
         const sp<WindowInfoHandle> handle = getWindowHandleLocked(connectionToken);
         if (handle != nullptr) {
@@ -6413,7 +6389,7 @@
  * Tell the policy that a connection has become responsive so that it can stop ANR.
  */
 void InputDispatcher::processConnectionResponsiveLocked(const Connection& connection) {
-    const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken();
+    const sp<IBinder>& connectionToken = connection.getToken();
     std::optional<gui::Pid> pid;
     if (connection.monitor) {
         pid = findMonitorPidByTokenLocked(connectionToken);
@@ -6464,8 +6440,8 @@
             mLock.unlock();
 
             if (const auto unhandledKeyFallback =
-                        mPolicy.dispatchUnhandledKey(connection->inputChannel->getConnectionToken(),
-                                                     event, keyEntry.policyFlags);
+                        mPolicy.dispatchUnhandledKey(connection->getToken(), event,
+                                                     keyEntry.policyFlags);
                 unhandledKeyFallback) {
                 event = *unhandledKeyFallback;
             }
@@ -6509,8 +6485,8 @@
         mLock.unlock();
 
         bool fallback = false;
-        if (auto fb = mPolicy.dispatchUnhandledKey(connection->inputChannel->getConnectionToken(),
-                                                   event, keyEntry.policyFlags);
+        if (auto fb = mPolicy.dispatchUnhandledKey(connection->getToken(), event,
+                                                   keyEntry.policyFlags);
             fb) {
             fallback = true;
             event = *fb;
@@ -6618,7 +6594,8 @@
 void InputDispatcher::traceOutboundQueueLength(const Connection& connection) {
     if (ATRACE_ENABLED()) {
         char counterName[40];
-        snprintf(counterName, sizeof(counterName), "oq:%s", connection.getWindowName().c_str());
+        snprintf(counterName, sizeof(counterName), "oq:%s",
+                 connection.getInputChannelName().c_str());
         ATRACE_INT(counterName, connection.outboundQueue.size());
     }
 }
@@ -6626,7 +6603,8 @@
 void InputDispatcher::traceWaitQueueLength(const Connection& connection) {
     if (ATRACE_ENABLED()) {
         char counterName[40];
-        snprintf(counterName, sizeof(counterName), "wq:%s", connection.getWindowName().c_str());
+        snprintf(counterName, sizeof(counterName), "wq:%s",
+                 connection.getInputChannelName().c_str());
         ATRACE_INT(counterName, connection.waitQueue.size());
     }
 }
@@ -6697,11 +6675,11 @@
 
 void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& changes) {
     if (changes.oldFocus) {
-        std::shared_ptr<InputChannel> focusedInputChannel = getInputChannelLocked(changes.oldFocus);
-        if (focusedInputChannel) {
+        std::shared_ptr<Connection> focusedConnection = getConnectionLocked(changes.oldFocus);
+        if (focusedConnection) {
             CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
                                        "focus left window");
-            synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options);
+            synthesizeCancelationEventsForConnectionLocked(focusedConnection, options);
             enqueueFocusEventLocked(changes.oldFocus, /*hasFocus=*/false, changes.reason);
         }
     }
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index e635852..155d485 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -369,8 +369,6 @@
             REQUIRES(mLock);
     sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
-    std::shared_ptr<InputChannel> getInputChannelLocked(const sp<IBinder>& windowToken) const
-            REQUIRES(mLock);
     sp<android::gui::WindowInfoHandle> getFocusedWindowHandleLocked(int displayId) const
             REQUIRES(mLock);
     bool canWindowReceiveMotionLocked(const sp<android::gui::WindowInfoHandle>& window,
@@ -617,9 +615,6 @@
             REQUIRES(mLock);
     void synthesizeCancelationEventsForMonitorsLocked(const CancelationOptions& options)
             REQUIRES(mLock);
-    void synthesizeCancelationEventsForInputChannelLocked(
-            const std::shared_ptr<InputChannel>& channel, const CancelationOptions& options)
-            REQUIRES(mLock);
     void synthesizeCancelationEventsForConnectionLocked(
             const std::shared_ptr<Connection>& connection, const CancelationOptions& options)
             REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index c02c5d6..28e3c6d 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -83,9 +83,9 @@
 }
 
 std::ostream& operator<<(std::ostream& out, const InputTarget& target) {
-    out << "{inputChannel=";
-    if (target.inputChannel != nullptr) {
-        out << target.inputChannel->getName();
+    out << "{connection=";
+    if (target.connection != nullptr) {
+        out << target.connection->getInputChannelName();
     } else {
         out << "<null>";
     }
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index aef866b..5728bdf 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -19,10 +19,11 @@
 #include <ftl/flags.h>
 #include <gui/WindowInfo.h>
 #include <gui/constants.h>
-#include <input/InputTransport.h>
 #include <ui/Transform.h>
 #include <utils/BitSet.h>
 #include <bitset>
+#include "Connection.h"
+#include "InputTargetFlags.h"
 
 namespace android::inputdispatcher {
 
@@ -33,29 +34,7 @@
  * window area.
  */
 struct InputTarget {
-    enum class Flags : uint32_t {
-        /* This flag indicates that the event is being delivered to a foreground application. */
-        FOREGROUND = 1 << 0,
-
-        /* This flag indicates that the MotionEvent falls within the area of the target
-         * obscured by another visible window above it.  The motion event should be
-         * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
-        WINDOW_IS_OBSCURED = 1 << 1,
-
-        /* This flag indicates that a motion event is being split across multiple windows. */
-        SPLIT = 1 << 2,
-
-        /* This flag indicates that the pointer coordinates dispatched to the application
-         * will be zeroed out to avoid revealing information to an application. This is
-         * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing
-         * the same UID from watching all touches. */
-        ZERO_COORDS = 1 << 3,
-
-        /* This flag indicates that the target of a MotionEvent is partly or wholly
-         * obscured by another visible window above it.  The motion event should be
-         * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED. */
-        WINDOW_IS_PARTIALLY_OBSCURED = 1 << 14,
-    };
+    using Flags = InputTargetFlags;
 
     enum class DispatchMode {
         /* This flag indicates that the event should be sent as is.
@@ -85,8 +64,8 @@
         ftl_last = SLIPPERY_ENTER,
     };
 
-    // The input channel to be targeted.
-    std::shared_ptr<InputChannel> inputChannel;
+    // The input connection to be targeted.
+    std::shared_ptr<Connection> connection;
 
     // Flags for the input target.
     ftl::Flags<Flags> flags;
diff --git a/services/inputflinger/dispatcher/InputTargetFlags.h b/services/inputflinger/dispatcher/InputTargetFlags.h
new file mode 100644
index 0000000..7497543
--- /dev/null
+++ b/services/inputflinger/dispatcher/InputTargetFlags.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 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 <ftl/flags.h>
+
+namespace android::inputdispatcher {
+
+enum class InputTargetFlags : uint32_t {
+    /* This flag indicates that the event is being delivered to a foreground application. */
+    FOREGROUND = 1 << 0,
+
+    /* This flag indicates that the MotionEvent falls within the area of the target
+     * obscured by another visible window above it.  The motion event should be
+     * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
+    WINDOW_IS_OBSCURED = 1 << 1,
+
+    /* This flag indicates that a motion event is being split across multiple windows. */
+    SPLIT = 1 << 2,
+
+    /* This flag indicates that the pointer coordinates dispatched to the application
+     * will be zeroed out to avoid revealing information to an application. This is
+     * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing
+     * the same UID from watching all touches. */
+    ZERO_COORDS = 1 << 3,
+
+    /* This flag indicates that the target of a MotionEvent is partly or wholly
+     * obscured by another visible window above it.  The motion event should be
+     * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED. */
+    WINDOW_IS_PARTIALLY_OBSCURED = 1 << 14,
+};
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Monitor.cpp b/services/inputflinger/dispatcher/Monitor.cpp
index 204791e..4e77d90 100644
--- a/services/inputflinger/dispatcher/Monitor.cpp
+++ b/services/inputflinger/dispatcher/Monitor.cpp
@@ -19,7 +19,7 @@
 namespace android::inputdispatcher {
 
 // --- Monitor ---
-Monitor::Monitor(const std::shared_ptr<InputChannel>& inputChannel, gui::Pid pid)
-      : inputChannel(inputChannel), pid(pid) {}
+Monitor::Monitor(const std::shared_ptr<Connection>& connection, gui::Pid pid)
+      : connection(connection), pid(pid) {}
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Monitor.h b/services/inputflinger/dispatcher/Monitor.h
index 1b1eb3a..d15a222 100644
--- a/services/inputflinger/dispatcher/Monitor.h
+++ b/services/inputflinger/dispatcher/Monitor.h
@@ -17,16 +17,16 @@
 #pragma once
 
 #include <gui/PidUid.h>
-#include <input/InputTransport.h>
+#include "Connection.h"
 
 namespace android::inputdispatcher {
 
 struct Monitor {
-    std::shared_ptr<InputChannel> inputChannel; // never null
+    std::shared_ptr<Connection> connection; // never null
 
     gui::Pid pid;
 
-    explicit Monitor(const std::shared_ptr<InputChannel>& inputChannel, gui::Pid pid);
+    explicit Monitor(const std::shared_ptr<Connection>& connection, gui::Pid pid);
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
new file mode 100644
index 0000000..a61fa85
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2024 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 "AndroidInputEventProtoConverter.h"
+
+#include <android-base/logging.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+
+namespace android::inputdispatcher::trace {
+
+void AndroidInputEventProtoConverter::toProtoMotionEvent(const TracedMotionEvent& event,
+                                                         proto::AndroidMotionEvent& outProto) {
+    outProto.set_event_id(event.id);
+    outProto.set_event_time_nanos(event.eventTime);
+    outProto.set_down_time_nanos(event.downTime);
+    outProto.set_source(event.source);
+    outProto.set_action(event.action);
+    outProto.set_device_id(event.deviceId);
+    outProto.set_display_id(event.displayId);
+    outProto.set_classification(static_cast<int32_t>(event.classification));
+    outProto.set_cursor_position_x(event.xCursorPosition);
+    outProto.set_cursor_position_y(event.yCursorPosition);
+    outProto.set_flags(event.flags);
+    outProto.set_policy_flags(event.policyFlags);
+
+    for (uint32_t i = 0; i < event.pointerProperties.size(); i++) {
+        auto* pointer = outProto.add_pointer();
+
+        const auto& props = event.pointerProperties[i];
+        pointer->set_pointer_id(props.id);
+        pointer->set_tool_type(static_cast<int32_t>(props.toolType));
+
+        const auto& coords = event.pointerCoords[i];
+        auto bits = BitSet64(coords.bits);
+        for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
+            const auto axis = bits.clearFirstMarkedBit();
+            auto axisEntry = pointer->add_axis_value();
+            axisEntry->set_axis(axis);
+            axisEntry->set_value(coords.values[axisIndex]);
+        }
+    }
+}
+
+void AndroidInputEventProtoConverter::toProtoKeyEvent(const TracedKeyEvent& event,
+                                                      proto::AndroidKeyEvent& outProto) {
+    outProto.set_event_id(event.id);
+    outProto.set_event_time_nanos(event.eventTime);
+    outProto.set_down_time_nanos(event.downTime);
+    outProto.set_source(event.source);
+    outProto.set_action(event.action);
+    outProto.set_device_id(event.deviceId);
+    outProto.set_display_id(event.displayId);
+    outProto.set_key_code(event.keyCode);
+    outProto.set_scan_code(event.scanCode);
+    outProto.set_meta_state(event.metaState);
+    outProto.set_repeat_count(event.repeatCount);
+    outProto.set_flags(event.flags);
+    outProto.set_policy_flags(event.policyFlags);
+}
+
+void AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(
+        const InputTracingBackendInterface::WindowDispatchArgs& args,
+        proto::AndroidWindowInputDispatchEvent& outProto) {
+    std::visit([&](auto entry) { outProto.set_event_id(entry.id); }, args.eventEntry);
+    outProto.set_vsync_id(args.vsyncId);
+    outProto.set_window_id(args.windowId);
+    outProto.set_resolved_flags(args.resolvedFlags);
+
+    if (auto* motion = std::get_if<TracedMotionEvent>(&args.eventEntry); motion != nullptr) {
+        for (size_t i = 0; i < motion->pointerProperties.size(); i++) {
+            auto* pointerProto = outProto.add_dispatched_pointer();
+            pointerProto->set_pointer_id(motion->pointerProperties[i].id);
+            const auto rawXY =
+                    MotionEvent::calculateTransformedXY(motion->source, args.rawTransform,
+                                                        motion->pointerCoords[i].getXYValue());
+            pointerProto->set_x_in_display(rawXY.x);
+            pointerProto->set_y_in_display(rawXY.y);
+
+            const auto& coords = motion->pointerCoords[i];
+            const auto coordsInWindow =
+                    MotionEvent::calculateTransformedCoords(motion->source, args.transform, coords);
+            auto bits = BitSet64(coords.bits);
+            for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
+                const uint32_t axis = bits.clearFirstMarkedBit();
+                const float axisValueInWindow = coordsInWindow.values[axisIndex];
+                if (coords.values[axisIndex] != axisValueInWindow) {
+                    auto* axisEntry = pointerProto->add_axis_value_in_window();
+                    axisEntry->set_axis(axis);
+                    axisEntry->set_value(axisValueInWindow);
+                }
+            }
+        }
+    }
+}
+
+} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
new file mode 100644
index 0000000..8a46f15
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2024 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 <perfetto/trace/android/android_input_event.pbzero.h>
+
+#include "InputTracingBackendInterface.h"
+
+namespace proto = perfetto::protos::pbzero;
+
+namespace android::inputdispatcher::trace {
+
+/**
+ * Write traced events into Perfetto protos.
+ */
+class AndroidInputEventProtoConverter {
+public:
+    static void toProtoMotionEvent(const TracedMotionEvent& event,
+                                   proto::AndroidMotionEvent& outProto);
+    static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto);
+    static void toProtoWindowDispatchEvent(const InputTracingBackendInterface::WindowDispatchArgs&,
+                                           proto::AndroidWindowInputDispatchEvent& outProto);
+};
+
+} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
new file mode 100644
index 0000000..b065729
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2024 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 "InputTracer"
+
+#include "InputTracer.h"
+
+#include <android-base/logging.h>
+#include <utils/AndroidThreads.h>
+
+namespace android::inputdispatcher::trace::impl {
+
+namespace {
+
+TracedEvent createTracedEvent(const MotionEntry& e) {
+    return TracedMotionEvent{e.id,
+                             e.eventTime,
+                             e.policyFlags,
+                             e.deviceId,
+                             e.source,
+                             e.displayId,
+                             e.action,
+                             e.actionButton,
+                             e.flags,
+                             e.metaState,
+                             e.buttonState,
+                             e.classification,
+                             e.edgeFlags,
+                             e.xPrecision,
+                             e.yPrecision,
+                             e.xCursorPosition,
+                             e.yCursorPosition,
+                             e.downTime,
+                             e.pointerProperties,
+                             e.pointerCoords};
+}
+
+TracedEvent createTracedEvent(const KeyEntry& e) {
+    return TracedKeyEvent{e.id,        e.eventTime, e.policyFlags, e.deviceId, e.source,
+                          e.displayId, e.action,    e.keyCode,     e.scanCode, e.metaState,
+                          e.downTime,  e.flags,     e.repeatCount};
+}
+
+} // namespace
+
+// --- InputTracer ---
+
+InputTracer::InputTracer(std::unique_ptr<InputTracingBackendInterface> backend)
+      : mTracerThread(&InputTracer::threadLoop, this), mBackend(std::move(backend)) {}
+
+InputTracer::~InputTracer() {
+    {
+        std::scoped_lock lock(mLock);
+        mThreadExit = true;
+    }
+    mThreadWakeCondition.notify_all();
+    mTracerThread.join();
+}
+
+std::unique_ptr<EventTrackerInterface> InputTracer::traceInboundEvent(const EventEntry& entry) {
+    std::scoped_lock lock(mLock);
+    TracedEvent traced;
+
+    if (entry.type == EventEntry::Type::MOTION) {
+        const auto& motion = static_cast<const MotionEntry&>(entry);
+        traced = createTracedEvent(motion);
+    } else if (entry.type == EventEntry::Type::KEY) {
+        const auto& key = static_cast<const KeyEntry&>(entry);
+        traced = createTracedEvent(key);
+    } else {
+        LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
+    }
+
+    return std::make_unique<EventTrackerImpl>(*this, std::move(traced));
+}
+
+void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie,
+                                       const InputTarget& target) {
+    std::scoped_lock lock(mLock);
+    auto& cookieState = getState(cookie);
+    if (!cookieState) {
+        LOG(FATAL) << "dispatchToTargetHint() should not be called after eventProcessingComplete()";
+    }
+    // TODO(b/210460522): Determine if the event is sensitive based on the target.
+}
+
+void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) {
+    {
+        std::scoped_lock lock(mLock);
+        auto& cookieState = getState(cookie);
+        if (!cookieState) {
+            LOG(FATAL) << "Traced event was already logged. "
+                          "eventProcessingComplete() was likely called more than once.";
+        }
+        mTraceQueue.emplace_back(std::move(*cookieState));
+        cookieState.reset();
+    } // release lock
+
+    mThreadWakeCondition.notify_all();
+}
+
+void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry,
+                                     const EventTrackerInterface* cookie) {
+    {
+        std::scoped_lock lock(mLock);
+        const EventEntry& entry = *dispatchEntry.eventEntry;
+
+        TracedEvent traced;
+        if (entry.type == EventEntry::Type::MOTION) {
+            const auto& motion = static_cast<const MotionEntry&>(entry);
+            traced = createTracedEvent(motion);
+        } else if (entry.type == EventEntry::Type::KEY) {
+            const auto& key = static_cast<const KeyEntry&>(entry);
+            traced = createTracedEvent(key);
+        } else {
+            LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
+        }
+
+        if (!cookie) {
+            // This event was not tracked as an inbound event, so trace it now.
+            mTraceQueue.emplace_back(traced);
+        }
+
+        // The vsyncId only has meaning if the event is targeting a window.
+        const int32_t windowId = dispatchEntry.windowId.value_or(0);
+        const int32_t vsyncId = dispatchEntry.windowId.has_value() ? dispatchEntry.vsyncId : 0;
+
+        mDispatchTraceQueue.emplace_back(std::move(traced), dispatchEntry.deliveryTime,
+                                         dispatchEntry.resolvedFlags, dispatchEntry.targetUid,
+                                         vsyncId, windowId, dispatchEntry.transform,
+                                         dispatchEntry.rawTransform);
+    } // release lock
+
+    mThreadWakeCondition.notify_all();
+}
+
+std::optional<InputTracer::EventState>& InputTracer::getState(const EventTrackerInterface& cookie) {
+    return static_cast<const EventTrackerImpl&>(cookie).mLockedState;
+}
+
+void InputTracer::threadLoop() {
+    androidSetThreadName("InputTracer");
+
+    while (true) {
+        std::vector<const EventState> eventsToTrace;
+        std::vector<const WindowDispatchArgs> dispatchEventsToTrace;
+        {
+            std::unique_lock lock(mLock);
+            base::ScopedLockAssertion assumeLocked(mLock);
+            if (mThreadExit) {
+                return;
+            }
+            if (mTraceQueue.empty() && mDispatchTraceQueue.empty()) {
+                // Wait indefinitely until the thread is awoken.
+                mThreadWakeCondition.wait(lock);
+            }
+
+            mTraceQueue.swap(eventsToTrace);
+            mDispatchTraceQueue.swap(dispatchEventsToTrace);
+        } // release lock
+
+        // Trace the events into the backend without holding the lock to reduce the amount of
+        // work performed in the critical section.
+        writeEventsToBackend(eventsToTrace, dispatchEventsToTrace);
+        eventsToTrace.clear();
+        dispatchEventsToTrace.clear();
+    }
+}
+
+void InputTracer::writeEventsToBackend(
+        const std::vector<const EventState>& events,
+        const std::vector<const WindowDispatchArgs>& dispatchEvents) {
+    for (const auto& event : events) {
+        if (auto* motion = std::get_if<TracedMotionEvent>(&event.event); motion != nullptr) {
+            mBackend->traceMotionEvent(*motion);
+        } else {
+            mBackend->traceKeyEvent(std::get<TracedKeyEvent>(event.event));
+        }
+    }
+
+    for (const auto& dispatchArgs : dispatchEvents) {
+        mBackend->traceWindowDispatch(dispatchArgs);
+    }
+}
+
+// --- InputTracer::EventTrackerImpl ---
+
+InputTracer::EventTrackerImpl::EventTrackerImpl(InputTracer& tracer, TracedEvent&& event)
+      : mTracer(tracer), mLockedState(event) {}
+
+InputTracer::EventTrackerImpl::~EventTrackerImpl() {
+    {
+        std::scoped_lock lock(mTracer.mLock);
+        if (!mLockedState) {
+            // This event has already been written to the trace as expected.
+            return;
+        }
+        // We're still holding on to the state, which means it hasn't yet been written to the trace.
+        // Write it to the trace now.
+        // TODO(b/210460522): Determine why/where the event is being destroyed before
+        //   eventProcessingComplete() is called.
+        mTracer.mTraceQueue.emplace_back(std::move(*mLockedState));
+        mLockedState.reset();
+    } // release lock
+
+    mTracer.mThreadWakeCondition.notify_all();
+}
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h
new file mode 100644
index 0000000..9fe395d
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracer.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2024 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 "InputTracerInterface.h"
+
+#include <android-base/thread_annotations.h>
+#include <gui/WindowInfo.h>
+
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <unordered_set>
+#include <vector>
+
+#include "../Entry.h"
+#include "InputTracingBackendInterface.h"
+
+namespace android::inputdispatcher::trace::impl {
+
+/**
+ * The tracer implementation for InputDispatcher.
+ *
+ * InputTracer is thread-safe, so it can be called from any thread. Upon construction, InputTracer
+ * will start its own thread that it uses for write events into the tracing backend. That is the
+ * one and only thread that will interact with the tracing backend, since the Perfetto backend
+ * uses thread-local storage.
+ *
+ * See the documentation in InputTracerInterface for the API surface.
+ */
+class InputTracer : public InputTracerInterface {
+public:
+    explicit InputTracer(std::unique_ptr<InputTracingBackendInterface>);
+    ~InputTracer() override;
+    InputTracer(const InputTracer&) = delete;
+    InputTracer& operator=(const InputTracer&) = delete;
+
+    std::unique_ptr<EventTrackerInterface> traceInboundEvent(const EventEntry&) override;
+    void dispatchToTargetHint(const EventTrackerInterface&, const InputTarget&) override;
+    void eventProcessingComplete(const EventTrackerInterface&) override;
+    void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface*) override;
+
+private:
+    std::mutex mLock;
+    std::thread mTracerThread;
+    bool mThreadExit GUARDED_BY(mLock){false};
+    std::condition_variable mThreadWakeCondition;
+    std::unique_ptr<InputTracingBackendInterface> mBackend;
+
+    // The state of a tracked event.
+    struct EventState {
+        const TracedEvent event;
+        // TODO(b/210460522): Add additional args for tracking event sensitivity and
+        //  dispatch target UIDs.
+    };
+    std::vector<const EventState> mTraceQueue GUARDED_BY(mLock);
+    using WindowDispatchArgs = InputTracingBackendInterface::WindowDispatchArgs;
+    std::vector<const WindowDispatchArgs> mDispatchTraceQueue GUARDED_BY(mLock);
+
+    // Provides thread-safe access to the state from an event tracker cookie.
+    std::optional<EventState>& getState(const EventTrackerInterface&) REQUIRES(mLock);
+
+    // Implementation of the event tracker cookie.
+    class EventTrackerImpl : public EventTrackerInterface {
+    public:
+        explicit EventTrackerImpl(InputTracer&, TracedEvent&& entry);
+        virtual ~EventTrackerImpl() override;
+
+    private:
+        InputTracer& mTracer;
+        // This event tracker cookie will only hold the state as long as it has not been written
+        // to the trace. The state is released when the event is written to the trace.
+        mutable std::optional<EventState> mLockedState;
+
+        // Only allow InputTracer access to the locked state through getTrackerState() to ensure
+        // that the InputTracer lock is held when this is accessed.
+        friend std::optional<EventState>& InputTracer::getState(const EventTrackerInterface&);
+    };
+
+    void threadLoop();
+    void writeEventsToBackend(const std::vector<const EventState>& events,
+                              const std::vector<const WindowDispatchArgs>& dispatchEvents);
+};
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
index b430a5b..bc47817 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
@@ -16,11 +16,65 @@
 
 #pragma once
 
-#include "../Entry.h"
+#include <gui/PidUid.h>
+#include <input/Input.h>
+#include <ui/Transform.h>
+
+#include <array>
+#include <variant>
+#include <vector>
 
 namespace android::inputdispatcher::trace {
 
 /**
+ * A representation of an Android KeyEvent used by the tracing backend.
+ */
+struct TracedKeyEvent {
+    int32_t id;
+    nsecs_t eventTime;
+    uint32_t policyFlags;
+    int32_t deviceId;
+    uint32_t source;
+    int32_t displayId;
+    int32_t action;
+    int32_t keyCode;
+    int32_t scanCode;
+    int32_t metaState;
+    nsecs_t downTime;
+    int32_t flags;
+    int32_t repeatCount;
+};
+
+/**
+ * A representation of an Android MotionEvent used by the tracing backend.
+ */
+struct TracedMotionEvent {
+    int32_t id;
+    nsecs_t eventTime;
+    uint32_t policyFlags;
+    int32_t deviceId;
+    uint32_t source;
+    int32_t displayId;
+    int32_t action;
+    int32_t actionButton;
+    int32_t flags;
+    int32_t metaState;
+    int32_t buttonState;
+    MotionClassification classification;
+    int32_t edgeFlags;
+    float xPrecision;
+    float yPrecision;
+    float xCursorPosition;
+    float yCursorPosition;
+    nsecs_t downTime;
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
+};
+
+/** A representation of a traced input event. */
+using TracedEvent = std::variant<TracedKeyEvent, TracedMotionEvent>;
+
+/**
  * An interface for the tracing backend, used for setting a custom backend for testing.
  */
 class InputTracingBackendInterface {
@@ -28,14 +82,14 @@
     virtual ~InputTracingBackendInterface() = default;
 
     /** Trace a KeyEvent. */
-    virtual void traceKeyEvent(const KeyEntry&) const = 0;
+    virtual void traceKeyEvent(const TracedKeyEvent&) const = 0;
 
     /** Trace a MotionEvent. */
-    virtual void traceMotionEvent(const MotionEntry&) const = 0;
+    virtual void traceMotionEvent(const TracedMotionEvent&) const = 0;
 
     /** Trace an event being sent to a window. */
     struct WindowDispatchArgs {
-        std::variant<MotionEntry, KeyEntry> eventEntry;
+        TracedEvent eventEntry;
         nsecs_t deliveryTime;
         int32_t resolvedFlags;
         gui::Uid targetUid;
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
new file mode 100644
index 0000000..4442ad8
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2024 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 "InputTracer"
+
+#include "InputTracingPerfettoBackend.h"
+
+#include "AndroidInputEventProtoConverter.h"
+
+#include <android-base/logging.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+
+namespace android::inputdispatcher::trace::impl {
+
+namespace {
+
+constexpr auto INPUT_EVENT_TRACE_DATA_SOURCE_NAME = "android.input.inputevent";
+
+} // namespace
+
+// --- PerfettoBackend::InputEventDataSource ---
+
+void PerfettoBackend::InputEventDataSource::OnStart(const perfetto::DataSourceBase::StartArgs&) {
+    LOG(INFO) << "Starting perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME;
+}
+
+void PerfettoBackend::InputEventDataSource::OnStop(const perfetto::DataSourceBase::StopArgs&) {
+    LOG(INFO) << "Stopping perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME;
+    InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { ctx.Flush(); });
+}
+
+// --- PerfettoBackend ---
+
+std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{};
+
+PerfettoBackend::PerfettoBackend() {
+    // Use a once-flag to ensure that the data source is only registered once per boot, since
+    // we never unregister the InputEventDataSource.
+    std::call_once(sDataSourceRegistrationFlag, []() {
+        perfetto::TracingInitArgs args;
+        args.backends = perfetto::kSystemBackend;
+        perfetto::Tracing::Initialize(args);
+
+        // Register our custom data source for input event tracing.
+        perfetto::DataSourceDescriptor dsd;
+        dsd.set_name(INPUT_EVENT_TRACE_DATA_SOURCE_NAME);
+        InputEventDataSource::Register(dsd);
+        LOG(INFO) << "InputTracer initialized for data source: "
+                  << INPUT_EVENT_TRACE_DATA_SOURCE_NAME;
+    });
+}
+
+void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event) const {
+    InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+        auto tracePacket = ctx.NewTracePacket();
+        auto* inputEvent = tracePacket->set_android_input_event();
+        auto* dispatchMotion = inputEvent->set_dispatcher_motion_event();
+        AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion);
+    });
+}
+
+void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event) const {
+    InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+        auto tracePacket = ctx.NewTracePacket();
+        auto* inputEvent = tracePacket->set_android_input_event();
+        auto* dispatchKey = inputEvent->set_dispatcher_key_event();
+        AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey);
+    });
+}
+
+void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs) const {
+    InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+        auto tracePacket = ctx.NewTracePacket();
+        auto* inputEventProto = tracePacket->set_android_input_event();
+        auto* dispatchEventProto = inputEventProto->set_dispatcher_window_dispatch_event();
+        AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs,
+                                                                    *dispatchEventProto);
+    });
+}
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
new file mode 100644
index 0000000..2777cfe
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 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 "InputTracingBackendInterface.h"
+
+#include <perfetto/tracing.h>
+#include <mutex>
+
+namespace android::inputdispatcher::trace::impl {
+
+/**
+ * The tracing backend that writes events into ongoing Perfetto traces.
+ *
+ * Example shell command to take an input trace from Perfetto:
+ *
+ *   adb shell  perfetto \
+ *    -c - --txt \
+ *    -o /data/misc/perfetto-traces/trace.input-trace \
+ *    <<END
+ *    buffers: {
+ *      size_kb: 5000
+ *      fill_policy: RING_BUFFER
+ *    }
+ *    data_sources: {
+ *      config {
+ *          name: "android.input.inputevent"
+ *      }
+ *    }
+ *    END
+ */
+class PerfettoBackend : public InputTracingBackendInterface {
+public:
+    PerfettoBackend();
+    ~PerfettoBackend() override = default;
+
+    void traceKeyEvent(const TracedKeyEvent&) const override;
+    void traceMotionEvent(const TracedMotionEvent&) const override;
+    void traceWindowDispatch(const WindowDispatchArgs&) const override;
+
+    class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> {
+    public:
+        void OnSetup(const SetupArgs&) override {}
+        void OnStart(const StartArgs&) override;
+        void OnStop(const StopArgs&) override;
+    };
+
+private:
+    static std::once_flag sDataSourceRegistrationFlag;
+};
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 9abfef1..79c8a4b 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -226,6 +226,9 @@
     // True to enable tap-to-click on touchpads.
     bool touchpadTapToClickEnabled;
 
+    // True to enable tap dragging on touchpads.
+    bool touchpadTapDraggingEnabled;
+
     // True to enable a zone on the right-hand side of touchpads where clicks will be turned into
     // context (a.k.a. "right") clicks.
     bool touchpadRightClickZoneEnabled;
@@ -266,6 +269,7 @@
             touchpadPointerSpeed(0),
             touchpadNaturalScrollingEnabled(true),
             touchpadTapToClickEnabled(true),
+            touchpadTapDraggingEnabled(false),
             touchpadRightClickZoneEnabled(false),
             stylusButtonMotionEventsEnabled(true),
             stylusPointerIconEnabled(false) {}
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index f954370..ba586d7 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_native_license"
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index a41064b..f3f15df 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -251,6 +251,7 @@
             mAssociatedDeviceType =
                     getValueByKey(readerConfig.deviceTypeAssociations, mIdentifier.location);
             mIsWaking = mConfiguration.getBool("device.wake").value_or(false);
+            mShouldSmoothScroll = mConfiguration.getBool("device.viewBehavior_smoothScroll");
         }
 
         if (!changes.any() || changes.test(Change::DEVICE_ALIAS)) {
@@ -401,7 +402,8 @@
 InputDeviceInfo InputDevice::getDeviceInfo() {
     InputDeviceInfo outDeviceInfo;
     outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal,
-                             mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE));
+                             mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE),
+                             {mShouldSmoothScroll});
 
     for_each_mapper(
             [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(outDeviceInfo); });
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index ba7234b..0719b0c 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -199,6 +199,7 @@
     std::optional<DisplayViewport> mAssociatedViewport;
     bool mHasMic;
     bool mDropUntilNextSync;
+    std::optional<bool> mShouldSmoothScroll;
 
     typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
     int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index d207ed1..65f69c5 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -164,8 +164,9 @@
         configureOnChangeDisplayInfo(readerConfig);
     }
 
+    // Pointer speed settings depend on display settings.
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED) ||
-        configurePointerCapture) {
+        changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) || configurePointerCapture) {
         configureOnChangePointerSpeed(readerConfig);
     }
     return out;
@@ -515,7 +516,11 @@
             mNewPointerVelocityControl.setCurve(
                     createAccelerationCurveForPointerSensitivity(config.mousePointerSpeed));
         } else {
-            mOldPointerVelocityControl.setParameters(config.pointerVelocityControlParameters);
+            mOldPointerVelocityControl.setParameters(
+                    (config.displaysWithMousePointerAccelerationDisabled.count(
+                             mDisplayId.value_or(ADISPLAY_ID_NONE)) == 0)
+                            ? config.pointerVelocityControlParameters
+                            : FLAT_VELOCITY_CONTROL_PARAMS);
         }
         mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters);
         mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters);
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index bdc1640..b990dd5 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -397,6 +397,8 @@
                 .setBoolValues({config.touchpadNaturalScrollingEnabled});
         mPropertyProvider.getProperty("Tap Enable")
                 .setBoolValues({config.touchpadTapToClickEnabled});
+        mPropertyProvider.getProperty("Tap Drag Enable")
+                .setBoolValues({config.touchpadTapDraggingEnabled});
         mPropertyProvider.getProperty("Button Right Click Zone Enable")
                 .setBoolValues({config.touchpadRightClickZoneEnabled});
     }
diff --git a/services/inputflinger/reporter/Android.bp b/services/inputflinger/reporter/Android.bp
index b1e1aee..e85a104 100644
--- a/services/inputflinger/reporter/Android.bp
+++ b/services/inputflinger/reporter/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_native_license"
diff --git a/services/inputflinger/rust/Android.bp b/services/inputflinger/rust/Android.bp
index 2803805..9e6dbe4 100644
--- a/services/inputflinger/rust/Android.bp
+++ b/services/inputflinger/rust/Android.bp
@@ -42,6 +42,7 @@
         "libbinder_rs",
         "liblog_rust",
         "liblogger",
+        "libnix",
     ],
     host_supported: true,
 }
diff --git a/services/inputflinger/rust/bounce_keys_filter.rs b/services/inputflinger/rust/bounce_keys_filter.rs
index 894b881..2d5039a 100644
--- a/services/inputflinger/rust/bounce_keys_filter.rs
+++ b/services/inputflinger/rust/bounce_keys_filter.rs
@@ -118,6 +118,10 @@
         }
         self.next.notify_devices_changed(device_infos);
     }
+
+    fn destroy(&mut self) {
+        self.next.destroy();
+    }
 }
 
 #[cfg(test)]
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
index e94a71f..a544fa3 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -22,11 +22,14 @@
 use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
     DeviceInfo::DeviceInfo,
     IInputFilter::{IInputFilter, IInputFilterCallbacks::IInputFilterCallbacks},
+    IInputThread::{IInputThread, IInputThreadCallback::IInputThreadCallback},
     InputFilterConfiguration::InputFilterConfiguration,
     KeyEvent::KeyEvent,
 };
 
 use crate::bounce_keys_filter::BounceKeysFilter;
+use crate::input_filter_thread::InputFilterThread;
+use crate::slow_keys_filter::SlowKeysFilter;
 use crate::sticky_keys_filter::StickyKeysFilter;
 use log::{error, info};
 use std::sync::{Arc, Mutex, RwLock};
@@ -35,6 +38,7 @@
 pub trait Filter {
     fn notify_key(&mut self, event: &KeyEvent);
     fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]);
+    fn destroy(&mut self);
 }
 
 struct InputFilterState {
@@ -50,6 +54,7 @@
     // Access to mutable references to mutable state (includes access to filters, enabled, etc.) is
     // guarded by Mutex for thread safety
     state: Mutex<InputFilterState>,
+    input_filter_thread: InputFilterThread,
 }
 
 impl Interface for InputFilter {}
@@ -67,7 +72,11 @@
         first_filter: Box<dyn Filter + Send + Sync>,
         callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
     ) -> InputFilter {
-        Self { callbacks, state: Mutex::new(InputFilterState { first_filter, enabled: false }) }
+        Self {
+            callbacks: callbacks.clone(),
+            state: Mutex::new(InputFilterState { first_filter, enabled: false }),
+            input_filter_thread: InputFilterThread::new(InputFilterThreadCreator::new(callbacks)),
+        }
     }
 }
 
@@ -89,24 +98,36 @@
     }
 
     fn notifyConfigurationChanged(&self, config: &InputFilterConfiguration) -> binder::Result<()> {
-        let mut state = self.state.lock().unwrap();
-        let mut first_filter: Box<dyn Filter + Send + Sync> =
-            Box::new(BaseFilter::new(self.callbacks.clone()));
-        if config.stickyKeysEnabled {
-            first_filter = Box::new(StickyKeysFilter::new(
-                first_filter,
-                ModifierStateListener::new(self.callbacks.clone()),
-            ));
-            state.enabled = true;
-            info!("Sticky keys filter is installed");
+        {
+            let mut state = self.state.lock().unwrap();
+            state.first_filter.destroy();
+            let mut first_filter: Box<dyn Filter + Send + Sync> =
+                Box::new(BaseFilter::new(self.callbacks.clone()));
+            if config.stickyKeysEnabled {
+                first_filter = Box::new(StickyKeysFilter::new(
+                    first_filter,
+                    ModifierStateListener::new(self.callbacks.clone()),
+                ));
+                state.enabled = true;
+                info!("Sticky keys filter is installed");
+            }
+            if config.slowKeysThresholdNs > 0 {
+                first_filter = Box::new(SlowKeysFilter::new(
+                    first_filter,
+                    config.slowKeysThresholdNs,
+                    self.input_filter_thread.clone(),
+                ));
+                state.enabled = true;
+                info!("Slow keys filter is installed");
+            }
+            if config.bounceKeysThresholdNs > 0 {
+                first_filter =
+                    Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs));
+                state.enabled = true;
+                info!("Bounce keys filter is installed");
+            }
+            state.first_filter = first_filter;
         }
-        if config.bounceKeysThresholdNs > 0 {
-            first_filter =
-                Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs));
-            state.enabled = true;
-            info!("Bounce keys filter is installed");
-        }
-        state.first_filter = first_filter;
         Result::Ok(())
     }
 }
@@ -132,27 +153,51 @@
     fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) {
         // do nothing
     }
+
+    fn destroy(&mut self) {
+        // do nothing
+    }
 }
 
-pub struct ModifierStateListener {
-    callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
-}
+/// This struct wraps around IInputFilterCallbacks restricting access to only
+/// {@code onModifierStateChanged()} method of the callback.
+#[derive(Clone)]
+pub struct ModifierStateListener(Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>);
 
 impl ModifierStateListener {
-    /// Create a new InputFilter instance.
     pub fn new(callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>) -> ModifierStateListener {
-        Self { callbacks }
+        Self(callbacks)
     }
 
     pub fn modifier_state_changed(&self, modifier_state: u32, locked_modifier_state: u32) {
         let _ = self
-            .callbacks
+            .0
             .read()
             .unwrap()
             .onModifierStateChanged(modifier_state as i32, locked_modifier_state as i32);
     }
 }
 
+/// This struct wraps around IInputFilterCallbacks restricting access to only
+/// {@code createInputFilterThread()} method of the callback.
+#[derive(Clone)]
+pub struct InputFilterThreadCreator(Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>);
+
+impl InputFilterThreadCreator {
+    pub fn new(
+        callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+    ) -> InputFilterThreadCreator {
+        Self(callbacks)
+    }
+
+    pub fn create(
+        &self,
+        input_thread_callback: &Strong<dyn IInputThreadCallback>,
+    ) -> Strong<dyn IInputThread> {
+        self.0.read().unwrap().createInputFilterThread(input_thread_callback).unwrap()
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use crate::input_filter::{
@@ -218,7 +263,7 @@
         let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
         let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
             bounceKeysThresholdNs: 100,
-            stickyKeysEnabled: false,
+            ..Default::default()
         });
         assert!(result.is_ok());
         let result = input_filter.isEnabled();
@@ -231,8 +276,8 @@
         let test_callbacks = TestCallbacks::new();
         let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
         let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
-            bounceKeysThresholdNs: 0,
             stickyKeysEnabled: true,
+            ..Default::default()
         });
         assert!(result.is_ok());
         let result = input_filter.isEnabled();
@@ -240,6 +285,33 @@
         assert!(result.unwrap());
     }
 
+    #[test]
+    fn test_notify_configuration_changed_enabled_slow_keys() {
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
+        let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
+            slowKeysThresholdNs: 100,
+            ..Default::default()
+        });
+        assert!(result.is_ok());
+        let result = input_filter.isEnabled();
+        assert!(result.is_ok());
+        assert!(result.unwrap());
+    }
+
+    #[test]
+    fn test_notify_configuration_changed_destroys_existing_filters() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::create_input_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))),
+        );
+        let _ = input_filter
+            .notifyConfigurationChanged(&InputFilterConfiguration { ..Default::default() });
+        assert!(test_filter.is_destroy_called());
+    }
+
     fn create_key_event() -> KeyEvent {
         KeyEvent {
             id: 1,
@@ -271,6 +343,7 @@
     struct TestFilterInner {
         is_device_changed_called: bool,
         last_event: Option<KeyEvent>,
+        is_destroy_called: bool,
     }
 
     #[derive(Default, Clone)]
@@ -296,6 +369,10 @@
         pub fn is_device_changed_called(&self) -> bool {
             self.0.read().unwrap().is_device_changed_called
         }
+
+        pub fn is_destroy_called(&self) -> bool {
+            self.0.read().unwrap().is_destroy_called
+        }
     }
 
     impl Filter for TestFilter {
@@ -305,14 +382,19 @@
         fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) {
             self.inner().is_device_changed_called = true;
         }
+        fn destroy(&mut self) {
+            self.inner().is_destroy_called = true;
+        }
     }
 }
 
 #[cfg(test)]
 pub mod test_callbacks {
-    use binder::Interface;
+    use binder::{BinderFeatures, Interface, Strong};
     use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
-        IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks, KeyEvent::KeyEvent,
+        IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
+        IInputThread::{BnInputThread, IInputThread, IInputThreadCallback::IInputThreadCallback},
+        KeyEvent::KeyEvent,
     };
     use std::sync::{Arc, RwLock, RwLockWriteGuard};
 
@@ -321,6 +403,7 @@
         last_modifier_state: u32,
         last_locked_modifier_state: u32,
         last_event: Option<KeyEvent>,
+        test_thread: Option<TestThread>,
     }
 
     #[derive(Default, Clone)]
@@ -354,6 +437,17 @@
         pub fn get_last_locked_modifier_state(&self) -> u32 {
             self.0.read().unwrap().last_locked_modifier_state
         }
+
+        pub fn is_thread_created(&self) -> bool {
+            self.0.read().unwrap().test_thread.is_some()
+        }
+
+        pub fn is_thread_finished(&self) -> bool {
+            if let Some(test_thread) = &self.0.read().unwrap().test_thread {
+                return test_thread.is_finish_called();
+            }
+            false
+        }
     }
 
     impl IInputFilterCallbacks for TestCallbacks {
@@ -371,5 +465,45 @@
             self.inner().last_locked_modifier_state = locked_modifier_state as u32;
             Result::Ok(())
         }
+
+        fn createInputFilterThread(
+            &self,
+            _callback: &Strong<dyn IInputThreadCallback>,
+        ) -> std::result::Result<Strong<dyn IInputThread>, binder::Status> {
+            let test_thread = TestThread::new();
+            self.inner().test_thread = Some(test_thread.clone());
+            Result::Ok(BnInputThread::new_binder(test_thread, BinderFeatures::default()))
+        }
+    }
+
+    #[derive(Default)]
+    struct TestThreadInner {
+        is_finish_called: bool,
+    }
+
+    #[derive(Default, Clone)]
+    struct TestThread(Arc<RwLock<TestThreadInner>>);
+
+    impl Interface for TestThread {}
+
+    impl TestThread {
+        pub fn new() -> Self {
+            Default::default()
+        }
+
+        fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> {
+            self.0.write().unwrap()
+        }
+
+        pub fn is_finish_called(&self) -> bool {
+            self.0.read().unwrap().is_finish_called
+        }
+    }
+
+    impl IInputThread for TestThread {
+        fn finish(&self) -> binder::Result<()> {
+            self.inner().is_finish_called = true;
+            Result::Ok(())
+        }
     }
 }
diff --git a/services/inputflinger/rust/input_filter_thread.rs b/services/inputflinger/rust/input_filter_thread.rs
new file mode 100644
index 0000000..2d503ae
--- /dev/null
+++ b/services/inputflinger/rust/input_filter_thread.rs
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2024 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.
+ */
+
+//! Input filter thread implementation in rust.
+//! Using IInputFilter.aidl interface to create ever looping thread with JNI support, rest of
+//! thread handling is done from rust side.
+//!
+//! NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't
+//! have JNI support and can't call into Java policy that we use currently. libutils provided
+//! Thread.h also recommends against using std::thread and using the provided infrastructure that
+//! already provides way of attaching JniEnv to the created thread. So, we are using an AIDL
+//! interface to expose the InputThread infrastructure to rust.
+
+use crate::input_filter::InputFilterThreadCreator;
+use binder::{BinderFeatures, Interface, Strong};
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::IInputThread::{
+    IInputThread, IInputThreadCallback::BnInputThreadCallback,
+    IInputThreadCallback::IInputThreadCallback,
+};
+use log::{debug, error};
+use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+use std::sync::{Arc, RwLock, RwLockWriteGuard};
+use std::time::Duration;
+use std::{thread, thread::Thread};
+
+/// Interface to receive callback from Input filter thread
+pub trait ThreadCallback {
+    /// Calls back after the requested timeout expires.
+    /// {@see InputFilterThread.request_timeout_at_time(...)}
+    ///
+    /// NOTE: In case of multiple requests, the timeout request which is earliest in time, will be
+    /// fulfilled and notified to all the listeners. It's up to the listeners to re-request another
+    /// timeout in the future.
+    fn notify_timeout_expired(&self, when_nanos: i64);
+    /// Unique name for the listener, which will be used to uniquely identify the listener.
+    fn name(&self) -> &str;
+}
+
+#[derive(Clone)]
+pub struct InputFilterThread {
+    thread_creator: InputFilterThreadCreator,
+    thread_callback_handler: ThreadCallbackHandler,
+    inner: Arc<RwLock<InputFilterThreadInner>>,
+}
+
+struct InputFilterThreadInner {
+    cpp_thread: Option<Strong<dyn IInputThread>>,
+    looper: Option<Thread>,
+    next_timeout: i64,
+    is_finishing: bool,
+}
+
+impl InputFilterThread {
+    /// Create a new InputFilterThread instance.
+    /// NOTE: This will create a new thread. Clone the existing instance to reuse the same thread.
+    pub fn new(thread_creator: InputFilterThreadCreator) -> InputFilterThread {
+        Self {
+            thread_creator,
+            thread_callback_handler: ThreadCallbackHandler::new(),
+            inner: Arc::new(RwLock::new(InputFilterThreadInner {
+                cpp_thread: None,
+                looper: None,
+                next_timeout: i64::MAX,
+                is_finishing: false,
+            })),
+        }
+    }
+
+    /// Listener requesting a timeout in future will receive a callback at or before the requested
+    /// time on the input filter thread.
+    /// {@see ThreadCallback.notify_timeout_expired(...)}
+    pub fn request_timeout_at_time(&self, when_nanos: i64) {
+        let filter_thread = &mut self.filter_thread();
+        if when_nanos < filter_thread.next_timeout {
+            filter_thread.next_timeout = when_nanos;
+            if let Some(looper) = &filter_thread.looper {
+                looper.unpark();
+            }
+        }
+    }
+
+    /// Registers a callback listener.
+    ///
+    /// NOTE: If a listener with the same name already exists when registering using
+    /// {@see InputFilterThread.register_thread_callback(...)}, we will ignore the listener. You
+    /// must clear any previously registered listeners using
+    /// {@see InputFilterThread.unregister_thread_callback(...) before registering the new listener.
+    ///
+    /// NOTE: Also, registering a callback will start the looper if not already started.
+    pub fn register_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
+        self.thread_callback_handler.register_thread_callback(callback);
+        self.start();
+    }
+
+    /// Unregisters a callback listener.
+    ///
+    /// NOTE: Unregistering a callback will stop the looper if not other callback registered.
+    pub fn unregister_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
+        self.thread_callback_handler.unregister_thread_callback(callback);
+        // Stop the thread if no registered callbacks exist. We will recreate the thread when new
+        // callbacks are registered.
+        let has_callbacks = self.thread_callback_handler.has_callbacks();
+        if !has_callbacks {
+            self.stop();
+        }
+    }
+
+    fn start(&self) {
+        debug!("InputFilterThread: start thread");
+        let filter_thread = &mut self.filter_thread();
+        if filter_thread.cpp_thread.is_none() {
+            filter_thread.cpp_thread = Some(self.thread_creator.create(
+                &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()),
+            ));
+            filter_thread.looper = None;
+            filter_thread.is_finishing = false;
+        }
+    }
+
+    fn stop(&self) {
+        debug!("InputFilterThread: stop thread");
+        let filter_thread = &mut self.filter_thread();
+        filter_thread.is_finishing = true;
+        if let Some(looper) = &filter_thread.looper {
+            looper.unpark();
+        }
+        if let Some(cpp_thread) = &filter_thread.cpp_thread {
+            let _ = cpp_thread.finish();
+        }
+        // Clear all references
+        filter_thread.cpp_thread = None;
+        filter_thread.looper = None;
+    }
+
+    fn loop_once(&self, now: i64) {
+        let mut wake_up_time = i64::MAX;
+        let mut timeout_expired = false;
+        {
+            // acquire thread lock
+            let filter_thread = &mut self.filter_thread();
+            if filter_thread.is_finishing {
+                // Thread is finishing so don't block processing on it and let it loop.
+                return;
+            }
+            if filter_thread.next_timeout != i64::MAX {
+                if filter_thread.next_timeout <= now {
+                    timeout_expired = true;
+                    filter_thread.next_timeout = i64::MAX;
+                } else {
+                    wake_up_time = filter_thread.next_timeout;
+                }
+            }
+            if filter_thread.looper.is_none() {
+                filter_thread.looper = Some(std::thread::current());
+            }
+        } // release thread lock
+        if timeout_expired {
+            self.thread_callback_handler.notify_timeout_expired(now);
+        }
+        if wake_up_time == i64::MAX {
+            thread::park();
+        } else {
+            let duration_now = Duration::from_nanos(now as u64);
+            let duration_wake_up = Duration::from_nanos(wake_up_time as u64);
+            thread::park_timeout(duration_wake_up - duration_now);
+        }
+    }
+
+    fn filter_thread(&self) -> RwLockWriteGuard<'_, InputFilterThreadInner> {
+        self.inner.write().unwrap()
+    }
+}
+
+impl Interface for InputFilterThread {}
+
+impl IInputThreadCallback for InputFilterThread {
+    fn loopOnce(&self) -> binder::Result<()> {
+        self.loop_once(clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds());
+        Result::Ok(())
+    }
+}
+
+#[derive(Default, Clone)]
+struct ThreadCallbackHandler(Arc<RwLock<ThreadCallbackHandlerInner>>);
+
+#[derive(Default)]
+struct ThreadCallbackHandlerInner {
+    callbacks: Vec<Box<dyn ThreadCallback + Send + Sync>>,
+}
+
+impl ThreadCallbackHandler {
+    fn new() -> Self {
+        Default::default()
+    }
+
+    fn has_callbacks(&self) -> bool {
+        !&self.0.read().unwrap().callbacks.is_empty()
+    }
+
+    fn register_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
+        let callbacks = &mut self.0.write().unwrap().callbacks;
+        if callbacks.iter().any(|x| x.name() == callback.name()) {
+            error!(
+                "InputFilterThread: register_thread_callback, callback {:?} already exists!",
+                callback.name()
+            );
+            return;
+        }
+        debug!(
+            "InputFilterThread: register_thread_callback, callback {:?} added!",
+            callback.name()
+        );
+        callbacks.push(callback);
+    }
+
+    fn unregister_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
+        let callbacks = &mut self.0.write().unwrap().callbacks;
+        if let Some(index) = callbacks.iter().position(|x| x.name() == callback.name()) {
+            callbacks.remove(index);
+            debug!(
+                "InputFilterThread: unregister_thread_callback, callback {:?} removed!",
+                callback.name()
+            );
+            return;
+        }
+        error!(
+            "InputFilterThread: unregister_thread_callback, callback {:?} doesn't exist",
+            callback.name()
+        );
+    }
+
+    fn notify_timeout_expired(&self, when_nanos: i64) {
+        let callbacks = &self.0.read().unwrap().callbacks;
+        for callback in callbacks.iter() {
+            callback.notify_timeout_expired(when_nanos);
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::input_filter::test_callbacks::TestCallbacks;
+    use crate::input_filter_thread::{
+        test_thread::TestThread, test_thread_callback::TestThreadCallback,
+    };
+
+    #[test]
+    fn test_register_callback_creates_cpp_thread() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread_callback = TestThreadCallback::new();
+        test_thread.register_thread_callback(test_thread_callback);
+        assert!(test_callbacks.is_thread_created());
+    }
+
+    #[test]
+    fn test_unregister_callback_finishes_cpp_thread() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread_callback = TestThreadCallback::new();
+        test_thread.register_thread_callback(test_thread_callback.clone());
+        test_thread.unregister_thread_callback(test_thread_callback);
+        assert!(test_callbacks.is_thread_finished());
+    }
+
+    #[test]
+    fn test_notify_timeout_called_after_timeout_expired() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread_callback = TestThreadCallback::new();
+        test_thread.register_thread_callback(test_thread_callback.clone());
+        test_thread.start_looper();
+
+        test_thread.request_timeout_at_time(500);
+        test_thread.dispatch_next();
+
+        test_thread.move_time_forward(500);
+
+        test_thread.stop_looper();
+        assert!(test_thread_callback.is_notify_timeout_called());
+    }
+
+    #[test]
+    fn test_notify_timeout_not_called_before_timeout_expired() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread_callback = TestThreadCallback::new();
+        test_thread.register_thread_callback(test_thread_callback.clone());
+        test_thread.start_looper();
+
+        test_thread.request_timeout_at_time(500);
+        test_thread.dispatch_next();
+
+        test_thread.move_time_forward(100);
+
+        test_thread.stop_looper();
+        assert!(!test_thread_callback.is_notify_timeout_called());
+    }
+}
+
+#[cfg(test)]
+pub mod test_thread {
+
+    use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator};
+    use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread};
+    use binder::Strong;
+    use std::sync::{
+        atomic::AtomicBool, atomic::AtomicI64, atomic::Ordering, Arc, RwLock, RwLockWriteGuard,
+    };
+    use std::time::Duration;
+
+    #[derive(Clone)]
+    pub struct TestThread {
+        input_thread: InputFilterThread,
+        inner: Arc<RwLock<TestThreadInner>>,
+        exit_flag: Arc<AtomicBool>,
+        now: Arc<AtomicI64>,
+    }
+
+    struct TestThreadInner {
+        join_handle: Option<std::thread::JoinHandle<()>>,
+    }
+
+    impl TestThread {
+        pub fn new(callbacks: TestCallbacks) -> TestThread {
+            Self {
+                input_thread: InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(
+                    RwLock::new(Strong::new(Box::new(callbacks))),
+                ))),
+                inner: Arc::new(RwLock::new(TestThreadInner { join_handle: None })),
+                exit_flag: Arc::new(AtomicBool::new(false)),
+                now: Arc::new(AtomicI64::new(0)),
+            }
+        }
+
+        fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> {
+            self.inner.write().unwrap()
+        }
+
+        pub fn get_input_thread(&self) -> InputFilterThread {
+            self.input_thread.clone()
+        }
+
+        pub fn register_thread_callback(&self, thread_callback: TestThreadCallback) {
+            self.input_thread.register_thread_callback(Box::new(thread_callback));
+        }
+
+        pub fn unregister_thread_callback(&self, thread_callback: TestThreadCallback) {
+            self.input_thread.unregister_thread_callback(Box::new(thread_callback));
+        }
+
+        pub fn start_looper(&self) {
+            self.exit_flag.store(false, Ordering::Relaxed);
+            let clone = self.clone();
+            let join_handle = std::thread::Builder::new()
+                .name("test_thread".to_string())
+                .spawn(move || {
+                    while !clone.exit_flag.load(Ordering::Relaxed) {
+                        clone.loop_once();
+                    }
+                })
+                .unwrap();
+            self.inner().join_handle = Some(join_handle);
+            // Sleep until the looper thread starts
+            std::thread::sleep(Duration::from_millis(10));
+        }
+
+        pub fn stop_looper(&self) {
+            self.exit_flag.store(true, Ordering::Relaxed);
+            {
+                let mut inner = self.inner();
+                if let Some(join_handle) = &inner.join_handle {
+                    join_handle.thread().unpark();
+                }
+                inner.join_handle.take().map(std::thread::JoinHandle::join);
+                inner.join_handle = None;
+            }
+            self.exit_flag.store(false, Ordering::Relaxed);
+        }
+
+        pub fn move_time_forward(&self, value: i64) {
+            let _ = self.now.fetch_add(value, Ordering::Relaxed);
+            self.dispatch_next();
+        }
+
+        pub fn dispatch_next(&self) {
+            if let Some(join_handle) = &self.inner().join_handle {
+                join_handle.thread().unpark();
+            }
+            // Sleep until the looper thread runs a loop
+            std::thread::sleep(Duration::from_millis(10));
+        }
+
+        fn loop_once(&self) {
+            self.input_thread.loop_once(self.now.load(Ordering::Relaxed));
+        }
+
+        pub fn request_timeout_at_time(&self, when_nanos: i64) {
+            self.input_thread.request_timeout_at_time(when_nanos);
+        }
+    }
+}
+
+#[cfg(test)]
+pub mod test_thread_callback {
+    use crate::input_filter_thread::ThreadCallback;
+    use std::sync::{Arc, RwLock, RwLockWriteGuard};
+
+    #[derive(Default)]
+    struct TestThreadCallbackInner {
+        is_notify_timeout_called: bool,
+    }
+
+    #[derive(Default, Clone)]
+    pub struct TestThreadCallback(Arc<RwLock<TestThreadCallbackInner>>);
+
+    impl TestThreadCallback {
+        pub fn new() -> Self {
+            Default::default()
+        }
+
+        fn inner(&self) -> RwLockWriteGuard<'_, TestThreadCallbackInner> {
+            self.0.write().unwrap()
+        }
+
+        pub fn is_notify_timeout_called(&self) -> bool {
+            self.0.read().unwrap().is_notify_timeout_called
+        }
+    }
+
+    impl ThreadCallback for TestThreadCallback {
+        fn notify_timeout_expired(&self, _when_nanos: i64) {
+            self.inner().is_notify_timeout_called = true;
+        }
+        fn name(&self) -> &str {
+            "TestThreadCallback"
+        }
+    }
+}
diff --git a/services/inputflinger/rust/lib.rs b/services/inputflinger/rust/lib.rs
index fa16898..25dfb03 100644
--- a/services/inputflinger/rust/lib.rs
+++ b/services/inputflinger/rust/lib.rs
@@ -21,6 +21,8 @@
 
 mod bounce_keys_filter;
 mod input_filter;
+mod input_filter_thread;
+mod slow_keys_filter;
 mod sticky_keys_filter;
 
 use crate::input_filter::InputFilter;
@@ -68,7 +70,9 @@
 /// incremented using `AIBinder_incStrong`. See `binder::unstable_api::new_spibinder`.
 unsafe fn create_inputflinger_rust(callback: *mut ffi::IInputFlingerRustBootstrapCallbackAIBinder) {
     logger::init(
-        logger::Config::default().with_tag_on_device(LOG_TAG).with_min_level(log::Level::Trace),
+        logger::Config::default()
+            .with_tag_on_device(LOG_TAG)
+            .with_max_level(log::LevelFilter::Trace),
     );
 
     let callback = callback as *mut AIBinder;
diff --git a/services/inputflinger/rust/slow_keys_filter.rs b/services/inputflinger/rust/slow_keys_filter.rs
new file mode 100644
index 0000000..01165b5
--- /dev/null
+++ b/services/inputflinger/rust/slow_keys_filter.rs
@@ -0,0 +1,382 @@
+/*
+ * Copyright 2024 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.
+ */
+
+//! Slow keys input filter implementation.
+//! Slow keys is an accessibility feature to aid users who have physical disabilities, that allows
+//! the user to specify the duration for which one must press-and-hold a key before the system
+//! accepts the keypress.
+use crate::input_filter::Filter;
+use crate::input_filter_thread::{InputFilterThread, ThreadCallback};
+use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+    DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+};
+use log::debug;
+use std::collections::HashSet;
+use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
+
+#[derive(Debug)]
+struct OngoingKeyDown {
+    scancode: i32,
+    device_id: i32,
+    down_time: i64,
+}
+
+struct SlowKeysFilterInner {
+    next: Box<dyn Filter + Send + Sync>,
+    slow_key_threshold_ns: i64,
+    external_devices: HashSet<i32>,
+    // This tracks KeyEvents that are blocked by Slow keys filter and will be passed through if the
+    // press duration exceeds the slow keys threshold.
+    pending_down_events: Vec<KeyEvent>,
+    // This tracks KeyEvent streams that have press duration greater than the slow keys threshold,
+    // hence any future ACTION_DOWN (if repeats are handled on HW side) or ACTION_UP are allowed to
+    // pass through without waiting.
+    ongoing_down_events: Vec<OngoingKeyDown>,
+    input_filter_thread: InputFilterThread,
+}
+
+#[derive(Clone)]
+pub struct SlowKeysFilter(Arc<RwLock<SlowKeysFilterInner>>);
+
+impl SlowKeysFilter {
+    /// Create a new SlowKeysFilter instance.
+    pub fn new(
+        next: Box<dyn Filter + Send + Sync>,
+        slow_key_threshold_ns: i64,
+        input_filter_thread: InputFilterThread,
+    ) -> SlowKeysFilter {
+        let filter = Self(Arc::new(RwLock::new(SlowKeysFilterInner {
+            next,
+            slow_key_threshold_ns,
+            external_devices: HashSet::new(),
+            pending_down_events: Vec::new(),
+            ongoing_down_events: Vec::new(),
+            input_filter_thread: input_filter_thread.clone(),
+        })));
+        input_filter_thread.register_thread_callback(Box::new(filter.clone()));
+        filter
+    }
+
+    fn read_inner(&self) -> RwLockReadGuard<'_, SlowKeysFilterInner> {
+        self.0.read().unwrap()
+    }
+
+    fn write_inner(&self) -> RwLockWriteGuard<'_, SlowKeysFilterInner> {
+        self.0.write().unwrap()
+    }
+
+    fn request_next_callback(&self) {
+        let slow_filter = &self.read_inner();
+        if slow_filter.pending_down_events.is_empty() {
+            return;
+        }
+        if let Some(event) = slow_filter.pending_down_events.iter().min_by_key(|x| x.downTime) {
+            slow_filter.input_filter_thread.request_timeout_at_time(event.downTime);
+        }
+    }
+}
+
+impl Filter for SlowKeysFilter {
+    fn notify_key(&mut self, event: &KeyEvent) {
+        {
+            // acquire write lock
+            let mut slow_filter = self.write_inner();
+            if !(slow_filter.external_devices.contains(&event.deviceId)
+                && event.source == Source::KEYBOARD)
+            {
+                slow_filter.next.notify_key(event);
+                return;
+            }
+            // Pass all events through if key down has already been processed
+            // Do update the downtime before sending the events through
+            if let Some(index) = slow_filter
+                .ongoing_down_events
+                .iter()
+                .position(|x| x.device_id == event.deviceId && x.scancode == event.scanCode)
+            {
+                let mut new_event = *event;
+                new_event.downTime = slow_filter.ongoing_down_events[index].down_time;
+                slow_filter.next.notify_key(&new_event);
+                if event.action == KeyEventAction::UP {
+                    slow_filter.ongoing_down_events.remove(index);
+                }
+                return;
+            }
+            match event.action {
+                KeyEventAction::DOWN => {
+                    if slow_filter
+                        .pending_down_events
+                        .iter()
+                        .any(|x| x.deviceId == event.deviceId && x.scanCode == event.scanCode)
+                    {
+                        debug!("Dropping key down event since another pending down event exists");
+                        return;
+                    }
+                    let mut pending_event = *event;
+                    pending_event.downTime += slow_filter.slow_key_threshold_ns;
+                    pending_event.eventTime = pending_event.downTime;
+                    slow_filter.pending_down_events.push(pending_event);
+                }
+                KeyEventAction::UP => {
+                    debug!("Dropping key up event due to insufficient press duration");
+                    if let Some(index) = slow_filter
+                        .pending_down_events
+                        .iter()
+                        .position(|x| x.deviceId == event.deviceId && x.scanCode == event.scanCode)
+                    {
+                        slow_filter.pending_down_events.remove(index);
+                    }
+                }
+                _ => (),
+            }
+        } // release write lock
+        self.request_next_callback();
+    }
+
+    fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) {
+        let mut slow_filter = self.write_inner();
+        slow_filter
+            .pending_down_events
+            .retain(|event| device_infos.iter().any(|x| event.deviceId == x.deviceId));
+        slow_filter
+            .ongoing_down_events
+            .retain(|event| device_infos.iter().any(|x| event.device_id == x.deviceId));
+        slow_filter.external_devices.clear();
+        for device_info in device_infos {
+            if device_info.external {
+                slow_filter.external_devices.insert(device_info.deviceId);
+            }
+        }
+        slow_filter.next.notify_devices_changed(device_infos);
+    }
+
+    fn destroy(&mut self) {
+        let mut slow_filter = self.write_inner();
+        slow_filter.input_filter_thread.unregister_thread_callback(Box::new(self.clone()));
+        slow_filter.next.destroy();
+    }
+}
+
+impl ThreadCallback for SlowKeysFilter {
+    fn notify_timeout_expired(&self, when_nanos: i64) {
+        {
+            // acquire write lock
+            let slow_filter = &mut self.write_inner();
+            for event in slow_filter.pending_down_events.clone() {
+                if event.downTime <= when_nanos {
+                    slow_filter.next.notify_key(&event);
+                    slow_filter.ongoing_down_events.push(OngoingKeyDown {
+                        scancode: event.scanCode,
+                        device_id: event.deviceId,
+                        down_time: event.downTime,
+                    });
+                }
+            }
+            slow_filter.pending_down_events.retain(|event| event.downTime > when_nanos);
+        } // release write lock
+        self.request_next_callback();
+    }
+
+    fn name(&self) -> &str {
+        "slow_keys_filter"
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::input_filter::{test_callbacks::TestCallbacks, test_filter::TestFilter, Filter};
+    use crate::input_filter_thread::test_thread::TestThread;
+    use crate::slow_keys_filter::SlowKeysFilter;
+    use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+    };
+
+    static BASE_KEY_EVENT: KeyEvent = KeyEvent {
+        id: 1,
+        deviceId: 1,
+        downTime: 0,
+        readTime: 0,
+        eventTime: 0,
+        source: Source::KEYBOARD,
+        displayId: 0,
+        policyFlags: 0,
+        action: KeyEventAction::DOWN,
+        flags: 0,
+        keyCode: 1,
+        scanCode: 0,
+        metaState: 0,
+    };
+
+    #[test]
+    fn test_is_notify_key_for_internal_keyboard_not_blocked() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = TestThread::new(test_callbacks.clone());
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_internal_device(
+            Box::new(next.clone()),
+            test_thread.clone(),
+            1,   /* device_id */
+            100, /* threshold */
+        );
+        test_thread.start_looper();
+
+        let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_is_notify_key_for_external_stylus_not_blocked() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = TestThread::new(test_callbacks.clone());
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_external_device(
+            Box::new(next.clone()),
+            test_thread.clone(),
+            1,   /* device_id */
+            100, /* threshold */
+        );
+        test_thread.start_looper();
+
+        let event =
+            KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_notify_key_for_external_keyboard_when_key_pressed_for_threshold_time() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = TestThread::new(test_callbacks.clone());
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_external_device(
+            Box::new(next.clone()),
+            test_thread.clone(),
+            1,   /* device_id */
+            100, /* threshold */
+        );
+        test_thread.start_looper();
+
+        filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
+        assert!(next.last_event().is_none());
+        test_thread.dispatch_next();
+
+        test_thread.move_time_forward(100);
+
+        test_thread.stop_looper();
+        assert_eq!(
+            next.last_event().unwrap(),
+            KeyEvent {
+                action: KeyEventAction::DOWN,
+                downTime: 100,
+                eventTime: 100,
+                ..BASE_KEY_EVENT
+            }
+        );
+    }
+
+    #[test]
+    fn test_notify_key_for_external_keyboard_when_key_not_pressed_for_threshold_time() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = TestThread::new(test_callbacks.clone());
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_external_device(
+            Box::new(next.clone()),
+            test_thread.clone(),
+            1,   /* device_id */
+            100, /* threshold */
+        );
+        test_thread.start_looper();
+
+        filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
+        test_thread.dispatch_next();
+
+        test_thread.move_time_forward(10);
+
+        filter.notify_key(&KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT });
+        test_thread.dispatch_next();
+
+        test_thread.stop_looper();
+        assert!(next.last_event().is_none());
+    }
+
+    #[test]
+    fn test_notify_key_for_external_keyboard_when_device_removed_before_threshold_time() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = TestThread::new(test_callbacks.clone());
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_external_device(
+            Box::new(next.clone()),
+            test_thread.clone(),
+            1,   /* device_id */
+            100, /* threshold */
+        );
+        test_thread.start_looper();
+
+        filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
+        assert!(next.last_event().is_none());
+        test_thread.dispatch_next();
+
+        filter.notify_devices_changed(&[]);
+        test_thread.dispatch_next();
+
+        test_thread.move_time_forward(100);
+
+        test_thread.stop_looper();
+        assert!(next.last_event().is_none());
+    }
+
+    fn setup_filter_with_external_device(
+        next: Box<dyn Filter + Send + Sync>,
+        test_thread: TestThread,
+        device_id: i32,
+        threshold: i64,
+    ) -> SlowKeysFilter {
+        setup_filter_with_devices(
+            next,
+            test_thread,
+            &[DeviceInfo { deviceId: device_id, external: true }],
+            threshold,
+        )
+    }
+
+    fn setup_filter_with_internal_device(
+        next: Box<dyn Filter + Send + Sync>,
+        test_thread: TestThread,
+        device_id: i32,
+        threshold: i64,
+    ) -> SlowKeysFilter {
+        setup_filter_with_devices(
+            next,
+            test_thread,
+            &[DeviceInfo { deviceId: device_id, external: false }],
+            threshold,
+        )
+    }
+
+    fn setup_filter_with_devices(
+        next: Box<dyn Filter + Send + Sync>,
+        test_thread: TestThread,
+        devices: &[DeviceInfo],
+        threshold: i64,
+    ) -> SlowKeysFilter {
+        let mut filter = SlowKeysFilter::new(next, threshold, test_thread.get_input_thread());
+        filter.notify_devices_changed(devices);
+        filter
+    }
+}
diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs
index da581b8..6c2277c 100644
--- a/services/inputflinger/rust/sticky_keys_filter.rs
+++ b/services/inputflinger/rust/sticky_keys_filter.rs
@@ -142,6 +142,10 @@
         }
         self.next.notify_devices_changed(device_infos);
     }
+
+    fn destroy(&mut self) {
+        self.next.destroy();
+    }
 }
 
 fn is_modifier_key(keycode: i32) -> bool {
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 55aa226..0544757 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_native_license"
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index f1f4a61..163eab0 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -665,7 +665,7 @@
 
     void SetUp() override {
         mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
-        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy);
+        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy, nullptr);
 
         mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
         // Start InputDispatcher thread
@@ -1113,7 +1113,7 @@
 
     sp<IBinder> getToken() { return mConsumer.getChannel()->getConnectionToken(); }
 
-    int getChannelFd() { return mConsumer.getChannel()->getFd().get(); }
+    int getChannelFd() { return mConsumer.getChannel()->getFd(); }
 
 private:
     InputConsumer mConsumer;
@@ -6151,6 +6151,219 @@
     monitor.assertNoEvents();
 }
 
+/**
+ * Two displays
+ * The first monitor has a foreground window, a monitor
+ * The second window has only one monitor.
+ * We first inject a Down event into the first display, this injection should succeed and both
+ * the foreground window and monitor should receive a down event, then inject a Down event into
+ * the second display as well, this injection should fail, at this point, the first display
+ * window and monitor should not receive a cancel or any other event.
+ * Continue to inject Move and UP events to the first display, the events should be received
+ * normally by the foreground window and monitor.
+ */
+TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCanceledWhenAnotherEmptyDisplayReceiveEvents) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver secondMonitor = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "The down event injected into the first display should succeed";
+
+    window->consumeMotionDown();
+    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID,
+                               {100, 200}))
+            << "The down event injected into the second display should fail since there's no "
+               "touchable window";
+
+    // Continue to inject event to first display.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {110, 220}))
+            << "The move event injected into the first display should succeed";
+
+    window->consumeMotionMove();
+    monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                             {110, 220}))
+            << "The up event injected into the first display should succeed";
+
+    window->consumeMotionUp();
+    monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+
+    window->assertNoEvents();
+    monitor.assertNoEvents();
+    secondMonitor.assertNoEvents();
+}
+
+/**
+ * Two displays
+ * There is a monitor and foreground window on each display.
+ * First, we inject down events into each of the two displays, at this point, the foreground windows
+ * and monitors on both displays should receive down events.
+ * At this point, the foreground window of the second display goes away, the gone window should
+ * receive the cancel event, and the other windows and monitors should not receive any events.
+ * Inject a move event into the second display. At this point, the injection should fail because
+ * the second display no longer has a foreground window. At this point, the monitor on the second
+ * display should receive a cancel event, and any windows or monitors on the first display should
+ * not receive any events, and any subsequent injection of events into the second display should
+ * also fail.
+ * Continue to inject events into the first display, and the events should all be injected
+ * successfully and received normally.
+ */
+TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCancelWhenAnotherDisplayMonitorTouchCanceled) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> secondWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "SecondForeground",
+                                       SECOND_DISPLAY_ID);
+
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver secondMonitor = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
+
+    // There is a foreground window on both displays.
+    mDispatcher->onWindowInfosChanged({{*window->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "The down event injected into the first display should succeed";
+
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID,
+                               {100, 200}))
+            << "The down event injected into the second display should succeed";
+
+    secondWindow->consumeMotionDown(SECOND_DISPLAY_ID);
+    secondMonitor.consumeMotionDown(SECOND_DISPLAY_ID);
+
+    // Now second window is gone away.
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    // The gone window should receive a cancel, and the monitor on the second display should not
+    // receive any events.
+    secondWindow->consumeMotionCancel(SECOND_DISPLAY_ID);
+    secondMonitor.assertNoEvents();
+
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                SECOND_DISPLAY_ID, {110, 220}))
+            << "The move event injected into the second display should fail because there's no "
+               "touchable window";
+    // Now the monitor on the second display should receive a cancel event.
+    secondMonitor.consumeMotionCancel(SECOND_DISPLAY_ID);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {110, 200}))
+            << "The move event injected into the first display should succeed";
+
+    window->consumeMotionMove();
+    monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID,
+                             {110, 220}))
+            << "The up event injected into the second display should fail because there's no "
+               "touchable window";
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                             {110, 220}))
+            << "The up event injected into the first display should succeed";
+
+    window->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+
+    window->assertNoEvents();
+    monitor.assertNoEvents();
+    secondWindow->assertNoEvents();
+    secondMonitor.assertNoEvents();
+}
+
+/**
+ * One display with transform
+ * There is a foreground window and a monitor on the display
+ * Inject down event and move event sequentially, the foreground window and monitor can receive down
+ * event and move event, then let the foreground window go away, the foreground window receives
+ * cancel event, inject move event again, the monitor receives cancel event, all the events received
+ * by the monitor should be with the same transform as the display
+ */
+TEST_F(InputDispatcherMonitorTest, MonitorTouchCancelEventWithDisplayTransform) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+
+    ui::Transform transform;
+    transform.set({1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0, 0, 1});
+
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = ADISPLAY_ID_DEFAULT;
+    displayInfo.transform = transform;
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {displayInfo}, 0, 0});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "The down event injected should succeed";
+
+    window->consumeMotionDown();
+    std::unique_ptr<MotionEvent> downMotionEvent = monitor.consumeMotion();
+    EXPECT_EQ(transform, downMotionEvent->getTransform());
+    EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, downMotionEvent->getAction());
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {110, 220}))
+            << "The move event injected should succeed";
+
+    window->consumeMotionMove();
+    std::unique_ptr<MotionEvent> moveMotionEvent = monitor.consumeMotion();
+    EXPECT_EQ(transform, moveMotionEvent->getTransform());
+    EXPECT_EQ(AMOTION_EVENT_ACTION_MOVE, moveMotionEvent->getAction());
+
+    // Let foreground window gone
+    mDispatcher->onWindowInfosChanged({{}, {displayInfo}, 0, 0});
+
+    // Foreground window should receive a cancel event, but not the monitor.
+    window->consumeMotionCancel();
+
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {110, 220}))
+            << "The move event injected should failed";
+    // Now foreground should not receive any events, but monitor should receive a cancel event
+    // with transform that same as display's display.
+    std::unique_ptr<MotionEvent> cancelMotionEvent = monitor.consumeMotion();
+    EXPECT_EQ(transform, cancelMotionEvent->getTransform());
+    EXPECT_EQ(ADISPLAY_ID_DEFAULT, cancelMotionEvent->getDisplayId());
+    EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, cancelMotionEvent->getAction());
+
+    // Other event inject to this display should fail.
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {110, 220}))
+            << "The up event injected should fail because the touched window was removed";
+    window->assertNoEvents();
+    monitor.assertNoEvents();
+}
+
 TEST_F(InputDispatcherTest, TestMoveEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
@@ -7090,12 +7303,9 @@
     sp<FakeWindowHandle> mWindow;
 
     virtual void SetUp() override {
-        mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
-        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy);
-        mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
-        mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY);
-        ASSERT_EQ(OK, mDispatcher->start());
+        InputDispatcherTest::SetUp();
 
+        mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY);
         setUpWindow();
     }
 
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 460a7b1..c1dc7ff 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2706,6 +2706,31 @@
     ASSERT_NO_FATAL_FAILURE(mapper2.assertProcessWasCalled());
 }
 
+TEST_F(InputDeviceTest, Configure_SmoothScrollViewBehaviorNotSet) {
+    // Set some behavior to force the configuration to be update.
+    mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.wake", "1");
+    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                        AINPUT_SOURCE_KEYBOARD);
+
+    std::list<NotifyArgs> unused =
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                               /*changes=*/{});
+
+    ASSERT_FALSE(mDevice->getDeviceInfo().getViewBehavior().shouldSmoothScroll.has_value());
+}
+
+TEST_F(InputDeviceTest, Configure_SmoothScrollViewBehaviorEnabled) {
+    mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.viewBehavior_smoothScroll", "1");
+    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+                                        AINPUT_SOURCE_KEYBOARD);
+
+    std::list<NotifyArgs> unused =
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                               /*changes=*/{});
+
+    ASSERT_TRUE(mDevice->getDeviceInfo().getViewBehavior().shouldSmoothScroll.value_or(false));
+}
+
 TEST_F(InputDeviceTest, WakeDevice_AddsWakeFlagToProcessNotifyArgs) {
     mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.wake", "1");
     FakeInputMapper& mapper =
diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp
index 8a4f6f0..81c3353 100644
--- a/services/inputflinger/tests/fuzzers/Android.bp
+++ b/services/inputflinger/tests/fuzzers/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_native_license"
diff --git a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
index be765cc..c2bf275 100644
--- a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
@@ -123,6 +123,7 @@
     config.touchpadPointerSpeed = fdp.ConsumeIntegralInRange(-7, 7);
     config.touchpadNaturalScrollingEnabled = fdp.ConsumeBool();
     config.touchpadTapToClickEnabled = fdp.ConsumeBool();
+    config.touchpadTapDraggingEnabled = fdp.ConsumeBool();
     config.touchpadRightClickZoneEnabled = fdp.ConsumeBool();
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
index c71c517..39748b8 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
@@ -60,7 +60,7 @@
     virtual void createClientCompositionCache(uint32_t cacheSize) = 0;
 
     // Sends the brightness setting to HWC
-    virtual void applyDisplayBrightness(const bool applyImmediately) = 0;
+    virtual void applyDisplayBrightness(bool applyImmediately) = 0;
 
 protected:
     ~Display() = default;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index c53b461..2dc9a1a 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -73,7 +73,7 @@
             const compositionengine::DisplayColorProfileCreationArgs&) override;
     void createRenderSurface(const compositionengine::RenderSurfaceCreationArgs&) override;
     void createClientCompositionCache(uint32_t cacheSize) override;
-    void applyDisplayBrightness(const bool applyImmediately) override;
+    void applyDisplayBrightness(bool applyImmediately) override;
     void setSecure(bool secure) override;
 
     // Internal helpers used by chooseCompositionStrategy()
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index d907bf5..6428d08 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -204,13 +204,12 @@
     setReleasedLayers(std::move(releasedLayers));
 }
 
-void Display::applyDisplayBrightness(const bool applyImmediately) {
-    auto& hwc = getCompositionEngine().getHwComposer();
-    const auto halDisplayId = HalDisplayId::tryCast(*getDisplayId());
-    if (const auto physicalDisplayId = PhysicalDisplayId::tryCast(*halDisplayId);
-        physicalDisplayId && getState().displayBrightness) {
+void Display::applyDisplayBrightness(bool applyImmediately) {
+    if (const auto displayId = ftl::Optional(getDisplayId()).and_then(PhysicalDisplayId::tryCast);
+        displayId && getState().displayBrightness) {
+        auto& hwc = getCompositionEngine().getHwComposer();
         const status_t result =
-                hwc.setDisplayBrightness(*physicalDisplayId, *getState().displayBrightness,
+                hwc.setDisplayBrightness(*displayId, *getState().displayBrightness,
                                          getState().displayBrightnessNits,
                                          Hwc2::Composer::DisplayBrightnessOptions{
                                                  .applyImmediately = applyImmediately})
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 9e35717..575a30e 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -97,13 +97,13 @@
     MOCK_CONST_METHOD1(isConnected, bool(PhysicalDisplayId));
     MOCK_CONST_METHOD2(getModes,
                        std::vector<HWComposer::HWCDisplayMode>(PhysicalDisplayId, int32_t));
-    MOCK_CONST_METHOD1(getActiveMode, std::optional<hal::HWConfigId>(PhysicalDisplayId));
+    MOCK_CONST_METHOD1(getActiveMode, ftl::Expected<hal::HWConfigId, status_t>(PhysicalDisplayId));
     MOCK_CONST_METHOD1(getColorModes, std::vector<ui::ColorMode>(PhysicalDisplayId));
     MOCK_METHOD3(setActiveColorMode, status_t(PhysicalDisplayId, ui::ColorMode, ui::RenderIntent));
     MOCK_CONST_METHOD0(isUsingVrComposer, bool());
     MOCK_CONST_METHOD1(getDisplayConnectionType, ui::DisplayConnectionType(PhysicalDisplayId));
     MOCK_CONST_METHOD1(isVsyncPeriodSwitchSupported, bool(PhysicalDisplayId));
-    MOCK_CONST_METHOD2(getDisplayVsyncPeriod, status_t(PhysicalDisplayId, nsecs_t*));
+    MOCK_CONST_METHOD1(getDisplayVsyncPeriod, ftl::Expected<nsecs_t, status_t>(PhysicalDisplayId));
     MOCK_METHOD4(setActiveModeWithConstraints,
                  status_t(PhysicalDisplayId, hal::HWConfigId,
                           const hal::VsyncPeriodChangeConstraints&,
diff --git a/services/surfaceflinger/Display/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h
index d07cdf5..c0e77bb 100644
--- a/services/surfaceflinger/Display/DisplayModeRequest.h
+++ b/services/surfaceflinger/Display/DisplayModeRequest.h
@@ -27,6 +27,9 @@
 
     // Whether to emit DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE.
     bool emitEvent = false;
+
+    // Whether to force the request to be applied, even if the mode is unchanged.
+    bool force = false;
 };
 
 inline bool operator==(const DisplayModeRequest& lhs, const DisplayModeRequest& rhs) {
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 799d62c..45f08a4 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -24,6 +24,7 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <common/FlagManager.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/DisplayColorProfile.h>
@@ -67,6 +68,7 @@
         mActiveModeFpsTrace(concatId("ActiveModeFps")),
         mRenderRateFpsTrace(concatId("RenderRateFps")),
         mPhysicalOrientation(args.physicalOrientation),
+        mPowerMode(ftl::Concat("PowerMode ", getId().value).c_str(), args.initialPowerMode),
         mIsPrimary(args.isPrimary),
         mRequestedRefreshRate(args.requestedRefreshRate),
         mRefreshRateSelector(std::move(args.refreshRateSelector)),
@@ -105,9 +107,7 @@
 
     mCompositionDisplay->getRenderSurface()->initialize();
 
-    if (const auto powerModeOpt = args.initialPowerMode) {
-        setPowerMode(*powerModeOpt);
-    }
+    setPowerMode(args.initialPowerMode);
 
     // initialize the display orientation transform.
     setProjection(ui::ROTATION_0, Rect::INVALID_RECT, Rect::INVALID_RECT);
@@ -172,6 +172,7 @@
 }
 
 void DisplayDevice::setPowerMode(hal::PowerMode mode) {
+    // TODO(b/241285876): Skip this for virtual displays.
     if (mode == hal::PowerMode::OFF || mode == hal::PowerMode::ON) {
         if (mStagedBrightness && mBrightness != mStagedBrightness) {
             getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
@@ -181,33 +182,26 @@
         getCompositionDisplay()->applyDisplayBrightness(true);
     }
 
-    if (mPowerMode) {
-        *mPowerMode = mode;
-    } else {
-        mPowerMode.emplace("PowerMode -" + to_string(getId()), mode);
-    }
+    mPowerMode = mode;
 
     getCompositionDisplay()->setCompositionEnabled(isPoweredOn());
 }
 
 void DisplayDevice::tracePowerMode() {
-    // assign the same value for tracing
-    if (mPowerMode) {
-        const hal::PowerMode powerMode = *mPowerMode;
-        *mPowerMode = powerMode;
-    }
+    // Assign the same value for tracing.
+    mPowerMode = mPowerMode.get();
 }
 
 void DisplayDevice::enableLayerCaching(bool enable) {
     getCompositionDisplay()->setLayerCachingEnabled(enable);
 }
 
-std::optional<hal::PowerMode> DisplayDevice::getPowerMode() const {
+hal::PowerMode DisplayDevice::getPowerMode() const {
     return mPowerMode;
 }
 
 bool DisplayDevice::isPoweredOn() const {
-    return mPowerMode && *mPowerMode != hal::PowerMode::OFF;
+    return mPowerMode != hal::PowerMode::OFF;
 }
 
 void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
@@ -221,6 +215,17 @@
 bool DisplayDevice::initiateModeChange(display::DisplayModeRequest&& desiredMode,
                                        const hal::VsyncPeriodChangeConstraints& constraints,
                                        hal::VsyncPeriodChangeTimeline& outTimeline) {
+    // TODO(b/255635711): Flow the DisplayModeRequest through the desired/pending/active states. For
+    // now, `desiredMode` and `mDesiredModeOpt` are one and the same, but the latter is not cleared
+    // until the next `SF::initiateDisplayModeChanges`. However, the desired mode has been consumed
+    // at this point, so clear the `force` flag to prevent an endless loop of `initiateModeChange`.
+    if (FlagManager::getInstance().connected_display()) {
+        std::scoped_lock lock(mDesiredModeLock);
+        if (mDesiredModeOpt) {
+            mDesiredModeOpt->force = false;
+        }
+    }
+
     mPendingModeOpt = std::move(desiredMode);
     mIsModeSetPending = true;
 
@@ -246,10 +251,8 @@
         return 0;
     }
 
-    nsecs_t vsyncPeriod;
-    const auto status = mHwComposer.getDisplayVsyncPeriod(physicalId, &vsyncPeriod);
-    if (status == NO_ERROR) {
-        return vsyncPeriod;
+    if (const auto vsyncPeriodOpt = mHwComposer.getDisplayVsyncPeriod(physicalId).value_opt()) {
+        return *vsyncPeriodOpt;
     }
 
     return refreshRateSelector().getActiveMode().modePtr->getVsyncRate().getPeriodNsecs();
@@ -526,8 +529,7 @@
     }
 }
 
-auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode, bool force)
-        -> DesiredModeAction {
+auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode) -> DesiredModeAction {
     ATRACE_CALL();
 
     const auto& desiredModePtr = desiredMode.mode.modePtr;
@@ -535,20 +537,26 @@
     LOG_ALWAYS_FATAL_IF(getPhysicalId() != desiredModePtr->getPhysicalDisplayId(),
                         "DisplayId mismatch");
 
-    ALOGV("%s(%s)", __func__, to_string(*desiredModePtr).c_str());
+    // TODO (b/318533819): Stringize DisplayModeRequest.
+    ALOGD("%s(%s, force=%s)", __func__, to_string(*desiredModePtr).c_str(),
+          desiredMode.force ? "true" : "false");
 
     std::scoped_lock lock(mDesiredModeLock);
     if (mDesiredModeOpt) {
         // A mode transition was already scheduled, so just override the desired mode.
         const bool emitEvent = mDesiredModeOpt->emitEvent;
+        const bool force = mDesiredModeOpt->force;
         mDesiredModeOpt = std::move(desiredMode);
         mDesiredModeOpt->emitEvent |= emitEvent;
+        if (FlagManager::getInstance().connected_display()) {
+            mDesiredModeOpt->force |= force;
+        }
         return DesiredModeAction::None;
     }
 
     // If the desired mode is already active...
     const auto activeMode = refreshRateSelector().getActiveMode();
-    if (!force && activeMode.modePtr->getId() == desiredModePtr->getId()) {
+    if (!desiredMode.force && activeMode.modePtr->getId() == desiredModePtr->getId()) {
         if (activeMode == desiredMode.mode) {
             return DesiredModeAction::None;
         }
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 97b56a2..edd57cc 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -173,8 +173,8 @@
     /* ------------------------------------------------------------------------
      * Display power mode management.
      */
-    std::optional<hardware::graphics::composer::hal::PowerMode> getPowerMode() const;
-    void setPowerMode(hardware::graphics::composer::hal::PowerMode mode);
+    hardware::graphics::composer::hal::PowerMode getPowerMode() const;
+    void setPowerMode(hardware::graphics::composer::hal::PowerMode);
     bool isPoweredOn() const;
     void tracePowerMode();
 
@@ -189,8 +189,7 @@
 
     enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch };
 
-    DesiredModeAction setDesiredMode(display::DisplayModeRequest&&, bool force = false)
-            EXCLUDES(mDesiredModeLock);
+    DesiredModeAction setDesiredMode(display::DisplayModeRequest&&) EXCLUDES(mDesiredModeLock);
 
     using DisplayModeRequestOpt = ftl::Optional<display::DisplayModeRequest>;
 
@@ -271,9 +270,7 @@
     ui::Rotation mOrientation = ui::ROTATION_0;
     bool mIsOrientationChanged = false;
 
-    // Allow nullopt as initial power mode.
-    using TracedPowerMode = TracedOrdinal<hardware::graphics::composer::hal::PowerMode>;
-    std::optional<TracedPowerMode> mPowerMode;
+    TracedOrdinal<hardware::graphics::composer::hal::PowerMode> mPowerMode;
 
     std::optional<float> mStagedBrightness;
     std::optional<float> mBrightness;
@@ -363,7 +360,8 @@
     HdrCapabilities hdrCapabilities;
     int32_t supportedPerFrameMetadata{0};
     std::unordered_map<ui::ColorMode, std::vector<ui::RenderIntent>> hwcColorModes;
-    std::optional<hardware::graphics::composer::hal::PowerMode> initialPowerMode;
+    hardware::graphics::composer::hal::PowerMode initialPowerMode{
+            hardware::graphics::composer::hal::PowerMode::OFF};
     bool isPrimary{false};
     DisplayModeId activeModeId;
     // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 64a8ae7..362ab9c 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -330,7 +330,11 @@
 
     t.join();
     close(pipefds[0]);
-    return str;
+
+    std::string hash;
+    mAidlComposer->getInterfaceHash(&hash);
+    return std::string(mAidlComposer->descriptor) +
+            " version:" + std::to_string(mComposerInterfaceVersion) + " hash:" + hash + str;
 }
 
 void AidlComposer::registerCallback(HWC2::ComposerCallback& callback) {
@@ -339,7 +343,9 @@
     }
 
     mAidlComposerCallback = ndk::SharedRefBase::make<AidlIComposerCallbackWrapper>(callback);
-    AIBinder_setMinSchedulerPolicy(mAidlComposerCallback->asBinder().get(), SCHED_FIFO, 2);
+
+    ndk::SpAIBinder binder = mAidlComposerCallback->asBinder();
+    AIBinder_setMinSchedulerPolicy(binder.get(), SCHED_FIFO, 2);
 
     const auto status = mAidlComposerClient->registerCallback(mAidlComposerCallback);
     if (!status.isOk()) {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 704ece5..db66f5b 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -27,6 +27,7 @@
 #include "HWC2.h"
 
 #include <android/configuration.h>
+#include <common/FlagManager.h>
 #include <ui/Fence.h>
 #include <ui/FloatRect.h>
 #include <ui/GraphicBuffer.h>
@@ -416,7 +417,19 @@
                                               VsyncPeriodChangeTimeline* outTimeline) {
     ALOGV("[%" PRIu64 "] setActiveConfigWithConstraints", mId);
 
-    if (isVsyncPeriodSwitchSupported()) {
+    // FIXME (b/319505580): At least the first config set on an external display must be
+    // `setActiveConfig`, so skip over the block that calls `setActiveConfigWithConstraints`
+    // for simplicity.
+    ui::DisplayConnectionType type = ui::DisplayConnectionType::Internal;
+    const bool connected_display = FlagManager::getInstance().connected_display();
+    if (connected_display) {
+        if (auto err = getConnectionType(&type); err != Error::NONE) {
+            return err;
+        }
+    }
+
+    if (isVsyncPeriodSwitchSupported() &&
+        (!connected_display || type != ui::DisplayConnectionType::External)) {
         Hwc2::IComposerClient::VsyncPeriodChangeConstraints hwc2Constraints;
         hwc2Constraints.desiredTimeNanos = constraints.desiredTimeNanos;
         hwc2Constraints.seamlessRequired = constraints.seamlessRequired;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index cf1c3c1..bac24c7 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -343,14 +343,18 @@
     return modes;
 }
 
-std::optional<hal::HWConfigId> HWComposer::getActiveMode(PhysicalDisplayId displayId) const {
-    RETURN_IF_INVALID_DISPLAY(displayId, std::nullopt);
+ftl::Expected<hal::HWConfigId, status_t> HWComposer::getActiveMode(
+        PhysicalDisplayId displayId) const {
+    RETURN_IF_INVALID_DISPLAY(displayId, ftl::Unexpected(BAD_INDEX));
     const auto hwcId = *fromPhysicalDisplayId(displayId);
 
     hal::HWConfigId configId;
     const auto error = static_cast<hal::Error>(mComposer->getActiveConfig(hwcId, &configId));
+    if (error == hal::Error::BAD_CONFIG) {
+        return ftl::Unexpected(NO_INIT);
+    }
 
-    RETURN_IF_HWC_ERROR_FOR("getActiveConfig", error, displayId, std::nullopt);
+    RETURN_IF_HWC_ERROR_FOR("getActiveConfig", error, displayId, ftl::Unexpected(UNKNOWN_ERROR));
     return configId;
 }
 
@@ -376,20 +380,20 @@
     return mDisplayData.at(displayId).hwcDisplay->isVsyncPeriodSwitchSupported();
 }
 
-status_t HWComposer::getDisplayVsyncPeriod(PhysicalDisplayId displayId,
-                                           nsecs_t* outVsyncPeriod) const {
-    RETURN_IF_INVALID_DISPLAY(displayId, 0);
+ftl::Expected<nsecs_t, status_t> HWComposer::getDisplayVsyncPeriod(
+        PhysicalDisplayId displayId) const {
+    RETURN_IF_INVALID_DISPLAY(displayId, ftl::Unexpected(BAD_INDEX));
 
     if (!isVsyncPeriodSwitchSupported(displayId)) {
-        return INVALID_OPERATION;
+        return ftl::Unexpected(INVALID_OPERATION);
     }
+
     const auto hwcId = *fromPhysicalDisplayId(displayId);
     Hwc2::VsyncPeriodNanos vsyncPeriodNanos = 0;
-    auto error =
+    const auto error =
             static_cast<hal::Error>(mComposer->getDisplayVsyncPeriod(hwcId, &vsyncPeriodNanos));
-    RETURN_IF_HWC_ERROR(error, displayId, 0);
-    *outVsyncPeriod = static_cast<nsecs_t>(vsyncPeriodNanos);
-    return NO_ERROR;
+    RETURN_IF_HWC_ERROR(error, displayId, ftl::Unexpected(UNKNOWN_ERROR));
+    return static_cast<nsecs_t>(vsyncPeriodNanos);
 }
 
 std::vector<ui::ColorMode> HWComposer::getColorModes(PhysicalDisplayId displayId) const {
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index fb32ff4..7fbbb1a 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -25,6 +25,7 @@
 #include <vector>
 
 #include <android-base/thread_annotations.h>
+#include <ftl/expected.h>
 #include <ftl/future.h>
 #include <ui/DisplayIdentification.h>
 #include <ui/FenceTime.h>
@@ -237,7 +238,7 @@
     virtual std::vector<HWCDisplayMode> getModes(PhysicalDisplayId,
                                                  int32_t maxFrameIntervalNs) const = 0;
 
-    virtual std::optional<hal::HWConfigId> getActiveMode(PhysicalDisplayId) const = 0;
+    virtual ftl::Expected<hal::HWConfigId, status_t> getActiveMode(PhysicalDisplayId) const = 0;
 
     virtual std::vector<ui::ColorMode> getColorModes(PhysicalDisplayId) const = 0;
 
@@ -247,8 +248,7 @@
     // Composer 2.4
     virtual ui::DisplayConnectionType getDisplayConnectionType(PhysicalDisplayId) const = 0;
     virtual bool isVsyncPeriodSwitchSupported(PhysicalDisplayId) const = 0;
-    virtual status_t getDisplayVsyncPeriod(PhysicalDisplayId displayId,
-                                           nsecs_t* outVsyncPeriod) const = 0;
+    virtual ftl::Expected<nsecs_t, status_t> getDisplayVsyncPeriod(PhysicalDisplayId) const = 0;
     virtual status_t setActiveModeWithConstraints(PhysicalDisplayId, hal::HWConfigId,
                                                   const hal::VsyncPeriodChangeConstraints&,
                                                   hal::VsyncPeriodChangeTimeline* outTimeline) = 0;
@@ -424,7 +424,7 @@
     std::vector<HWCDisplayMode> getModes(PhysicalDisplayId,
                                          int32_t maxFrameIntervalNs) const override;
 
-    std::optional<hal::HWConfigId> getActiveMode(PhysicalDisplayId) const override;
+    ftl::Expected<hal::HWConfigId, status_t> getActiveMode(PhysicalDisplayId) const override;
 
     std::vector<ui::ColorMode> getColorModes(PhysicalDisplayId) const override;
 
@@ -435,8 +435,7 @@
     // Composer 2.4
     ui::DisplayConnectionType getDisplayConnectionType(PhysicalDisplayId) const override;
     bool isVsyncPeriodSwitchSupported(PhysicalDisplayId) const override;
-    status_t getDisplayVsyncPeriod(PhysicalDisplayId displayId,
-                                   nsecs_t* outVsyncPeriod) const override;
+    ftl::Expected<nsecs_t, status_t> getDisplayVsyncPeriod(PhysicalDisplayId) const override;
     status_t setActiveModeWithConstraints(PhysicalDisplayId, hal::HWConfigId,
                                           const hal::VsyncPeriodChangeConstraints&,
                                           hal::VsyncPeriodChangeTimeline* outTimeline) override;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index c4ff9cc..12ab2c2 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -291,6 +291,7 @@
 
 std::string HidlComposer::dumpDebugInfo() {
     std::string info;
+    info += std::string(mComposer->descriptor) + "\n";
     mComposer->dumpDebugInfo([&](const auto& tmpInfo) { info = tmpInfo.c_str(); });
 
     return info;
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index a0c943b..ee6d37b 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -48,6 +48,7 @@
 using aidl::android::hardware::power::Boost;
 using aidl::android::hardware::power::Mode;
 using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::SessionTag;
 using aidl::android::hardware::power::WorkDuration;
 
 PowerAdvisor::~PowerAdvisor() = default;
@@ -206,9 +207,12 @@
 
 bool PowerAdvisor::ensurePowerHintSessionRunning() {
     if (mHintSession == nullptr && !mHintSessionThreadIds.empty() && usePowerHintSession()) {
-        auto ret = getPowerHal().createHintSession(getpid(), static_cast<int32_t>(getuid()),
-                                                   mHintSessionThreadIds, mTargetDuration.ns());
-
+        auto ret =
+                getPowerHal().createHintSessionWithConfig(getpid(), static_cast<int32_t>(getuid()),
+                                                          mHintSessionThreadIds,
+                                                          mTargetDuration.ns(),
+                                                          SessionTag::SURFACEFLINGER,
+                                                          &mSessionConfig);
         if (ret.isOk()) {
             mHintSession = ret.value();
         }
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index bbe51cc0..d6ffb2a 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -292,6 +292,9 @@
     // Whether we should send reportActualWorkDuration calls
     static const bool sUseReportActualDuration;
 
+    // Metadata about the session returned from PowerHAL
+    aidl::android::hardware::power::SessionConfig mSessionConfig;
+
     // How long we expect hwc to run after the present call until it waits for the fence
     static constexpr const Duration kFenceWaitStartDelayValidated{150us};
     static constexpr const Duration kFenceWaitStartDelaySkippedValidate{250us};
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 38974a2..3ef9e69 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -315,6 +315,7 @@
     if (obj.hasInputInfo()) {
         out << "\n    input{"
             << "(" << obj.inputInfo.inputConfig.string() << ")";
+        if (obj.inputInfo.canOccludePresentation) out << " canOccludePresentation";
         if (obj.touchCropId != UNASSIGNED_LAYER_ID) out << " touchCropId=" << obj.touchCropId;
         if (obj.inputInfo.replaceTouchableRegionWithCrop) out << " replaceTouchableRegionWithCrop";
         auto touchableRegion = obj.inputInfo.touchableRegion.getBounds();
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 8df5d8c..0966fe0 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -1044,6 +1044,8 @@
     snapshot.inputInfo.touchOcclusionMode = requested.hasInputInfo()
             ? requested.windowInfoHandle->getInfo()->touchOcclusionMode
             : parentSnapshot.inputInfo.touchOcclusionMode;
+    snapshot.inputInfo.canOccludePresentation = parentSnapshot.inputInfo.canOccludePresentation ||
+            (requested.flags & layer_state_t::eCanOccludePresentation);
     if (requested.dropInputMode == gui::DropInputMode::ALL ||
         parentSnapshot.dropInputMode == gui::DropInputMode::ALL) {
         snapshot.dropInputMode = gui::DropInputMode::ALL;
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 209df79..2cf4c1b 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -170,6 +170,9 @@
         if ((oldFlags ^ flags) & layer_state_t::eIgnoreDestinationFrame) {
             changes |= RequestedLayerState::Changes::Geometry;
         }
+        if ((oldFlags ^ flags) & layer_state_t::eCanOccludePresentation) {
+            changes |= RequestedLayerState::Changes::Input;
+        }
     }
 
     if (clientState.what & layer_state_t::eBufferChanged) {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index c8b1059..219a8e2 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -78,11 +78,13 @@
 #include "SurfaceFlinger.h"
 #include "TimeStats/TimeStats.h"
 #include "TunnelModeEnabledReporter.h"
+#include "Utils/FenceUtils.h"
 
 #define DEBUG_RESIZE 0
 #define EARLY_RELEASE_ENABLED false
 
 namespace android {
+using namespace std::chrono_literals;
 namespace {
 constexpr int kDumpTableRowLength = 159;
 
@@ -2911,7 +2913,8 @@
 }
 
 void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult,
-                             ui::LayerStack layerStack) {
+                             ui::LayerStack layerStack,
+                             std::function<FenceResult(FenceResult)>&& continuation) {
     // If we are displayed on multiple displays in a single composition cycle then we would
     // need to do careful tracking to enable the use of the mLastClientCompositionFence.
     //  For example we can only use it if all the displays are client comp, and we need
@@ -2946,11 +2949,41 @@
             break;
         }
     }
+
+    if (!FlagManager::getInstance().screenshot_fence_preservation() && continuation) {
+        futureFenceResult = ftl::Future(futureFenceResult).then(std::move(continuation)).share();
+    }
+
     if (ch != nullptr) {
         ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
         ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
         ch->name = mName;
+    } else if (FlagManager::getInstance().screenshot_fence_preservation()) {
+        // If we didn't get a release callback yet, e.g. some scenarios when capturing screenshots
+        // asynchronously, then make sure we don't drop the fence.
+        mAdditionalPreviousReleaseFences.emplace_back(std::move(futureFenceResult),
+                                                      std::move(continuation));
+        std::vector<FenceAndContinuation> mergedFences;
+        sp<Fence> prevFence = nullptr;
+        // For a layer that's frequently screenshotted, try to merge fences to make sure we don't
+        // grow unbounded.
+        for (const auto& futureAndContinution : mAdditionalPreviousReleaseFences) {
+            auto result = futureAndContinution.future.wait_for(0s);
+            if (result != std::future_status::ready) {
+                mergedFences.emplace_back(futureAndContinution);
+                continue;
+            }
+
+            mergeFence(getDebugName(), futureAndContinution.chain().get().value_or(Fence::NO_FENCE),
+                       prevFence);
+        }
+        if (prevFence != nullptr) {
+            mergedFences.emplace_back(ftl::yield(FenceResult(std::move(prevFence))).share());
+        }
+
+        mAdditionalPreviousReleaseFences.swap(mergedFences);
     }
+
     if (mBufferInfo.mBuffer) {
         mPreviouslyPresentedLayerStacks.push_back(layerStack);
     }
@@ -3440,6 +3473,15 @@
             handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence;
             handle->frameNumber = mDrawingState.frameNumber;
             handle->previousFrameNumber = mDrawingState.previousFrameNumber;
+            if (FlagManager::getInstance().screenshot_fence_preservation() &&
+                mPreviousReleaseBufferEndpoint == handle->listener) {
+                // Add fences from previous screenshots now so that they can be dispatched to the
+                // client.
+                for (const auto& futureAndContinution : mAdditionalPreviousReleaseFences) {
+                    handle->previousReleaseFences.emplace_back(futureAndContinution.chain());
+                }
+                mAdditionalPreviousReleaseFences.clear();
+            }
 
             // Store so latched time and release fence can be set
             mDrawingState.callbackHandles.push_back(handle);
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index c772e0e..dfd57c6 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -555,7 +555,8 @@
     const compositionengine::LayerFECompositionState* getCompositionState() const;
     bool fenceHasSignaled() const;
     void onPreComposition(nsecs_t refreshStartTime);
-    void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack);
+    void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack,
+                          std::function<FenceResult(FenceResult)>&& continuation = nullptr);
 
     void setWasClientComposed(const sp<Fence>& fence) {
         mLastClientCompositionFence = fence;
@@ -932,6 +933,19 @@
     // the release fences from the correct displays when we release the last buffer
     // from the layer.
     std::vector<ui::LayerStack> mPreviouslyPresentedLayerStacks;
+    struct FenceAndContinuation {
+        ftl::SharedFuture<FenceResult> future;
+        std::function<FenceResult(FenceResult)> continuation;
+
+        ftl::SharedFuture<FenceResult> chain() const {
+            if (continuation) {
+                return ftl::Future(future).then(continuation).share();
+            } else {
+                return future;
+            }
+        }
+    };
+    std::vector<FenceAndContinuation> mAdditionalPreviousReleaseFences;
     // Exposed so SurfaceFlinger can assert that it's held
     const sp<SurfaceFlinger> mFlinger;
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index c80c8fd..ccd1c0f 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -404,6 +404,9 @@
     }();
     generateFrameTimeline(vsyncEventData, frameInterval.ns(), systemTime(SYSTEM_TIME_MONOTONIC),
                           presentTime, deadline);
+    if (FlagManager::getInstance().vrr_config()) {
+        mCallback.onExpectedPresentTimePosted(TimePoint::fromNs(presentTime));
+    }
     return vsyncEventData;
 }
 
@@ -721,6 +724,11 @@
                 removeDisplayEventConnectionLocked(consumer);
         }
     }
+    if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC &&
+        FlagManager::getInstance().vrr_config()) {
+        mCallback.onExpectedPresentTimePosted(
+                TimePoint::fromNs(event.vsync.vsyncData.preferredExpectedPresentationTime()));
+    }
 }
 
 void EventThread::dump(std::string& result) const {
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 8970103..90e61a9 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -142,6 +142,7 @@
     virtual bool throttleVsync(TimePoint, uid_t) = 0;
     virtual Period getVsyncPeriod(uid_t) = 0;
     virtual void resync() = 0;
+    virtual void onExpectedPresentTimePosted(TimePoint) = 0;
 };
 
 namespace impl {
diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
index 3b61de7..9f4f5b6 100644
--- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h
+++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
@@ -30,6 +30,8 @@
     virtual void kernelTimerChanged(bool expired) = 0;
     virtual void triggerOnFrameRateOverridesChanged() = 0;
     virtual void onChoreographerAttached() = 0;
+    virtual void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>,
+                                             Fps renderRate) = 0;
 
 protected:
     ~ISchedulerCallback() = default;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index c3709e5..7614453 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -843,9 +843,13 @@
     const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
     using fps_approx_ops::operator<;
 
+    // A method for UI Toolkit to send the touch signal via "HighHint" category vote,
+    // which will touch boost when there are no ExplicitDefault layer votes. This is an
+    // incomplete solution but accounts for cases such as games that use `setFrameRate` with default
+    // compatibility to limit the frame rate, which should not have touch boost.
     const bool hasInteraction = signals.touch || interactiveLayers > 0;
-    if (hasInteraction && explicitDefaultVoteLayers == 0 && explicitCategoryVoteLayers == 0 &&
-        touchBoostForExplicitExact &&
+
+    if (hasInteraction && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
         scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
         ALOGV("Touch Boost");
         ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index fff97f7..6979f03 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -71,15 +71,13 @@
 namespace android::scheduler {
 
 Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features,
-                     surfaceflinger::Factory& factory, Fps activeRefreshRate, TimeStats& timeStats,
-                     IVsyncTrackerCallback& vsyncTrackerCallback)
+                     surfaceflinger::Factory& factory, Fps activeRefreshRate, TimeStats& timeStats)
       : android::impl::MessageQueue(compositor),
         mFeatures(features),
         mVsyncConfiguration(factory.createVsyncConfiguration(activeRefreshRate)),
         mVsyncModulator(sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs())),
         mRefreshRateStats(std::make_unique<RefreshRateStats>(timeStats, activeRefreshRate)),
-        mSchedulerCallback(callback),
-        mVsyncTrackerCallback(vsyncTrackerCallback) {}
+        mSchedulerCallback(callback) {}
 
 Scheduler::~Scheduler() {
     // MessageQueue depends on VsyncSchedule, so first destroy it.
@@ -134,10 +132,11 @@
 }
 
 void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
-    auto schedulePtr = std::make_shared<VsyncSchedule>(
-            selectorPtr->getActiveMode().modePtr, mFeatures,
-            [this](PhysicalDisplayId id, bool enable) { onHardwareVsyncRequest(id, enable); },
-            mVsyncTrackerCallback);
+    auto schedulePtr =
+            std::make_shared<VsyncSchedule>(selectorPtr->getActiveMode().modePtr, mFeatures,
+                                            [this](PhysicalDisplayId id, bool enable) {
+                                                onHardwareVsyncRequest(id, enable);
+                                            });
 
     registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr));
 }
@@ -222,7 +221,12 @@
             targets.try_emplace(id, &targeter.target());
         }
 
-        if (!compositor.commit(pacesetterPtr->displayId, targets)) return;
+        if (!compositor.commit(pacesetterPtr->displayId, targets)) {
+            if (FlagManager::getInstance().vrr_config()) {
+                compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId);
+            }
+            return;
+        }
     }
 
     // The pacesetter may have changed or been registered anew during commit.
@@ -263,6 +267,9 @@
     }
 
     const auto resultsPerDisplay = compositor.composite(pacesetterPtr->displayId, targeters);
+    if (FlagManager::getInstance().vrr_config()) {
+        compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId);
+    }
     compositor.sample();
 
     for (const auto& [id, targeter] : targeters) {
@@ -323,6 +330,19 @@
     // behaviour.
     return Period::fromNs(currentPeriod.ns() * divisor);
 }
+void Scheduler::onExpectedPresentTimePosted(TimePoint expectedPresentTime) {
+    const auto frameRateMode = [this] {
+        std::scoped_lock lock(mDisplayLock);
+        const auto pacesetterOpt = pacesetterDisplayLocked();
+        const Display& pacesetter = *pacesetterOpt;
+        return pacesetter.selectorPtr->getActiveMode();
+    }();
+
+    if (frameRateMode.modePtr->getVrrConfig()) {
+        mSchedulerCallback.onExpectedPresentTimePosted(expectedPresentTime, frameRateMode.modePtr,
+                                                       frameRateMode.fps);
+    }
+}
 
 ConnectionHandle Scheduler::createEventThread(Cycle cycle,
                                               frametimeline::TokenManager* tokenManager,
@@ -646,13 +666,13 @@
         return Fps{};
     }
     const Display& display = *displayOpt;
-    const nsecs_t threshold =
-            display.selectorPtr->getActiveMode().modePtr->getVsyncRate().getPeriodNsecs() / 2;
-    const nsecs_t nextVsyncTime =
-            display.schedulePtr->getTracker()
-                    .nextAnticipatedVSyncTimeFrom(currentExpectedPresentTime.ns() + threshold,
-                                                  currentExpectedPresentTime.ns());
-    return Fps::fromPeriodNsecs(nextVsyncTime - currentExpectedPresentTime.ns());
+    const Duration threshold =
+            display.selectorPtr->getActiveMode().modePtr->getVsyncRate().getPeriod() / 2;
+    const TimePoint nextVsyncTime =
+            display.schedulePtr->vsyncDeadlineAfter(currentExpectedPresentTime + threshold,
+                                                    currentExpectedPresentTime);
+    const Duration frameInterval = nextVsyncTime - currentExpectedPresentTime;
+    return Fps::fromPeriodNsecs(frameInterval.ns());
 }
 
 void Scheduler::resync() {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 9912622..9f29e9f 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -111,7 +111,7 @@
 
 public:
     Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, surfaceflinger::Factory&,
-              Fps activeRefreshRate, TimeStats&, IVsyncTrackerCallback&);
+              Fps activeRefreshRate, TimeStats&);
     virtual ~Scheduler();
 
     void startTimers();
@@ -458,6 +458,7 @@
     bool throttleVsync(TimePoint, uid_t) override;
     Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
     void resync() override EXCLUDES(mDisplayLock);
+    void onExpectedPresentTimePosted(TimePoint expectedPresentTime) override EXCLUDES(mDisplayLock);
 
     // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
     struct Connection {
@@ -497,8 +498,6 @@
 
     ISchedulerCallback& mSchedulerCallback;
 
-    IVsyncTrackerCallback& mVsyncTrackerCallback;
-
     // mDisplayLock may be locked while under mPolicyLock.
     mutable std::mutex mPolicyLock;
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 6e12b33..8697696 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -48,14 +48,12 @@
 VSyncPredictor::~VSyncPredictor() = default;
 
 VSyncPredictor::VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
-                               size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
-                               IVsyncTrackerCallback& callback)
+                               size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
       : mId(modePtr->getPhysicalDisplayId()),
         mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
         kHistorySize(historySize),
         kMinimumSamplesForPrediction(minimumSamplesForPrediction),
         kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
-        mVsyncTrackerCallback(callback),
         mDisplayModePtr(modePtr) {
     resetModel();
 }
@@ -312,13 +310,7 @@
             FlagManager::getInstance().vrr_config() && !lastFrameMissed && lastVsyncOpt
             ? std::max(timePoint, *lastVsyncOpt + minFramePeriod - threshold)
             : timePoint;
-    const auto vsyncTime = snapToVsyncAlignedWithRenderRate(baseTime);
-    if (FlagManager::getInstance().vrr_config() && mDisplayModePtr->getVrrConfig()) {
-        const auto vsyncTimePoint = TimePoint::fromNs(vsyncTime);
-        const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
-        mVsyncTrackerCallback.onVsyncGenerated(vsyncTimePoint, mDisplayModePtr, renderRate);
-    }
-    return vsyncTime;
+    return snapToVsyncAlignedWithRenderRate(baseTime);
 }
 
 nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const {
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 9191003..8fd7e60 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -37,11 +37,9 @@
      * \param [in] minimumSamplesForPrediction The minimum number of samples to collect before
      * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter
      * samples that fall outlierTolerancePercent from an anticipated vsync event.
-     * \param [in] IVsyncTrackerCallback The callback for the VSyncTracker.
      */
     VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
-                   size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
-                   IVsyncTrackerCallback&);
+                   size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent);
     ~VSyncPredictor();
 
     bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
@@ -106,7 +104,6 @@
     size_t const kHistorySize;
     size_t const kMinimumSamplesForPrediction;
     size_t const kOutlierTolerancePercent;
-    IVsyncTrackerCallback& mVsyncTrackerCallback;
     std::mutex mutable mMutex;
 
     std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex);
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 417163f..37bd4b4 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -26,12 +26,6 @@
 
 namespace android::scheduler {
 
-struct IVsyncTrackerCallback {
-    virtual ~IVsyncTrackerCallback() = default;
-    virtual void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
-                                  Fps renderRate) = 0;
-};
-
 /*
  * VSyncTracker is an interface for providing estimates on future Vsync signal times based on
  * historical vsync timing data.
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index db6a187..001938c 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -57,11 +57,10 @@
 };
 
 VsyncSchedule::VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags features,
-                             RequestHardwareVsync requestHardwareVsync,
-                             IVsyncTrackerCallback& callback)
+                             RequestHardwareVsync requestHardwareVsync)
       : mId(modePtr->getPhysicalDisplayId()),
         mRequestHardwareVsync(std::move(requestHardwareVsync)),
-        mTracker(createTracker(modePtr, callback)),
+        mTracker(createTracker(modePtr)),
         mDispatch(createDispatch(mTracker)),
         mController(createController(modePtr->getPhysicalDisplayId(), *mTracker, features)),
         mTracer(features.test(Feature::kTracePredictedVsync)
@@ -89,8 +88,12 @@
     return period();
 }
 
-TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint) const {
-    return TimePoint::fromNs(mTracker->nextAnticipatedVSyncTimeFrom(timePoint.ns()));
+TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint,
+                                            ftl::Optional<TimePoint> lastVsyncOpt) const {
+    return TimePoint::fromNs(
+            mTracker->nextAnticipatedVSyncTimeFrom(timePoint.ns(),
+                                                   lastVsyncOpt.transform(
+                                                           [](TimePoint t) { return t.ns(); })));
 }
 
 void VsyncSchedule::dump(std::string& out) const {
@@ -111,15 +114,14 @@
     mDispatch->dump(out);
 }
 
-VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull<DisplayModePtr> modePtr,
-                                                       IVsyncTrackerCallback& callback) {
+VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull<DisplayModePtr> modePtr) {
     // TODO(b/144707443): Tune constants.
     constexpr size_t kHistorySize = 20;
     constexpr size_t kMinSamplesForPrediction = 6;
     constexpr uint32_t kDiscardOutlierPercent = 20;
 
     return std::make_unique<VSyncPredictor>(modePtr, kHistorySize, kMinSamplesForPrediction,
-                                            kDiscardOutlierPercent, callback);
+                                            kDiscardOutlierPercent);
 }
 
 VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) {
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 722ea0b..85cd3e7 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -57,13 +57,13 @@
 public:
     using RequestHardwareVsync = std::function<void(PhysicalDisplayId, bool enabled)>;
 
-    VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags, RequestHardwareVsync,
-                  IVsyncTrackerCallback&);
+    VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags, RequestHardwareVsync);
     ~VsyncSchedule();
 
     // IVsyncSource overrides:
     Period period() const override;
-    TimePoint vsyncDeadlineAfter(TimePoint) const override;
+    TimePoint vsyncDeadlineAfter(TimePoint,
+                                 ftl::Optional<TimePoint> lastVsyncOpt = {}) const override;
     Period minFramePeriod() const override;
 
     // Inform the schedule that the display mode changed the schedule needs to recalibrate
@@ -127,7 +127,7 @@
     friend class android::VsyncScheduleTest;
     friend class android::fuzz::SchedulerFuzzer;
 
-    static TrackerPtr createTracker(ftl::NonNull<DisplayModePtr> modePtr, IVsyncTrackerCallback&);
+    static TrackerPtr createTracker(ftl::NonNull<DisplayModePtr> modePtr);
     static DispatchPtr createDispatch(TrackerPtr);
     static ControllerPtr createController(PhysicalDisplayId, VsyncTracker&, FeatureFlags);
 
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
index 0154060..f0f7a87 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
@@ -16,13 +16,14 @@
 
 #pragma once
 
+#include <ftl/optional.h>
 #include <scheduler/Time.h>
 
 namespace android::scheduler {
 
 struct IVsyncSource {
     virtual Period period() const = 0;
-    virtual TimePoint vsyncDeadlineAfter(TimePoint) const = 0;
+    virtual TimePoint vsyncDeadlineAfter(TimePoint, ftl::Optional<TimePoint> = {}) const = 0;
     virtual Period minFramePeriod() const = 0;
 
 protected:
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
index 12ee36e..8673a22 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
@@ -47,6 +47,9 @@
     virtual CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId,
                                                  const scheduler::FrameTargeters&) = 0;
 
+    // Sends a hint about the expected present time
+    virtual void sendNotifyExpectedPresentHint(PhysicalDisplayId) = 0;
+
     // Samples the composited frame via RegionSamplingThread.
     virtual void sample() = 0;
 
diff --git a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
index a9abcaf..29711af 100644
--- a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
+++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
@@ -38,7 +38,9 @@
     const TimePoint vsyncDeadline;
 
     Period period() const override { return vsyncPeriod; }
-    TimePoint vsyncDeadlineAfter(TimePoint) const override { return vsyncDeadline; }
+    TimePoint vsyncDeadlineAfter(TimePoint, ftl::Optional<TimePoint> = {}) const override {
+        return vsyncDeadline;
+    }
     Period minFramePeriod() const override { return framePeriod; }
 };
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index ddd78ae..89a341f 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -116,6 +116,7 @@
 #include <common/FlagManager.h>
 #include <gui/LayerStatePermissions.h>
 #include <gui/SchedulingPolicy.h>
+#include <gui/SyncScreenCaptureListener.h>
 #include <ui/DisplayIdentification.h>
 #include "BackgroundExecutor.h"
 #include "Client.h"
@@ -798,22 +799,26 @@
     }));
 }
 
-static std::optional<renderengine::RenderEngine::RenderEngineType>
-chooseRenderEngineTypeViaSysProp() {
+void chooseRenderEngineType(renderengine::RenderEngineCreationArgs::Builder& builder) {
     char prop[PROPERTY_VALUE_MAX];
-    property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "skiaglthreaded");
+    property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "");
 
     if (strcmp(prop, "skiagl") == 0) {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_GL;
+        builder.setThreaded(renderengine::RenderEngine::Threaded::NO)
+                .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::GL);
     } else if (strcmp(prop, "skiaglthreaded") == 0) {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED;
+        builder.setThreaded(renderengine::RenderEngine::Threaded::YES)
+                .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::GL);
     } else if (strcmp(prop, "skiavk") == 0) {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_VK;
+        builder.setThreaded(renderengine::RenderEngine::Threaded::NO)
+                .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::VK);
     } else if (strcmp(prop, "skiavkthreaded") == 0) {
-        return renderengine::RenderEngine::RenderEngineType::SKIA_VK_THREADED;
+        builder.setThreaded(renderengine::RenderEngine::Threaded::YES)
+                .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::VK);
     } else {
-        ALOGE("Unrecognized RenderEngineType %s; ignoring!", prop);
-        return {};
+        builder.setGraphicsApi(FlagManager::getInstance().vulkan_renderengine()
+                                       ? renderengine::RenderEngine::GraphicsApi::VK
+                                       : renderengine::RenderEngine::GraphicsApi::GL);
     }
 }
 
@@ -839,9 +844,7 @@
                                    useContextPriority
                                            ? renderengine::RenderEngine::ContextPriority::REALTIME
                                            : renderengine::RenderEngine::ContextPriority::MEDIUM);
-    if (auto type = chooseRenderEngineTypeViaSysProp()) {
-        builder.setRenderEngineType(type.value());
-    }
+    chooseRenderEngineType(builder);
     mRenderEngine = renderengine::RenderEngine::create(builder.build());
     mCompositionEngine->setRenderEngine(mRenderEngine.get());
     mMaxRenderTargetSize =
@@ -1227,8 +1230,10 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& request, bool force) {
-    const auto displayId = request.mode.modePtr->getPhysicalDisplayId();
+void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& desiredMode) {
+    const auto mode = desiredMode.mode;
+    const auto displayId = mode.modePtr->getPhysicalDisplayId();
+
     ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
     const auto display = getDisplayDeviceLocked(displayId);
@@ -1237,10 +1242,9 @@
         return;
     }
 
-    const auto mode = request.mode;
-    const bool emitEvent = request.emitEvent;
+    const bool emitEvent = desiredMode.emitEvent;
 
-    switch (display->setDesiredMode(std::move(request), force)) {
+    switch (display->setDesiredMode(std::move(desiredMode))) {
         case DisplayDevice::DesiredModeAction::InitiateDisplayModeSwitch:
             // DisplayDevice::setDesiredMode updated the render rate, so inform Scheduler.
             mScheduler->setRenderRate(displayId,
@@ -1426,7 +1430,8 @@
               to_string(displayModePtrOpt->get()->getVsyncRate()).c_str(),
               to_string(display->getId()).c_str());
 
-        if (display->getActiveMode() == desiredModeOpt->mode) {
+        if ((!FlagManager::getInstance().connected_display() || !desiredModeOpt->force) &&
+            display->getActiveMode() == desiredModeOpt->mode) {
             applyActiveMode(display);
             continue;
         }
@@ -1950,6 +1955,7 @@
 
     const char* const whence = __func__;
     return ftl::Future(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
+               // TODO(b/241285876): Validate that the display is physical instead of failing later.
                if (const auto display = getDisplayDeviceLocked(displayToken)) {
                    const bool supportsDisplayBrightnessCommand =
                            getHwComposer().getComposer()->isSupported(
@@ -1999,7 +2005,6 @@
                                                      Hwc2::Composer::DisplayBrightnessOptions{
                                                              .applyImmediately = true});
                    }
-
                } else {
                    ALOGE("%s: Invalid display token %p", whence, displayToken.get());
                    return ftl::yield<status_t>(NAME_NOT_FOUND);
@@ -2750,23 +2755,11 @@
         refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay);
     }
 
-    const TimePoint expectedPresentTime = pacesetterTarget.expectedPresentTime();
     // TODO(b/255601557) Update frameInterval per display
-    refreshArgs.frameInterval = mScheduler->getNextFrameInterval(pacesetterId, expectedPresentTime);
+    refreshArgs.frameInterval =
+            mScheduler->getNextFrameInterval(pacesetterId, pacesetterTarget.expectedPresentTime());
     refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
     refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0;
-    {
-        auto& notifyExpectedPresentData = mNotifyExpectedPresentMap[pacesetterId];
-        auto lastExpectedPresentTimestamp = TimePoint::fromNs(
-                notifyExpectedPresentData.lastExpectedPresentTimestamp.load().ns());
-        if (expectedPresentTime > lastExpectedPresentTimestamp) {
-            // If the values are not same, then hint is sent with newer value.
-            // And because composition always follows the notifyExpectedPresentIfRequired, we can
-            // skip updating the lastExpectedPresentTimestamp in this case.
-            notifyExpectedPresentData.lastExpectedPresentTimestamp
-                    .compare_exchange_weak(lastExpectedPresentTimestamp, expectedPresentTime);
-        }
-    }
     // Store the present time just before calling to the composition engine so we could notify
     // the scheduler.
     const auto presentTime = systemTime();
@@ -2830,6 +2823,7 @@
         scheduleComposite(FrameHint::kNone);
     }
 
+    mNotifyExpectedPresentMap[pacesetterId].hintStatus = NotifyExpectedPresentHintStatus::Start;
     onCompositionPresented(pacesetterId, frameTargeters, presentTime);
 
     const bool hadGpuComposited =
@@ -3290,7 +3284,11 @@
 std::pair<DisplayModes, DisplayModePtr> SurfaceFlinger::loadDisplayModes(
         PhysicalDisplayId displayId) const {
     std::vector<HWComposer::HWCDisplayMode> hwcModes;
-    std::optional<hal::HWDisplayId> activeModeHwcId;
+    std::optional<hal::HWConfigId> activeModeHwcIdOpt;
+
+    const bool isExternalDisplay = FlagManager::getInstance().connected_display() &&
+            getHwComposer().getDisplayConnectionType(displayId) ==
+                    ui::DisplayConnectionType::External;
 
     int attempt = 0;
     constexpr int kMaxAttempts = 3;
@@ -3298,10 +3296,81 @@
         hwcModes = getHwComposer().getModes(displayId,
                                             scheduler::RefreshRateSelector::kMinSupportedFrameRate
                                                     .getPeriodNsecs());
-        activeModeHwcId = getHwComposer().getActiveMode(displayId);
+        const auto activeModeHwcIdExp = getHwComposer().getActiveMode(displayId);
+        activeModeHwcIdOpt = activeModeHwcIdExp.value_opt();
 
-        const auto isActiveMode = [activeModeHwcId](const HWComposer::HWCDisplayMode& mode) {
-            return mode.hwcId == activeModeHwcId;
+        if (isExternalDisplay &&
+            activeModeHwcIdExp.has_error([](status_t error) { return error == NO_INIT; })) {
+            constexpr nsecs_t k59HzVsyncPeriod = 16949153;
+            constexpr nsecs_t k60HzVsyncPeriod = 16666667;
+
+            // DM sets the initial mode for an external display to 1080p@60, but
+            // this comes after SF creates its own state (including the
+            // DisplayDevice). For now, pick the same mode in order to avoid
+            // inconsistent state and unnecessary mode switching.
+            // TODO (b/318534874): Let DM decide the initial mode.
+            //
+            // Try to find 1920x1080 @ 60 Hz
+            if (const auto iter = std::find_if(hwcModes.begin(), hwcModes.end(),
+                                               [](const auto& mode) {
+                                                   return mode.width == 1920 &&
+                                                           mode.height == 1080 &&
+                                                           mode.vsyncPeriod == k60HzVsyncPeriod;
+                                               });
+                iter != hwcModes.end()) {
+                activeModeHwcIdOpt = iter->hwcId;
+                break;
+            }
+
+            // Try to find 1920x1080 @ 59-60 Hz
+            if (const auto iter = std::find_if(hwcModes.begin(), hwcModes.end(),
+                                               [](const auto& mode) {
+                                                   return mode.width == 1920 &&
+                                                           mode.height == 1080 &&
+                                                           mode.vsyncPeriod >= k60HzVsyncPeriod &&
+                                                           mode.vsyncPeriod <= k59HzVsyncPeriod;
+                                               });
+                iter != hwcModes.end()) {
+                activeModeHwcIdOpt = iter->hwcId;
+                break;
+            }
+
+            // The display does not support 1080p@60, and this is the last attempt to pick a display
+            // mode. Prefer 60 Hz if available, with the closest resolution to 1080p.
+            if (attempt + 1 == kMaxAttempts) {
+                std::vector<HWComposer::HWCDisplayMode> hwcModeOpts;
+
+                for (const auto& mode : hwcModes) {
+                    if (mode.width <= 1920 && mode.height <= 1080 &&
+                        mode.vsyncPeriod >= k60HzVsyncPeriod &&
+                        mode.vsyncPeriod <= k59HzVsyncPeriod) {
+                        hwcModeOpts.push_back(mode);
+                    }
+                }
+
+                if (const auto iter = std::max_element(hwcModeOpts.begin(), hwcModeOpts.end(),
+                                                       [](const auto& a, const auto& b) {
+                                                           const auto aSize = a.width * a.height;
+                                                           const auto bSize = b.width * b.height;
+                                                           if (aSize < bSize)
+                                                               return true;
+                                                           else if (aSize == bSize)
+                                                               return a.vsyncPeriod > b.vsyncPeriod;
+                                                           else
+                                                               return false;
+                                                       });
+                    iter != hwcModeOpts.end()) {
+                    activeModeHwcIdOpt = iter->hwcId;
+                    break;
+                }
+
+                // hwcModeOpts was empty, use hwcModes[0] as the last resort
+                activeModeHwcIdOpt = hwcModes[0].hwcId;
+            }
+        }
+
+        const auto isActiveMode = [activeModeHwcIdOpt](const HWComposer::HWCDisplayMode& mode) {
+            return mode.hwcId == activeModeHwcIdOpt;
         };
 
         if (std::any_of(hwcModes.begin(), hwcModes.end(), isActiveMode)) {
@@ -3311,7 +3380,7 @@
 
     if (attempt == kMaxAttempts) {
         const std::string activeMode =
-                activeModeHwcId ? std::to_string(*activeModeHwcId) : "unknown"s;
+                activeModeHwcIdOpt ? std::to_string(*activeModeHwcIdOpt) : "unknown"s;
         ALOGE("HWC failed to report an active mode that is supported: activeModeHwcId=%s, "
               "hwcModes={%s}",
               activeMode.c_str(), base::Join(hwcModes, ", ").c_str());
@@ -3355,10 +3424,14 @@
     // Keep IDs if modes have not changed.
     const auto& modes = sameModes ? oldModes : newModes;
     const DisplayModePtr activeMode =
-            std::find_if(modes.begin(), modes.end(), [activeModeHwcId](const auto& pair) {
-                return pair.second->getHwcId() == activeModeHwcId;
+            std::find_if(modes.begin(), modes.end(), [activeModeHwcIdOpt](const auto& pair) {
+                return pair.second->getHwcId() == activeModeHwcIdOpt;
             })->second;
 
+    if (isExternalDisplay) {
+        ALOGI("External display %s initial mode: {%s}", to_string(displayId).c_str(),
+              to_string(*activeMode).c_str());
+    }
     return {modes, activeMode};
 }
 
@@ -3544,9 +3617,7 @@
             getPhysicalDisplayOrientation(compositionDisplay->getId(), creationArgs.isPrimary);
     ALOGV("Display Orientation: %s", toCString(creationArgs.physicalOrientation));
 
-    // virtual displays are always considered enabled
-    creationArgs.initialPowerMode =
-            state.isVirtual() ? std::make_optional(hal::PowerMode::ON) : std::nullopt;
+    creationArgs.initialPowerMode = state.isVirtual() ? hal::PowerMode::ON : hal::PowerMode::OFF;
 
     creationArgs.requestedRefreshRate = state.requestedRefreshRate;
 
@@ -3665,6 +3736,27 @@
     }
 
     mDisplays.try_emplace(displayToken, std::move(display));
+
+    // For an external display, loadDisplayModes already attempted to select the same mode
+    // as DM, but SF still needs to be updated to match.
+    // TODO (b/318534874): Let DM decide the initial mode.
+    if (const auto& physical = state.physical;
+        mScheduler && physical && FlagManager::getInstance().connected_display()) {
+        const bool isInternalDisplay = mPhysicalDisplays.get(physical->id)
+                                               .transform(&PhysicalDisplay::isInternal)
+                                               .value_or(false);
+
+        if (!isInternalDisplay) {
+            auto activeModePtr = physical->activeMode;
+            const auto fps = activeModePtr->getPeakFps();
+
+            setDesiredMode(
+                    {.mode = scheduler::FrameRateMode{fps,
+                                                      ftl::as_non_null(std::move(activeModePtr))},
+                     .emitEvent = false,
+                     .force = true});
+        }
+    }
 }
 
 void SurfaceFlinger::processDisplayRemoved(const wp<IBinder>& displayToken) {
@@ -3774,6 +3866,9 @@
         mVisibleRegionsDirty = true;
         mUpdateInputInfo = true;
 
+        // Apply the current color matrix to any added or changed display.
+        mCurrentState.colorMatrixChanged = true;
+
         // find the displays that were removed
         // (ie: in drawing state but not in current state)
         // also handle displays that changed
@@ -4110,8 +4205,9 @@
     }
 }
 
-void SurfaceFlinger::onVsyncGenerated(TimePoint expectedPresentTime,
-                                      ftl::NonNull<DisplayModePtr> modePtr, Fps renderRate) {
+void SurfaceFlinger::onExpectedPresentTimePosted(TimePoint expectedPresentTime,
+                                                 ftl::NonNull<DisplayModePtr> modePtr,
+                                                 Fps renderRate) {
     const auto vsyncPeriod = modePtr->getVsyncRate().getPeriod();
     const auto timeoutOpt = [&]() -> std::optional<Period> {
         const auto vrrConfig = modePtr->getVrrConfig();
@@ -4132,45 +4228,91 @@
                                                      TimePoint expectedPresentTime,
                                                      Fps frameInterval,
                                                      std::optional<Period> timeoutOpt) {
-    {
-        auto& data = mNotifyExpectedPresentMap[displayId];
-        const auto lastExpectedPresentTimestamp = data.lastExpectedPresentTimestamp.load();
-        const auto lastFrameInterval = data.lastFrameInterval;
-        data.lastFrameInterval = frameInterval;
-        const auto threshold = Duration::fromNs(vsyncPeriod.ns() / 2);
+    auto& data = mNotifyExpectedPresentMap[displayId];
+    const auto lastExpectedPresentTimestamp = data.lastExpectedPresentTimestamp;
+    const auto lastFrameInterval = data.lastFrameInterval;
+    data.lastFrameInterval = frameInterval;
+    data.lastExpectedPresentTimestamp = expectedPresentTime;
+    const auto threshold = Duration::fromNs(vsyncPeriod.ns() / 2);
 
-        const constexpr nsecs_t kOneSecondNs =
-                std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
-        const auto timeout = Period::fromNs(timeoutOpt && timeoutOpt->ns() > 0 ? timeoutOpt->ns()
-                                                                               : kOneSecondNs);
-        const bool frameIntervalIsOnCadence =
-                isFrameIntervalOnCadence(expectedPresentTime, lastExpectedPresentTimestamp,
-                                         lastFrameInterval, timeout, threshold);
+    const constexpr nsecs_t kOneSecondNs =
+            std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
+    const auto timeout =
+            Period::fromNs(timeoutOpt && timeoutOpt->ns() > 0 ? timeoutOpt->ns() : kOneSecondNs);
+    const bool frameIntervalIsOnCadence =
+            isFrameIntervalOnCadence(expectedPresentTime, lastExpectedPresentTimestamp,
+                                     lastFrameInterval, timeout, threshold);
 
-        const bool expectedPresentWithinTimeout =
-                isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp,
-                                               timeoutOpt, threshold);
+    const bool expectedPresentWithinTimeout =
+            isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp,
+                                           timeoutOpt, threshold);
+    if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) {
+        return;
+    }
 
-        using fps_approx_ops::operator!=;
-        if (frameIntervalIsOnCadence && frameInterval != lastFrameInterval) {
-            data.lastExpectedPresentTimestamp = expectedPresentTime;
-        }
-
-        if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) {
+    auto hintStatus = data.hintStatus.load();
+    if (!expectedPresentWithinTimeout) {
+        if (hintStatus != NotifyExpectedPresentHintStatus::Sent ||
+            (timeoutOpt && timeoutOpt->ns() == 0)) {
+            // Send the hint immediately if timeout, as the hint gets
+            // delayed otherwise, as the frame is scheduled close
+            // to the actual present.
+            if (data.hintStatus
+                        .compare_exchange_strong(hintStatus,
+                                                 NotifyExpectedPresentHintStatus::ScheduleOnTx)) {
+                scheduleNotifyExpectedPresentHint(displayId);
+            }
             return;
         }
-        data.lastExpectedPresentTimestamp = expectedPresentTime;
+    }
+
+    if (hintStatus != NotifyExpectedPresentHintStatus::Start) {
+        return;
+    }
+    data.hintStatus.store(NotifyExpectedPresentHintStatus::ScheduleOnPresent);
+    mScheduler->scheduleFrame();
+}
+
+void SurfaceFlinger::scheduleNotifyExpectedPresentHint(PhysicalDisplayId displayId) {
+    auto itr = mNotifyExpectedPresentMap.find(displayId);
+    if (itr == mNotifyExpectedPresentMap.end()) {
+        return;
     }
 
     const char* const whence = __func__;
-    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
-        const auto status = getHwComposer().notifyExpectedPresent(displayId, expectedPresentTime,
-                                                                  frameInterval);
+    const auto sendHint = [=, this]() {
+        auto& data = mNotifyExpectedPresentMap.at(displayId);
+        data.hintStatus.store(NotifyExpectedPresentHintStatus::Sent);
+        const auto status =
+                getHwComposer().notifyExpectedPresent(displayId, data.lastExpectedPresentTimestamp,
+                                                      data.lastFrameInterval);
         if (status != NO_ERROR) {
             ALOGE("%s failed to notifyExpectedPresentHint for display %" PRId64, whence,
                   displayId.value);
         }
-    }));
+    };
+
+    if (itr->second.hintStatus == NotifyExpectedPresentHintStatus::ScheduleOnTx) {
+        return static_cast<void>(mScheduler->schedule([=,
+                                                       this]() FTL_FAKE_GUARD(kMainThreadContext) {
+            auto& data = mNotifyExpectedPresentMap.at(displayId);
+            auto scheduleHintOnTx = NotifyExpectedPresentHintStatus::ScheduleOnTx;
+            if (data.hintStatus.compare_exchange_strong(scheduleHintOnTx,
+                                                        NotifyExpectedPresentHintStatus::Sent)) {
+                sendHint();
+            }
+        }));
+    }
+    sendHint();
+}
+
+void SurfaceFlinger::sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) {
+    if (auto itr = mNotifyExpectedPresentMap.find(displayId);
+        itr == mNotifyExpectedPresentMap.end() ||
+        itr->second.hintStatus != NotifyExpectedPresentHintStatus::ScheduleOnPresent) {
+        return;
+    }
+    scheduleNotifyExpectedPresentHint(displayId);
 }
 
 void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) {
@@ -4212,8 +4354,7 @@
 
     mScheduler = std::make_unique<Scheduler>(static_cast<ICompositor&>(*this),
                                              static_cast<ISchedulerCallback&>(*this), features,
-                                             getFactory(), activeRefreshRate, *mTimeStats,
-                                             static_cast<IVsyncTrackerCallback&>(*this));
+                                             getFactory(), activeRefreshRate, *mTimeStats);
     mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
     if (FlagManager::getInstance().vrr_config()) {
         mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps);
@@ -4272,7 +4413,6 @@
     }
 
     mDrawingState = mCurrentState;
-    // clear the "changed" flags in current state
     mCurrentState.colorMatrixChanged = false;
 
     if (mVisibleRegionsDirty) {
@@ -5732,7 +5872,7 @@
         case ISurfaceComposerClient::eFXSurfaceContainer:
         case ISurfaceComposerClient::eFXSurfaceBufferState:
             args.flags |= ISurfaceComposerClient::eNoColorFill;
-            FMT_FALLTHROUGH;
+            [[fallthrough]];
         case ISurfaceComposerClient::eFXSurfaceEffect: {
             result = createBufferStateLayer(args, &outResult.handle, &layer);
             std::atomic<int32_t>* pendingBufferCounter = layer->getPendingBufferCounter();
@@ -5810,12 +5950,6 @@
 }
 
 void SurfaceFlinger::initializeDisplays() {
-    const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
-    if (!display) return;
-
-    const sp<IBinder> token = display->getDisplayToken().promote();
-    LOG_ALWAYS_FATAL_IF(token == nullptr);
-
     TransactionState state;
     state.inputWindowCommands = mInputWindowCommands;
     const nsecs_t now = systemTime();
@@ -5826,18 +5960,10 @@
     const uint64_t transactionId = (static_cast<uint64_t>(mPid) << 32) | mUniqueTransactionId++;
     state.id = transactionId;
 
-    // reset screen orientation and use primary layer stack
-    DisplayState d;
-    d.what = DisplayState::eDisplayProjectionChanged |
-             DisplayState::eLayerStackChanged;
-    d.token = token;
-    d.layerStack = ui::DEFAULT_LAYER_STACK;
-    d.orientation = ui::ROTATION_0;
-    d.orientedDisplaySpaceRect.makeInvalid();
-    d.layerStackSpaceRect.makeInvalid();
-    d.width = 0;
-    d.height = 0;
-    state.displays.add(d);
+    auto layerStack = ui::DEFAULT_LAYER_STACK.id;
+    for (const auto& [id, display] : FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays)) {
+        state.displays.push(DisplayState(display.token(), ui::LayerStack::fromValue(layerStack++)));
+    }
 
     std::vector<TransactionState> transactions;
     transactions.emplace_back(state);
@@ -5850,12 +5976,25 @@
 
     {
         ftl::FakeGuard guard(mStateLock);
-        setPowerModeInternal(display, hal::PowerMode::ON);
+
+        // In case of a restart, ensure all displays are off.
+        for (const auto& [id, display] : mPhysicalDisplays) {
+            setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::OFF);
+        }
+
+        // Power on all displays. The primary display is first, so becomes the active display. Also,
+        // the DisplayCapability set of a display is populated on its first powering on. Do this now
+        // before responding to any Binder query from DisplayManager about display capabilities.
+        for (const auto& [id, display] : mPhysicalDisplays) {
+            setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::ON);
+        }
     }
 }
 
 void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode) {
     if (display->isVirtual()) {
+        // TODO(b/241285876): This code path should not be reachable, so enforce this at compile
+        // time.
         ALOGE("%s: Invalid operation on virtual display", __func__);
         return;
     }
@@ -5863,8 +6002,8 @@
     const auto displayId = display->getPhysicalId();
     ALOGD("Setting power mode %d on display %s", mode, to_string(displayId).c_str());
 
-    const auto currentModeOpt = display->getPowerMode();
-    if (currentModeOpt == mode) {
+    const auto currentMode = display->getPowerMode();
+    if (currentMode == mode) {
         return;
     }
 
@@ -5881,7 +6020,7 @@
     display->setPowerMode(mode);
 
     const auto activeMode = display->refreshRateSelector().getActiveMode().modePtr;
-    if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) {
+    if (currentMode == hal::PowerMode::OFF) {
         // Turn on the display
 
         // Activate the display (which involves a modeset to the active mode) when the inner or
@@ -5926,7 +6065,7 @@
         mVisibleRegionsDirty = true;
         scheduleComposite(FrameHint::kActive);
     } else if (mode == hal::PowerMode::OFF) {
-        const bool currentModeNotDozeSuspend = (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND);
+        const bool currentModeNotDozeSuspend = (currentMode != hal::PowerMode::DOZE_SUSPEND);
         // Turn off the display
         if (displayId == mActiveDisplayId) {
             if (const auto display = getActivatableDisplay()) {
@@ -5967,7 +6106,7 @@
     } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) {
         // Update display while dozing
         getHwComposer().setPowerMode(displayId, mode);
-        if (*currentModeOpt == hal::PowerMode::DOZE_SUSPEND &&
+        if (currentMode == hal::PowerMode::DOZE_SUSPEND &&
             (displayId == mActiveDisplayId || FlagManager::getInstance().multithreaded_present())) {
             if (displayId == mActiveDisplayId) {
                 ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON.");
@@ -7740,6 +7879,12 @@
                         kAllowProtected, kGrayscale, captureListener);
 }
 
+ScreenCaptureResults SurfaceFlinger::captureLayersSync(const LayerCaptureArgs& args) {
+    sp<SyncScreenCaptureListener> captureListener = sp<SyncScreenCaptureListener>::make();
+    captureLayers(args, captureListener);
+    return captureListener->waitForResults();
+}
+
 void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
                                    const sp<IScreenCaptureListener>& captureListener) {
     ATRACE_CALL();
@@ -8149,23 +8294,27 @@
     //
     // TODO(b/196334700) Once we use RenderEngineThreaded everywhere we can always defer the call
     // to CompositionEngine::present.
-    const bool renderEngineIsThreaded = [&]() {
-        using Type = renderengine::RenderEngine::RenderEngineType;
-        const auto type = mRenderEngine->getRenderEngineType();
-        return type == Type::SKIA_GL_THREADED;
-    }();
-    auto presentFuture = renderEngineIsThreaded ? ftl::defer(std::move(present)).share()
-                                                : ftl::yield(present()).share();
+    auto presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share()
+                                                     : ftl::yield(present()).share();
 
     for (auto& [layer, layerFE] : layers) {
-        layer->onLayerDisplayed(ftl::Future(presentFuture)
-                                        .then([layerFE = std::move(layerFE)](FenceResult) {
-                                            return layerFE->stealCompositionResult()
-                                                    .releaseFences.back()
-                                                    .first.get();
-                                        })
-                                        .share(),
-                                ui::INVALID_LAYER_STACK);
+        layer->onLayerDisplayed(presentFuture, ui::INVALID_LAYER_STACK,
+                                [layerFE = std::move(layerFE)](FenceResult) {
+                                    if (FlagManager::getInstance()
+                                                .screenshot_fence_preservation()) {
+                                        const auto compositionResult =
+                                                layerFE->stealCompositionResult();
+                                        const auto& fences = compositionResult.releaseFences;
+                                        // CompositionEngine may choose to cull layers that
+                                        // aren't visible, so pass a non-fence.
+                                        return fences.empty() ? Fence::NO_FENCE
+                                                              : fences.back().first.get();
+                                    } else {
+                                        return layerFE->stealCompositionResult()
+                                                .releaseFences.back()
+                                                .first.get();
+                                    }
+                                });
     }
 
     return presentFuture;
@@ -8332,7 +8481,7 @@
         return INVALID_OPERATION;
     }
 
-    setDesiredMode({std::move(preferredMode), .emitEvent = true}, force);
+    setDesiredMode({std::move(preferredMode), .emitEvent = true, .force = force});
 
     // Update the frameRateOverride list as the display render rate might have changed
     if (mScheduler->updateFrameRateOverrides(scheduler::GlobalSignals{}, preferredFps)) {
@@ -9584,6 +9733,12 @@
     return binderStatusFromStatusT(NO_ERROR);
 }
 
+binder::Status SurfaceComposerAIDL::captureLayersSync(const LayerCaptureArgs& args,
+                                                      ScreenCaptureResults* outResults) {
+    *outResults = mFlinger->captureLayersSync(args);
+    return binderStatusFromStatusT(NO_ERROR);
+}
+
 binder::Status SurfaceComposerAIDL::captureLayers(
         const LayerCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) {
     mFlinger->captureLayers(args, captureListener);
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 6e12172..be05797 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -199,8 +199,7 @@
                        private HWC2::ComposerCallback,
                        private ICompositor,
                        private scheduler::ISchedulerCallback,
-                       private compositionengine::ICEPowerCallback,
-                       private scheduler::IVsyncTrackerCallback {
+                       private compositionengine::ICEPowerCallback {
 public:
     struct SkipInitializationTag {};
 
@@ -560,6 +559,7 @@
 
     void captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
     void captureDisplay(DisplayId, const CaptureArgs&, const sp<IScreenCaptureListener>&);
+    ScreenCaptureResults captureLayersSync(const LayerCaptureArgs&);
     void captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
 
     status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* stats);
@@ -685,14 +685,12 @@
     void kernelTimerChanged(bool expired) override;
     void triggerOnFrameRateOverridesChanged() override;
     void onChoreographerAttached() override;
+    void onExpectedPresentTimePosted(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
+                                     Fps renderRate) override;
 
     // ICEPowerCallback overrides:
     void notifyCpuLoadUp() override;
 
-    // IVsyncTrackerCallback overrides
-    void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
-                          Fps renderRate) override;
-
     // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates.
     void toggleKernelIdleTimer() REQUIRES(mStateLock);
 
@@ -718,7 +716,7 @@
     // Show hdr sdr ratio overlay
     bool mHdrSdrRatioOverlay = false;
 
-    void setDesiredMode(display::DisplayModeRequest&&, bool force = false) REQUIRES(mStateLock);
+    void setDesiredMode(display::DisplayModeRequest&&) REQUIRES(mStateLock);
 
     status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps,
                                        Fps maxFps);
@@ -903,7 +901,8 @@
      * Display and layer stack management
      */
 
-    // Called during boot, and restart after system_server death.
+    // Called during boot and restart after system_server death, setting the stage for bootanimation
+    // before DisplayManager takes over.
     void initializeDisplays() REQUIRES(kMainThreadContext);
 
     sp<const DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) const
@@ -1480,14 +1479,28 @@
     ftl::SmallMap<int64_t, sp<SurfaceControl>, 3> mMirrorMapForDebug;
 
     // NotifyExpectedPresentHint
+    enum class NotifyExpectedPresentHintStatus {
+        // Represents that framework can start sending hint if required.
+        Start,
+        // Represents that the hint is already sent.
+        Sent,
+        // Represents that the hint will be scheduled with a new frame.
+        ScheduleOnPresent,
+        // Represents that a hint will be sent instantly by scheduling on the main thread.
+        ScheduleOnTx
+    };
     struct NotifyExpectedPresentData {
-        // lastExpectedPresentTimestamp is read and write from multiple threads such as
-        // main thread, EventThread, MessageQueue. And is atomic for that reason.
-        std::atomic<TimePoint> lastExpectedPresentTimestamp{};
+        TimePoint lastExpectedPresentTimestamp{};
         Fps lastFrameInterval{};
+        // hintStatus is read and write from multiple threads such as
+        // main thread, EventThread. And is atomic for that reason.
+        std::atomic<NotifyExpectedPresentHintStatus> hintStatus =
+                NotifyExpectedPresentHintStatus::Start;
     };
     std::unordered_map<PhysicalDisplayId, NotifyExpectedPresentData> mNotifyExpectedPresentMap;
-
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) override
+            REQUIRES(kMainThreadContext);
+    void scheduleNotifyExpectedPresentHint(PhysicalDisplayId displayId);
     void notifyExpectedPresentIfRequired(PhysicalDisplayId, Period vsyncPeriod,
                                          TimePoint expectedPresentTime, Fps frameInterval,
                                          std::optional<Period> timeoutOpt);
@@ -1546,6 +1559,7 @@
                                       const sp<IScreenCaptureListener>&) override;
     binder::Status captureLayers(const LayerCaptureArgs&,
                                  const sp<IScreenCaptureListener>&) override;
+    binder::Status captureLayersSync(const LayerCaptureArgs&, ScreenCaptureResults* results);
 
     // TODO(b/239076119): Remove deprecated AIDL.
     [[deprecated]] binder::Status clearAnimationFrameStats() override {
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index 6a155c1..7b5298c 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -25,6 +25,7 @@
 
 #include "TransactionCallbackInvoker.h"
 #include "BackgroundExecutor.h"
+#include "Utils/FenceUtils.h"
 
 #include <cinttypes>
 
@@ -127,33 +128,8 @@
     sp<IBinder> surfaceControl = handle->surfaceControl.promote();
     if (surfaceControl) {
         sp<Fence> prevFence = nullptr;
-
         for (const auto& future : handle->previousReleaseFences) {
-            sp<Fence> currentFence = future.get().value_or(Fence::NO_FENCE);
-            if (prevFence == nullptr && currentFence->getStatus() != Fence::Status::Invalid) {
-                prevFence = std::move(currentFence);
-            } else if (prevFence != nullptr) {
-                // If both fences are signaled or both are unsignaled, we need to merge
-                // them to get an accurate timestamp.
-                if (prevFence->getStatus() != Fence::Status::Invalid &&
-                    prevFence->getStatus() == currentFence->getStatus()) {
-                    char fenceName[32] = {};
-                    snprintf(fenceName, 32, "%.28s", handle->name.c_str());
-                    sp<Fence> mergedFence = Fence::merge(fenceName, prevFence, currentFence);
-                    if (mergedFence->isValid()) {
-                        prevFence = std::move(mergedFence);
-                    }
-                } else if (currentFence->getStatus() == Fence::Status::Unsignaled) {
-                    // If one fence has signaled and the other hasn't, the unsignaled
-                    // fence will approximately correspond with the correct timestamp.
-                    // There's a small race if both fences signal at about the same time
-                    // and their statuses are retrieved with unfortunate timing. However,
-                    // by this point, they will have both signaled and only the timestamp
-                    // will be slightly off; any dependencies after this point will
-                    // already have been met.
-                    prevFence = std::move(currentFence);
-                }
-            }
+            mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence);
         }
         handle->previousReleaseFence = prevFence;
         handle->previousReleaseFences.clear();
diff --git a/services/surfaceflinger/Utils/FenceUtils.h b/services/surfaceflinger/Utils/FenceUtils.h
new file mode 100644
index 0000000..f6f7006
--- /dev/null
+++ b/services/surfaceflinger/Utils/FenceUtils.h
@@ -0,0 +1,51 @@
+/**
+ * Copyright (C) 2024 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 <ui/Fence.h>
+
+namespace android {
+
+// TODO: measure if Fence::merge is cheaper
+inline void mergeFence(const char* debugName, sp<Fence>&& incomingFence, sp<Fence>& prevFence) {
+    if (prevFence == nullptr && incomingFence->getStatus() != Fence::Status::Invalid) {
+        prevFence = std::move(incomingFence);
+    } else if (prevFence != nullptr) {
+        // If both fences are signaled or both are unsignaled, we need to merge
+        // them to get an accurate timestamp.
+        if (prevFence->getStatus() != Fence::Status::Invalid &&
+            prevFence->getStatus() == incomingFence->getStatus()) {
+            char fenceName[32] = {};
+            snprintf(fenceName, 32, "%.28s", debugName);
+            sp<Fence> mergedFence = Fence::merge(fenceName, prevFence, incomingFence);
+            if (mergedFence->isValid()) {
+                prevFence = std::move(mergedFence);
+            }
+        } else if (incomingFence->getStatus() == Fence::Status::Unsignaled) {
+            // If one fence has signaled and the other hasn't, the unsignaled
+            // fence will approximately correspond with the correct timestamp.
+            // There's a small race if both fences signal at about the same time
+            // and their statuses are retrieved with unfortunate timing. However,
+            // by this point, they will have both signaled and only the timestamp
+            // will be slightly off; any dependencies after this point will
+            // already have been met.
+            prevFence = std::move(incomingFence);
+        }
+    }
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index a27e100..b07e7ac 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -128,6 +128,8 @@
     DUMP_READ_ONLY_FLAG(fp16_client_target);
     DUMP_READ_ONLY_FLAG(game_default_frame_rate);
     DUMP_READ_ONLY_FLAG(enable_layer_command_batching);
+    DUMP_READ_ONLY_FLAG(screenshot_fence_preservation);
+    DUMP_READ_ONLY_FLAG(vulkan_renderengine);
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
 #undef DUMP_FLAG_INTERVAL
@@ -202,6 +204,8 @@
 FLAG_MANAGER_READ_ONLY_FLAG(fp16_client_target, "debug.sf.fp16_client_target")
 FLAG_MANAGER_READ_ONLY_FLAG(game_default_frame_rate, "")
 FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "")
+FLAG_MANAGER_READ_ONLY_FLAG(screenshot_fence_preservation, "debug.sf.screenshot_fence_preservation")
+FLAG_MANAGER_READ_ONLY_FLAG(vulkan_renderengine, "debug.renderengine.vulkan")
 
 /// Trunk stable server flags ///
 FLAG_MANAGER_SERVER_FLAG(late_boot_misc2, "")
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index c01465b..2a30a40 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -68,6 +68,8 @@
     bool fp16_client_target() const;
     bool game_default_frame_rate() const;
     bool enable_layer_command_batching() const;
+    bool screenshot_fence_preservation() const;
+    bool vulkan_renderengine() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
index 68237c8..682079f 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
@@ -143,7 +143,6 @@
     void invokeComposerHal2_2(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
     void invokeComposerHal2_3(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
     void invokeComposerHal2_4(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
-    void getDisplayVsyncPeriod();
     void setActiveModeWithConstraints();
     void getDisplayIdentificationData();
     void dumpHwc();
@@ -202,11 +201,6 @@
     return display;
 }
 
-void DisplayHardwareFuzzer::getDisplayVsyncPeriod() {
-    nsecs_t outVsyncPeriod;
-    mHwc.getDisplayVsyncPeriod(mPhysicalDisplayId, &outVsyncPeriod);
-}
-
 void DisplayHardwareFuzzer::setActiveModeWithConstraints() {
     hal::VsyncPeriodChangeTimeline outTimeline;
     mHwc.setActiveModeWithConstraints(mPhysicalDisplayId, kActiveConfig, {} /*constraints*/,
@@ -617,8 +611,7 @@
 
     mHwc.getDisplayConnectionType(mPhysicalDisplayId);
     mHwc.isVsyncPeriodSwitchSupported(mPhysicalDisplayId);
-
-    getDisplayVsyncPeriod();
+    mHwc.getDisplayVsyncPeriod(mPhysicalDisplayId);
 
     setActiveModeWithConstraints();
 
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index fa79956..0d15f62 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -228,9 +228,9 @@
                       VsyncSchedule::TrackerPtr tracker,
                       std::shared_ptr<RefreshRateSelector> selectorPtr,
                       surfaceflinger::Factory& factory, TimeStats& timeStats,
-                      ISchedulerCallback& callback, IVsyncTrackerCallback& vsyncTrackerCallback)
+                      ISchedulerCallback& callback)
           : Scheduler(*this, callback, Feature::kContentDetection, factory,
-                      selectorPtr->getActiveMode().fps, timeStats, vsyncTrackerCallback) {
+                      selectorPtr->getActiveMode().fps, timeStats) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplayInternal(displayId, std::move(selectorPtr),
                                 std::shared_ptr<VsyncSchedule>(
@@ -289,6 +289,8 @@
     }
     void sample() override {}
 
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {}
+
     // MessageQueue overrides:
     void scheduleFrame() override {}
     void postMessage(sp<MessageHandler>&& handler) override { handler->handleMessage(Message()); }
@@ -396,8 +398,7 @@
 } // namespace surfaceflinger::test
 
 // TODO(b/189053744) : Create a common test/mock library for surfaceflinger
-class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback,
-                                     private scheduler::IVsyncTrackerCallback {
+class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback {
 public:
     using HotplugEvent = SurfaceFlinger::HotplugEvent;
 
@@ -660,7 +661,6 @@
                         std::unique_ptr<EventThread> appEventThread,
                         std::unique_ptr<EventThread> sfEventThread,
                         scheduler::ISchedulerCallback* callback = nullptr,
-                        scheduler::IVsyncTrackerCallback* vsyncTrackerCallback = nullptr,
                         bool hasMultipleModes = false) {
         constexpr DisplayModeId kModeId60{0};
         DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz));
@@ -675,8 +675,7 @@
         mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
                                                       std::move(vsyncTracker), mRefreshRateSelector,
                                                       mFactory, *mFlinger->mTimeStats,
-                                                      *(callback ?: this),
-                                                      *(vsyncTrackerCallback ?: this));
+                                                      *(callback ?: this));
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
@@ -796,9 +795,7 @@
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
     void onChoreographerAttached() override {}
-
-    // IVsyncTrackerCallback overrides
-    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
+    void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
 
     surfaceflinger::test::Factory mFactory;
     sp<SurfaceFlinger> mFlinger =
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index c098dda..8cd6e1b 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -103,6 +103,7 @@
     bool throttleVsync(TimePoint, uid_t) override { return false; }
     Period getVsyncPeriod(uid_t) override { return kSyncPeriod; }
     void resync() override {}
+    void onExpectedPresentTimePosted(TimePoint) override {}
 };
 
 void SchedulerFuzzer::fuzzEventThread() {
@@ -180,21 +181,15 @@
     dump<scheduler::VSyncDispatchTimerQueueEntry>(&entry, &mFdp);
 }
 
-struct VsyncTrackerCallback : public scheduler::IVsyncTrackerCallback {
-    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
-};
-
 void SchedulerFuzzer::fuzzVSyncPredictor() {
     uint16_t now = mFdp.ConsumeIntegral<uint16_t>();
     uint16_t historySize = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
     uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
     nsecs_t idealPeriod = mFdp.ConsumeIntegralInRange<nsecs_t>(1, UINT32_MAX);
-    VsyncTrackerCallback callback;
     const auto mode = ftl::as_non_null(
             mock::createDisplayMode(DisplayModeId(0), Fps::fromPeriodNsecs(idealPeriod)));
     scheduler::VSyncPredictor tracker{mode, historySize, minimumSamplesForPrediction,
-                                      mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/,
-                                      callback};
+                                      mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/};
     uint16_t period = mFdp.ConsumeIntegral<uint16_t>();
     tracker.setDisplayModePtr(ftl::as_non_null(
             mock::createDisplayMode(DisplayModeId(0), Fps::fromPeriodNsecs(period))));
@@ -441,7 +436,9 @@
         FuzzedDataProvider& fuzzer;
 
         Period period() const { return getFuzzedDuration(fuzzer); }
-        TimePoint vsyncDeadlineAfter(TimePoint) const { return getFuzzedTimePoint(fuzzer); }
+        TimePoint vsyncDeadlineAfter(TimePoint, ftl::Optional<TimePoint> = {}) const {
+            return getFuzzedTimePoint(fuzzer);
+        }
         Period minFramePeriod() const { return period(); }
     } vsyncSource{mFdp};
 
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index f097a13..fcbef01 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -166,3 +166,11 @@
   bug: "293371537"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "screenshot_fence_preservation"
+  namespace: "core_graphics"
+  description: "Bug fix around screenshot fences"
+  bug: "302703346"
+  is_fixed_read_only: true
+}
diff --git a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
index f1bb231..b17b529 100644
--- a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
@@ -17,7 +17,7 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
-#include "DisplayTransactionTestHelpers.h"
+#include "DualDisplayTransactionTest.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -25,31 +25,10 @@
 namespace android {
 namespace {
 
-struct ActiveDisplayRotationFlagsTest : DisplayTransactionTest {
-    static constexpr bool kWithMockScheduler = false;
-    ActiveDisplayRotationFlagsTest() : DisplayTransactionTest(kWithMockScheduler) {}
-
+struct ActiveDisplayRotationFlagsTest
+      : DualDisplayTransactionTest<hal::PowerMode::ON, hal::PowerMode::OFF> {
     void SetUp() override {
-        injectMockScheduler(kInnerDisplayId);
-
-        // Inject inner and outer displays with uninitialized power modes.
-        constexpr bool kInitPowerMode = false;
-        {
-            InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
-            auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
-            injector.setPowerMode(std::nullopt);
-            injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
-            mInnerDisplay = injector.inject();
-        }
-        {
-            OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
-            auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
-            injector.setPowerMode(std::nullopt);
-            mOuterDisplay = injector.inject();
-        }
-
-        mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
-        mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+        DualDisplayTransactionTest::SetUp();
 
         // The flags are a static variable, so by modifying them in the test, we
         // are modifying the real ones used by SurfaceFlinger. Save the original
@@ -64,10 +43,6 @@
 
     void TearDown() override { mFlinger.mutableActiveDisplayRotationFlags() = mOldRotationFlags; }
 
-    static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
-    static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
-
-    sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
     ui::Transform::RotationFlags mOldRotationFlags;
 };
 
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 5809ea0..7c191a5 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -95,6 +95,7 @@
         "MessageQueueTest.cpp",
         "PowerAdvisorTest.cpp",
         "SmallAreaDetectionAllowMappingsTest.cpp",
+        "SurfaceFlinger_ColorMatrixTest.cpp",
         "SurfaceFlinger_CreateDisplayTest.cpp",
         "SurfaceFlinger_DestroyDisplayTest.cpp",
         "SurfaceFlinger_DisplayModeSwitching.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h b/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h
new file mode 100644
index 0000000..34e4ba5
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 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 <gtest/gtest.h>
+
+#include <compositionengine/Display.h>
+#include <compositionengine/mock/DisplaySurface.h>
+#include <renderengine/mock/RenderEngine.h>
+
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockComposer.h"
+#include "mock/DisplayHardware/MockPowerAdvisor.h"
+#include "mock/MockTimeStats.h"
+#include "mock/system/window/MockNativeWindow.h"
+
+namespace android {
+
+// Minimal setup to use TestableSurfaceFlinger::commitAndComposite.
+struct CommitAndCompositeTest : testing::Test {
+    void SetUp() override {
+        mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID});
+        mComposer = new Hwc2::mock::Composer();
+        mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
+        mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+        mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
+        mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
+        mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
+
+        constexpr bool kIsPrimary = true;
+        FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
+                .setPowerMode(hal::PowerMode::ON)
+                .inject(&mFlinger, mComposer);
+        auto compostionEngineDisplayArgs =
+                compositionengine::DisplayCreationArgsBuilder()
+                        .setId(DEFAULT_DISPLAY_ID)
+                        .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
+                        .setPowerAdvisor(mPowerAdvisor)
+                        .setName("Internal display")
+                        .build();
+        auto compositionDisplay =
+                compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
+                                                       std::move(compostionEngineDisplayArgs));
+        mDisplay = FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
+                                             ui::DisplayConnectionType::Internal, HWC_DISPLAY,
+                                             kIsPrimary)
+                           .setDisplaySurface(mDisplaySurface)
+                           .setNativeWindow(mNativeWindow)
+                           .setPowerMode(hal::PowerMode::ON)
+                           .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector())
+                           .skipRegisterDisplay()
+                           .inject();
+    }
+
+    using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+    using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+
+    static constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
+    static constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+    static constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
+    static constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
+
+    TestableSurfaceFlinger mFlinger;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+    sp<DisplayDevice> mDisplay;
+    sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
+            sp<compositionengine::mock::DisplaySurface>::make();
+    sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
+    mock::TimeStats* mTimeStats = new mock::TimeStats();
+    Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
+    Hwc2::mock::Composer* mComposer = nullptr;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index beb2147..7d8a30a 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -25,6 +25,7 @@
 
 #include <compositionengine/Display.h>
 #include <compositionengine/mock/DisplaySurface.h>
+#include <ftl/future.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <gui/IProducerListener.h>
@@ -227,14 +228,6 @@
     LayerCase::cleanup(this);
 }
 
-template <class T>
-std::future<T> futureOf(T obj) {
-    std::promise<T> resultPromise;
-    std::future<T> resultFuture = resultPromise.get_future();
-    resultPromise.set_value(std::move(obj));
-    return resultFuture;
-}
-
 /* ------------------------------------------------------------------------
  * Variants for each display configuration which can be tested
  */
@@ -327,13 +320,13 @@
                 .WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings,
                                     const std::vector<renderengine::LayerSettings>&,
                                     const std::shared_ptr<renderengine::ExternalTexture>&,
-                                    base::unique_fd&&) -> std::future<FenceResult> {
+                                    base::unique_fd&&) -> ftl::Future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.clip);
-                    return futureOf<FenceResult>(Fence::NO_FENCE);
+                    return ftl::yield<FenceResult>(Fence::NO_FENCE);
                 });
     }
 
@@ -378,14 +371,14 @@
                 .WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings,
                                     const std::vector<renderengine::LayerSettings>&,
                                     const std::shared_ptr<renderengine::ExternalTexture>&,
-                                    base::unique_fd&&) -> std::future<FenceResult> {
+                                    base::unique_fd&&) -> ftl::Future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.clip);
                     EXPECT_EQ(ui::Dataspace::UNKNOWN, displaySettings.outputDataspace);
-                    return futureOf<FenceResult>(Fence::NO_FENCE);
+                    return ftl::yield<FenceResult>(Fence::NO_FENCE);
                 });
     }
 
@@ -578,7 +571,7 @@
                 .WillOnce([&](const renderengine::DisplaySettings& displaySettings,
                               const std::vector<renderengine::LayerSettings>& layerSettings,
                               const std::shared_ptr<renderengine::ExternalTexture>&,
-                              base::unique_fd&&) -> std::future<FenceResult> {
+                              base::unique_fd&&) -> ftl::Future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
@@ -586,7 +579,8 @@
                               displaySettings.clip);
                     // screen capture adds an additional color layer as an alpha
                     // prefill, so gtet the back layer.
-                    std::future<FenceResult> resultFuture = futureOf<FenceResult>(Fence::NO_FENCE);
+                    ftl::Future<FenceResult> resultFuture =
+                            ftl::yield<FenceResult>(Fence::NO_FENCE);
                     if (layerSettings.empty()) {
                         ADD_FAILURE() << "layerSettings was not expected to be empty in "
                                          "setupREBufferCompositionCommonCallExpectations "
@@ -627,7 +621,7 @@
                 .WillOnce([&](const renderengine::DisplaySettings& displaySettings,
                               const std::vector<renderengine::LayerSettings>& layerSettings,
                               const std::shared_ptr<renderengine::ExternalTexture>&,
-                              base::unique_fd&&) -> std::future<FenceResult> {
+                              base::unique_fd&&) -> ftl::Future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
@@ -635,7 +629,8 @@
                               displaySettings.clip);
                     // screen capture adds an additional color layer as an alpha
                     // prefill, so get the back layer.
-                    std::future<FenceResult> resultFuture = futureOf<FenceResult>(Fence::NO_FENCE);
+                    ftl::Future<FenceResult> resultFuture =
+                            ftl::yield<FenceResult>(Fence::NO_FENCE);
                     if (layerSettings.empty()) {
                         ADD_FAILURE()
                                 << "layerSettings was not expected to be empty in "
@@ -709,7 +704,7 @@
                 .WillOnce([&](const renderengine::DisplaySettings& displaySettings,
                               const std::vector<renderengine::LayerSettings>& layerSettings,
                               const std::shared_ptr<renderengine::ExternalTexture>&,
-                              base::unique_fd&&) -> std::future<FenceResult> {
+                              base::unique_fd&&) -> ftl::Future<FenceResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
@@ -717,7 +712,8 @@
                               displaySettings.clip);
                     // screen capture adds an additional color layer as an alpha
                     // prefill, so get the back layer.
-                    std::future<FenceResult> resultFuture = futureOf<FenceResult>(Fence::NO_FENCE);
+                    ftl::Future<FenceResult> resultFuture =
+                            ftl::yield<FenceResult>(Fence::NO_FENCE);
                     if (layerSettings.empty()) {
                         ADD_FAILURE() << "layerSettings was not expected to be empty in "
                                          "setupInsecureREBufferCompositionCommonCallExpectations "
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index 1379665..fa31643 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -80,8 +80,7 @@
                             std::unique_ptr<EventThread>(mEventThread),
                             std::unique_ptr<EventThread>(mSFEventThread),
                             TestableSurfaceFlinger::DefaultDisplayMode{displayId},
-                            TestableSurfaceFlinger::SchedulerCallbackImpl::kMock,
-                            TestableSurfaceFlinger::VsyncTrackerCallbackImpl::kMock);
+                            TestableSurfaceFlinger::SchedulerCallbackImpl::kMock);
 }
 
 void DisplayTransactionTest::injectMockComposer(int virtualDisplayCount) {
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 6671414..f26336a 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -347,7 +347,6 @@
 
     // The HWC active configuration id
     static constexpr hal::HWConfigId HWC_ACTIVE_CONFIG_ID = 2001;
-    static constexpr PowerMode INIT_POWER_MODE = hal::PowerMode::ON;
 
     static void injectPendingHotplugEvent(DisplayTransactionTest* test, Connection connection) {
         test->mFlinger.mutablePendingHotplugEvents().emplace_back(
@@ -355,7 +354,7 @@
     }
 
     // Called by tests to inject a HWC display setup
-    template <bool kInitPowerMode = true>
+    template <hal::PowerMode kPowerMode = hal::PowerMode::ON>
     static void injectHwcDisplayWithNoDefaultCapabilities(DisplayTransactionTest* test) {
         const auto displayId = DisplayVariant::DISPLAY_ID::get();
         ASSERT_FALSE(GpuVirtualDisplayId::tryCast(displayId));
@@ -364,22 +363,37 @@
                 .setHwcDisplayId(HWC_DISPLAY_ID)
                 .setResolution(DisplayVariant::RESOLUTION)
                 .setActiveConfig(HWC_ACTIVE_CONFIG_ID)
-                .setPowerMode(kInitPowerMode ? std::make_optional(INIT_POWER_MODE) : std::nullopt)
+                .setPowerMode(kPowerMode)
                 .inject(&test->mFlinger, test->mComposer);
     }
 
     // Called by tests to inject a HWC display setup
-    template <bool kInitPowerMode = true>
+    //
+    // TODO(b/241285876): The `kExpectSetPowerModeOnce` argument is set to `false` by tests that
+    // power on/off displays several times. Replace those catch-all expectations with `InSequence`
+    // and `RetiresOnSaturation`.
+    //
+    template <hal::PowerMode kPowerMode = hal::PowerMode::ON, bool kExpectSetPowerModeOnce = true>
     static void injectHwcDisplay(DisplayTransactionTest* test) {
-        if constexpr (kInitPowerMode) {
-            EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _))
-                    .WillOnce(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})),
-                                    Return(Error::NONE)));
+        if constexpr (kExpectSetPowerModeOnce) {
+            if constexpr (kPowerMode == hal::PowerMode::ON) {
+                EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _))
+                        .WillOnce(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})),
+                                        Return(Error::NONE)));
+            }
 
-            EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, INIT_POWER_MODE))
+            EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, kPowerMode))
                     .WillOnce(Return(Error::NONE));
+        } else {
+            EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _))
+                    .WillRepeatedly(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})),
+                                          Return(Error::NONE)));
+
+            EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, _))
+                    .WillRepeatedly(Return(Error::NONE));
         }
-        injectHwcDisplayWithNoDefaultCapabilities<kInitPowerMode>(test);
+
+        injectHwcDisplayWithNoDefaultCapabilities<kPowerMode>(test);
     }
 
     static std::shared_ptr<compositionengine::Display> injectCompositionDisplay(
@@ -447,9 +461,11 @@
                     ? IComposerClient::DisplayConnectionType::INTERNAL
                     : IComposerClient::DisplayConnectionType::EXTERNAL;
 
+            using ::testing::AtLeast;
             EXPECT_CALL(*test->mComposer, getDisplayConnectionType(HWC_DISPLAY_ID, _))
-                    .WillOnce(DoAll(SetArgPointee<1>(CONNECTION_TYPE),
-                                    Return(hal::V2_4::Error::NONE)));
+                    .Times(AtLeast(1))
+                    .WillRepeatedly(DoAll(SetArgPointee<1>(CONNECTION_TYPE),
+                                          Return(hal::V2_4::Error::NONE)));
         }
 
         EXPECT_CALL(*test->mComposer, setClientTargetSlotCount(_))
diff --git a/services/surfaceflinger/tests/unittests/DualDisplayTransactionTest.h b/services/surfaceflinger/tests/unittests/DualDisplayTransactionTest.h
new file mode 100644
index 0000000..90e716f
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/DualDisplayTransactionTest.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "DisplayTransactionTestHelpers.h"
+
+namespace android {
+
+template <hal::PowerMode kInnerDisplayPowerMode, hal::PowerMode kOuterDisplayPowerMode,
+          bool kExpectSetPowerModeOnce = true>
+struct DualDisplayTransactionTest : DisplayTransactionTest {
+    static constexpr bool kWithMockScheduler = false;
+    DualDisplayTransactionTest() : DisplayTransactionTest(kWithMockScheduler) {}
+
+    void SetUp() override {
+        injectMockScheduler(kInnerDisplayId);
+
+        {
+            InnerDisplayVariant::injectHwcDisplay<kInnerDisplayPowerMode, kExpectSetPowerModeOnce>(
+                    this);
+
+            auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
+            injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
+            injector.setPowerMode(kInnerDisplayPowerMode);
+            mInnerDisplay = injector.inject();
+        }
+        {
+            OuterDisplayVariant::injectHwcDisplay<kOuterDisplayPowerMode, kExpectSetPowerModeOnce>(
+                    this);
+
+            auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
+            injector.setPowerMode(kOuterDisplayPowerMode);
+            mOuterDisplay = injector.inject();
+        }
+    }
+
+    static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
+    static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
+
+    sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 45db0c5..d5ec654 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -85,6 +85,7 @@
     bool throttleVsync(TimePoint, uid_t) override;
     Period getVsyncPeriod(uid_t) override;
     void resync() override;
+    void onExpectedPresentTimePosted(TimePoint) override;
 
     void setupEventThread();
     sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder,
@@ -107,6 +108,7 @@
                                                       int32_t expectedConfigId,
                                                       nsecs_t expectedVsyncPeriod);
     void expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t);
+    void expectOnExpectedPresentTimePosted(nsecs_t expectedPresentTime);
     void expectUidFrameRateMappingEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                             std::vector<FrameRateOverride>);
 
@@ -131,6 +133,7 @@
             mVSyncCallbackUnregisterRecorder;
     AsyncCallRecorder<void (*)()> mResyncCallRecorder;
     AsyncCallRecorder<void (*)(nsecs_t, uid_t)> mThrottleVsyncCallRecorder;
+    AsyncCallRecorder<void (*)(nsecs_t)> mOnExpectedPresentTimePostedRecorder;
     ConnectionEventRecorder mConnectionEventCallRecorder{0};
     ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0};
 
@@ -190,6 +193,10 @@
     mResyncCallRecorder.recordCall();
 }
 
+void EventThreadTest::onExpectedPresentTimePosted(TimePoint expectedPresentTime) {
+    mOnExpectedPresentTimePostedRecorder.recordCall(expectedPresentTime.ns());
+}
+
 void EventThreadTest::setupEventThread() {
     mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
     mThread = std::make_unique<impl::EventThread>("EventThreadTest", mVsyncSchedule,
@@ -244,6 +251,12 @@
     EXPECT_EQ(uid, std::get<1>(args.value()));
 }
 
+void EventThreadTest::expectOnExpectedPresentTimePosted(nsecs_t expectedPresentTime) {
+    auto args = mOnExpectedPresentTimePostedRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    EXPECT_EQ(expectedPresentTime, std::get<0>(args.value()));
+}
+
 void EventThreadTest::expectVsyncEventReceivedByConnection(
         const char* name, ConnectionEventRecorder& connectionEventRecorder,
         nsecs_t expectedTimestamp, unsigned expectedCount) {
@@ -410,6 +423,7 @@
     onVSyncEvent(123, 456, 789);
     expectThrottleVsyncReceived(456, mConnectionUid);
     expectVsyncEventReceivedByConnection(123, 1u);
+    expectOnExpectedPresentTimePosted(456);
 
     // EventThread is requesting one more callback due to VsyncRequest::SingleSuppressCallback
     expectVSyncCallbackScheduleReceived(true);
@@ -562,16 +576,19 @@
     onVSyncEvent(123, 456, 789);
     expectThrottleVsyncReceived(456, mConnectionUid);
     expectVsyncEventReceivedByConnection(123, 1u);
+    expectOnExpectedPresentTimePosted(456);
 
     // A second event should go to the same places.
     onVSyncEvent(456, 123, 0);
     expectThrottleVsyncReceived(123, mConnectionUid);
     expectVsyncEventReceivedByConnection(456, 2u);
+    expectOnExpectedPresentTimePosted(123);
 
     // A third event should go to the same places.
     onVSyncEvent(789, 777, 111);
     expectThrottleVsyncReceived(777, mConnectionUid);
     expectVsyncEventReceivedByConnection(789, 3u);
+    expectOnExpectedPresentTimePosted(777);
 }
 
 TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) {
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index a5c0657..a25804c 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -104,7 +104,7 @@
 
 TEST_F(HWComposerTest, getActiveMode) {
     // Unknown display.
-    EXPECT_EQ(mHwc.getActiveMode(PhysicalDisplayId::fromPort(0)), std::nullopt);
+    EXPECT_EQ(mHwc.getActiveMode(PhysicalDisplayId::fromPort(0)), ftl::Unexpected(BAD_INDEX));
 
     constexpr hal::HWDisplayId kHwcDisplayId = 2;
     expectHotplugConnect(kHwcDisplayId);
@@ -117,14 +117,20 @@
         EXPECT_CALL(*mHal, getActiveConfig(kHwcDisplayId, _))
                 .WillOnce(Return(HalError::BAD_DISPLAY));
 
-        EXPECT_EQ(mHwc.getActiveMode(info->id), std::nullopt);
+        EXPECT_EQ(mHwc.getActiveMode(info->id), ftl::Unexpected(UNKNOWN_ERROR));
+    }
+    {
+        EXPECT_CALL(*mHal, getActiveConfig(kHwcDisplayId, _))
+                .WillOnce(Return(HalError::BAD_CONFIG));
+
+        EXPECT_EQ(mHwc.getActiveMode(info->id), ftl::Unexpected(NO_INIT));
     }
     {
         constexpr hal::HWConfigId kConfigId = 42;
         EXPECT_CALL(*mHal, getActiveConfig(kHwcDisplayId, _))
                 .WillOnce(DoAll(SetArgPointee<1>(kConfigId), Return(HalError::NONE)));
 
-        EXPECT_EQ(mHwc.getActiveMode(info->id), kConfigId);
+        EXPECT_EQ(mHwc.getActiveMode(info->id).value_opt(), kConfigId);
     }
 }
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 734fddb..01762c1 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -165,12 +165,8 @@
                                                   DisplayModeId(0));
 
     mock::SchedulerCallback mSchedulerCallback;
-    mock::VsyncTrackerCallback mVsyncTrackerCallback;
-
     TestableSurfaceFlinger mFlinger;
-
-    TestableScheduler* mScheduler =
-            new TestableScheduler(mSelector, mFlinger, mSchedulerCallback, mVsyncTrackerCallback);
+    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback);
 };
 
 namespace {
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 9456e37..bc06a31 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -146,11 +146,8 @@
                                                   DisplayModeId(0));
 
     mock::SchedulerCallback mSchedulerCallback;
-
-    mock::VsyncTrackerCallback mVsyncTrackerCallback;
     TestableSurfaceFlinger mFlinger;
-    TestableScheduler* mScheduler =
-            new TestableScheduler(mSelector, mFlinger, mSchedulerCallback, mVsyncTrackerCallback);
+    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback);
 };
 
 namespace {
diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
index 22cfbd8..9fe9ee8 100644
--- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -64,10 +64,8 @@
                                                                               HI_FPS)),
                                                   DisplayModeId(0));
     mock::SchedulerCallback mSchedulerCallback;
-    mock::VsyncTrackerCallback mVsyncTrackerCallback;
     TestableSurfaceFlinger mFlinger;
-    TestableScheduler* mScheduler =
-            new TestableScheduler(mSelector, mFlinger, mSchedulerCallback, mVsyncTrackerCallback);
+    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback);
 };
 
 namespace {
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 13b47ff..3baa48d 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -1237,4 +1237,25 @@
     EXPECT_TRUE(foundInputLayer);
 }
 
+TEST_F(LayerSnapshotTest, canOccludePresentation) {
+    setFlags(12, layer_state_t::eCanOccludePresentation, layer_state_t::eCanOccludePresentation);
+    LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                    .layerLifecycleManager = mLifecycleManager,
+                                    .includeMetadata = false,
+                                    .displays = mFrontEndDisplayInfos,
+                                    .displayChanges = false,
+                                    .globalShadowSettings = globalShadowSettings,
+                                    .supportsBlur = true,
+                                    .supportedLayerGenericMetadata = {},
+                                    .genericLayerMetadataKeyMap = {}};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_EQ(getSnapshot(1)->inputInfo.canOccludePresentation, false);
+
+    // ensure we can set the property on the window info for layer and all its children
+    EXPECT_EQ(getSnapshot(12)->inputInfo.canOccludePresentation, true);
+    EXPECT_EQ(getSnapshot(121)->inputInfo.canOccludePresentation, true);
+    EXPECT_EQ(getSnapshot(1221)->inputInfo.canOccludePresentation, true);
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index 249ed40..f5661fc 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -41,6 +41,7 @@
         return {};
     }
     void sample() override {}
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId) {}
 } gNoOpCompositor;
 
 class TestableMessageQueue : public impl::MessageQueue {
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index 9c66a97..415b0d2 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -74,12 +74,14 @@
 void PowerAdvisorTest::startPowerHintSession(bool returnValidSession) {
     mMockPowerHintSession = ndk::SharedRefBase::make<NiceMock<MockIPowerHintSession>>();
     if (returnValidSession) {
-        ON_CALL(*mMockPowerHalController, createHintSession)
-                .WillByDefault(
-                        Return(HalResult<std::shared_ptr<IPowerHintSession>>::
-                                       fromStatus(binder::Status::ok(), mMockPowerHintSession)));
+        ON_CALL(*mMockPowerHalController, createHintSessionWithConfig)
+                .WillByDefault(DoAll(SetArgPointee<5>(aidl::android::hardware::power::SessionConfig{
+                                             .id = 12}),
+                                     Return(HalResult<std::shared_ptr<IPowerHintSession>>::
+                                                    fromStatus(binder::Status::ok(),
+                                                               mMockPowerHintSession))));
     } else {
-        ON_CALL(*mMockPowerHalController, createHintSession)
+        ON_CALL(*mMockPowerHalController, createHintSessionWithConfig)
                 .WillByDefault(Return(HalResult<std::shared_ptr<IPowerHintSession>>::
                                               fromStatus(binder::Status::ok(), nullptr)));
     }
@@ -283,7 +285,7 @@
 }
 
 TEST_F(PowerAdvisorTest, hintSessionOnlyCreatedOnce) {
-    EXPECT_CALL(*mMockPowerHalController, createHintSession(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mMockPowerHalController, createHintSessionWithConfig(_, _, _, _, _, _)).Times(1);
     mPowerAdvisor->onBootFinished();
     startPowerHintSession();
     mPowerAdvisor->startPowerHintSession({1, 2, 3});
@@ -335,7 +337,7 @@
         return ndk::ScopedAStatus::fromExceptionCode(-127);
     });
 
-    ON_CALL(*mMockPowerHalController, createHintSession)
+    ON_CALL(*mMockPowerHalController, createHintSessionWithConfig)
             .WillByDefault(Return(
                     HalResult<std::shared_ptr<IPowerHintSession>>::
                             fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr)));
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 1e526ba..c03cbd7 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -1624,6 +1624,7 @@
     EXPECT_EQ(120_Hz, actualFrameRateMode.fps);
     EXPECT_EQ(kModeId120, actualFrameRateMode.modePtr->getId());
 
+    // No touch boost, for example a game that uses setFrameRate(30, default compatibility).
     lr1.vote = LayerVoteType::ExplicitCategory;
     lr1.frameRateCategory = FrameRateCategory::HighHint;
     lr1.name = "ExplicitCategory HighHint";
@@ -1652,8 +1653,9 @@
     lr2.frameRateCategory = FrameRateCategory::Low;
     lr2.name = "ExplicitCategory Low";
     actualFrameRateMode = selector.getBestFrameRateMode(layers);
-    EXPECT_EQ(30_Hz, actualFrameRateMode.fps);
-    EXPECT_EQ(kModeId30, actualFrameRateMode.modePtr->getId());
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualFrameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualFrameRateMode.modePtr->getId());
 
     lr1.vote = LayerVoteType::ExplicitCategory;
     lr1.frameRateCategory = FrameRateCategory::HighHint;
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 6986689..10c5848 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -95,10 +95,8 @@
                                                   kDisplay1Mode60->getId());
 
     mock::SchedulerCallback mSchedulerCallback;
-    mock::VsyncTrackerCallback mVsyncTrackerCallback;
     TestableSurfaceFlinger mFlinger;
-    TestableScheduler* mScheduler =
-            new TestableScheduler{mSelector, mFlinger, mSchedulerCallback, mVsyncTrackerCallback};
+    TestableScheduler* mScheduler = new TestableScheduler{mSelector, mFlinger, mSchedulerCallback};
     surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder;
 
     ConnectionHandle mConnectionHandle;
@@ -516,6 +514,7 @@
         }
 
         void sample() override {}
+        void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {}
     } compositor(*mScheduler);
 
     mScheduler->doFrameSignal(compositor, VsyncId(42));
@@ -568,7 +567,7 @@
                                                         frameRate.getPeriodNsecs())}));
     std::shared_ptr<VSyncPredictor> vrrTracker =
             std::make_shared<VSyncPredictor>(kMode, kHistorySize, kMinimumSamplesForPrediction,
-                                             kOutlierTolerancePercent, mVsyncTrackerCallback);
+                                             kOutlierTolerancePercent);
     std::shared_ptr<RefreshRateSelector> vrrSelectorPtr =
             std::make_shared<RefreshRateSelector>(makeModes(kMode), kMode->getId());
     TestableScheduler scheduler{std::make_unique<android::mock::VsyncController>(),
@@ -576,8 +575,7 @@
                                 vrrSelectorPtr,
                                 mFlinger.getFactory(),
                                 mFlinger.getTimeStats(),
-                                mSchedulerCallback,
-                                mVsyncTrackerCallback};
+                                mSchedulerCallback};
 
     scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker);
     vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp
new file mode 100644
index 0000000..f127213
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2024 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"
+
+#include "CommitAndCompositeTest.h"
+
+#define EXPECT_COLOR_MATRIX_CHANGED(current, drawing)               \
+    EXPECT_EQ(current, mFlinger.currentState().colorMatrixChanged); \
+    EXPECT_EQ(drawing, mFlinger.drawingState().colorMatrixChanged);
+
+namespace android {
+
+class ColorMatrixTest : public CommitAndCompositeTest {};
+
+TEST_F(ColorMatrixTest, colorMatrixChanged) {
+    EXPECT_COLOR_MATRIX_CHANGED(true, true);
+    mFlinger.mutableTransactionFlags() |= eTransactionNeeded;
+
+    mFlinger.commitAndComposite();
+    EXPECT_COLOR_MATRIX_CHANGED(false, false);
+
+    mFlinger.setDaltonizerType(ColorBlindnessType::Deuteranomaly);
+    EXPECT_COLOR_MATRIX_CHANGED(true, false);
+
+    mFlinger.commit();
+    EXPECT_COLOR_MATRIX_CHANGED(false, true);
+
+    mFlinger.commitAndComposite();
+    EXPECT_COLOR_MATRIX_CHANGED(false, false);
+}
+
+TEST_F(ColorMatrixTest, colorMatrixChangedAfterDisplayTransaction) {
+    EXPECT_COLOR_MATRIX_CHANGED(true, true);
+    mFlinger.mutableTransactionFlags() |= eTransactionNeeded;
+
+    mFlinger.commitAndComposite();
+    EXPECT_COLOR_MATRIX_CHANGED(false, false);
+
+    mFlinger.createDisplay(String8("Test Display"), false);
+
+    mFlinger.commit();
+    EXPECT_COLOR_MATRIX_CHANGED(false, true);
+
+    mFlinger.commitAndComposite();
+    EXPECT_COLOR_MATRIX_CHANGED(false, false);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 8b16a8a..82b4ad0 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -21,9 +21,13 @@
 #include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/MockDisplayModeSpecs.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/test/FlagUtils.h>
 #include <ftl/fake_guard.h>
 #include <scheduler/Fps.h>
 
+using namespace com::android::graphics::surfaceflinger;
+
 namespace android {
 namespace {
 
@@ -360,6 +364,13 @@
 }
 
 TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) {
+    SET_FLAG_FOR_TEST(flags::connected_display, true);
+
+    // For the inner display, this is handled by setupHwcHotplugCallExpectations.
+    EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
+            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
+                            Return(hal::V2_4::Error::NONE)));
+
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
@@ -429,6 +440,12 @@
 }
 
 TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) {
+    SET_FLAG_FOR_TEST(flags::connected_display, true);
+
+    // For the inner display, this is handled by setupHwcHotplugCallExpectations.
+    EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
+            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
+                            Return(hal::V2_4::Error::NONE)));
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
@@ -512,6 +529,13 @@
 }
 
 TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) {
+    SET_FLAG_FOR_TEST(flags::connected_display, true);
+
+    // For the inner display, this is handled by setupHwcHotplugCallExpectations.
+    EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
+            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
+                            Return(hal::V2_4::Error::NONE)));
+
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
index 844b96c..19f8deb 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
@@ -17,43 +17,21 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
-#include "DisplayTransactionTestHelpers.h"
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/test/FlagUtils.h>
+#include "DualDisplayTransactionTest.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+using namespace com::android::graphics::surfaceflinger;
+
 namespace android {
 namespace {
 
-struct FoldableTest : DisplayTransactionTest {
-    static constexpr bool kWithMockScheduler = false;
-    FoldableTest() : DisplayTransactionTest(kWithMockScheduler) {}
-
-    void SetUp() override {
-        injectMockScheduler(kInnerDisplayId);
-
-        // Inject inner and outer displays with uninitialized power modes.
-        constexpr bool kInitPowerMode = false;
-        {
-            InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
-            auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
-            injector.setPowerMode(std::nullopt);
-            injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
-            mInnerDisplay = injector.inject();
-        }
-        {
-            OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
-            auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
-            injector.setPowerMode(std::nullopt);
-            mOuterDisplay = injector.inject();
-        }
-    }
-
-    static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
-    static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
-
-    sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
-};
+constexpr bool kExpectSetPowerModeOnce = false;
+struct FoldableTest : DualDisplayTransactionTest<hal::PowerMode::OFF, hal::PowerMode::OFF,
+                                                 kExpectSetPowerModeOnce> {};
 
 TEST_F(FoldableTest, promotesPacesetterOnBoot) {
     // When the device boots, the inner display should be the pacesetter.
@@ -189,6 +167,7 @@
 }
 
 TEST_F(FoldableTest, requestVsyncOnPowerOn) {
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
     EXPECT_CALL(mFlinger.scheduler()->mockRequestHardwareVsync, Call(kInnerDisplayId, true))
             .Times(1);
     EXPECT_CALL(mFlinger.scheduler()->mockRequestHardwareVsync, Call(kOuterDisplayId, true))
@@ -199,6 +178,7 @@
 }
 
 TEST_F(FoldableTest, disableVsyncOnPowerOffPacesetter) {
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
     // When the device boots, the inner display should be the pacesetter.
     ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kInnerDisplayId);
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
index 29acfaa..4e9fba7 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
@@ -17,79 +17,16 @@
 #undef LOG_TAG
 #define LOG_TAG "SurfaceFlingerGetDisplayStatsTest"
 
-#include <compositionengine/Display.h>
-#include <compositionengine/mock/DisplaySurface.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-#include <renderengine/mock/RenderEngine.h>
 #include <ui/DisplayStatInfo.h>
-#include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockComposer.h"
-#include "mock/DisplayHardware/MockPowerAdvisor.h"
-#include "mock/MockTimeStats.h"
-#include "mock/system/window/MockNativeWindow.h"
 
-using namespace android;
-using namespace testing;
+#include "CommitAndCompositeTest.h"
 
 namespace android {
 namespace {
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
-using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
 
-constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
-constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
-constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
-constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
-
-class SurfaceFlingerGetDisplayStatsTest : public Test {
-public:
-    void SetUp() override;
-
-protected:
-    TestableSurfaceFlinger mFlinger;
-    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
-    sp<DisplayDevice> mDisplay;
-    sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
-            sp<compositionengine::mock::DisplaySurface>::make();
-    sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
-    mock::TimeStats* mTimeStats = new mock::TimeStats();
-    Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
-    Hwc2::mock::Composer* mComposer = nullptr;
-};
-
-void SurfaceFlingerGetDisplayStatsTest::SetUp() {
-    mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID});
-    mComposer = new Hwc2::mock::Composer();
-    mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
-    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
-    mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
-    mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
-    mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
-    static constexpr bool kIsPrimary = true;
-    FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
-            .setPowerMode(hal::PowerMode::ON)
-            .inject(&mFlinger, mComposer);
-    auto compostionEngineDisplayArgs =
-            compositionengine::DisplayCreationArgsBuilder()
-                    .setId(DEFAULT_DISPLAY_ID)
-                    .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
-                    .setPowerAdvisor(mPowerAdvisor)
-                    .setName("injected display")
-                    .build();
-    auto compositionDisplay =
-            compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
-                                                   std::move(compostionEngineDisplayArgs));
-    mDisplay =
-            FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
-                                      ui::DisplayConnectionType::Internal, HWC_DISPLAY, kIsPrimary)
-                    .setDisplaySurface(mDisplaySurface)
-                    .setNativeWindow(mNativeWindow)
-                    .setPowerMode(hal::PowerMode::ON)
-                    .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector())
-                    .skipRegisterDisplay()
-                    .inject();
-}
+struct SurfaceFlingerGetDisplayStatsTest : CommitAndCompositeTest {};
 
 // TODO (b/277364366): Clients should be updated to pass in the display they want.
 TEST_F(SurfaceFlingerGetDisplayStatsTest, nullptrSucceeds) {
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
index 1583f64..eaf4684 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
@@ -17,66 +17,49 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
-#include "DisplayTransactionTestHelpers.h"
+#include "DualDisplayTransactionTest.h"
 
 namespace android {
 namespace {
 
-class InitializeDisplaysTest : public DisplayTransactionTest {};
+constexpr bool kExpectSetPowerModeOnce = false;
+struct InitializeDisplaysTest : DualDisplayTransactionTest<hal::PowerMode::OFF, hal::PowerMode::OFF,
+                                                           kExpectSetPowerModeOnce> {};
 
-TEST_F(InitializeDisplaysTest, commitsPrimaryDisplay) {
-    using Case = SimplePrimaryDisplayCase;
-
-    // --------------------------------------------------------------------
-    // Preconditions
-
-    // A primary display is set up
-    Case::Display::injectHwcDisplay(this);
-    auto primaryDisplay = Case::Display::makeFakeExistingDisplayInjector(this);
-    primaryDisplay.inject();
-
-    // --------------------------------------------------------------------
-    // Call Expectations
-
-    // We expect a call to get the active display config.
-    Case::Display::setupHwcGetActiveConfigCallExpectations(this);
-
-    // We expect a scheduled commit for the display transaction.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+TEST_F(InitializeDisplaysTest, initializesDisplays) {
+    // Scheduled by the display transaction, and by powering on each display.
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(3);
 
     EXPECT_CALL(static_cast<mock::VSyncTracker&>(
                         mFlinger.scheduler()->getVsyncSchedule()->getTracker()),
                 nextAnticipatedVSyncTimeFrom(_, _))
             .WillRepeatedly(Return(0));
 
-    // --------------------------------------------------------------------
-    // Invocation
-
     FTL_FAKE_GUARD(kMainThreadContext, mFlinger.initializeDisplays());
 
-    // --------------------------------------------------------------------
-    // Postconditions
+    for (const auto& display : {mInnerDisplay, mOuterDisplay}) {
+        const auto token = display->getDisplayToken().promote();
+        ASSERT_TRUE(token);
 
-    // The primary display should have a current state
-    ASSERT_TRUE(hasCurrentDisplayState(primaryDisplay.token()));
-    const auto& primaryDisplayState = getCurrentDisplayState(primaryDisplay.token());
+        ASSERT_TRUE(hasCurrentDisplayState(token));
+        const auto& state = getCurrentDisplayState(token);
 
-    // The primary display state should be reset
-    EXPECT_EQ(ui::DEFAULT_LAYER_STACK, primaryDisplayState.layerStack);
-    EXPECT_EQ(ui::ROTATION_0, primaryDisplayState.orientation);
-    EXPECT_EQ(Rect::INVALID_RECT, primaryDisplayState.orientedDisplaySpaceRect);
-    EXPECT_EQ(Rect::INVALID_RECT, primaryDisplayState.layerStackSpaceRect);
+        const ui::LayerStack expectedLayerStack = display == mInnerDisplay
+                ? ui::DEFAULT_LAYER_STACK
+                : ui::LayerStack::fromValue(ui::DEFAULT_LAYER_STACK.id + 1);
 
-    // The width and height should both be zero
-    EXPECT_EQ(0u, primaryDisplayState.width);
-    EXPECT_EQ(0u, primaryDisplayState.height);
+        EXPECT_EQ(expectedLayerStack, state.layerStack);
+        EXPECT_EQ(ui::ROTATION_0, state.orientation);
+        EXPECT_EQ(Rect::INVALID_RECT, state.orientedDisplaySpaceRect);
+        EXPECT_EQ(Rect::INVALID_RECT, state.layerStackSpaceRect);
 
-    // The display should be set to PowerMode::ON
-    ASSERT_TRUE(hasDisplayDevice(primaryDisplay.token()));
-    auto displayDevice = primaryDisplay.mutableDisplayDevice();
-    EXPECT_EQ(PowerMode::ON, displayDevice->getPowerMode());
+        EXPECT_EQ(0u, state.width);
+        EXPECT_EQ(0u, state.height);
 
-    // The display transaction needed flag should be set.
+        ASSERT_TRUE(hasDisplayDevice(token));
+        EXPECT_EQ(PowerMode::ON, getDisplayDevice(token).getPowerMode());
+    }
+
     EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
 }
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
index 7206e29..91b9018 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
@@ -17,31 +17,81 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
+#include <gui/SurfaceComposerClient.h>
 #include "DisplayTransactionTestHelpers.h"
 
 namespace android {
 
 using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using android::hardware::graphics::composer::V2_1::Error;
 
 class NotifyExpectedPresentTest : public DisplayTransactionTest {
 public:
     void SetUp() override {
-        mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this).inject();
-        FakeHwcDisplayInjector(mDisplay->getPhysicalId(), hal::DisplayType::PHYSICAL, kIsPrimary)
+        const auto display = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this).inject();
+        mPhysicalDisplayId = display->getPhysicalId();
+        FakeHwcDisplayInjector(mPhysicalDisplayId, hal::DisplayType::PHYSICAL, /*isPrimary=*/true)
                 .setPowerMode(hal::PowerMode::ON)
                 .inject(&mFlinger, mComposer);
+
+        ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(mPhysicalDisplayId,
+                                                                      TimePoint::fromNs(0),
+                                                                      kFps60Hz));
+        mCompositor = std::make_unique<Compositor>(mPhysicalDisplayId, mFlinger);
     }
 
 protected:
-    sp<DisplayDevice> mDisplay;
-    static constexpr bool kIsPrimary = true;
-    static constexpr hal::HWDisplayId HWC_DISPLAY_ID =
-            FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
-};
+    struct Compositor final : ICompositor {
+        explicit Compositor(PhysicalDisplayId displayId, TestableSurfaceFlinger& surfaceFlinger)
+              : displayId(displayId), surfaceFlinger(surfaceFlinger) {}
 
-TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) {
-    const auto physicDisplayId = mDisplay->getPhysicalId();
-    auto expectedPresentTime = systemTime() + ms2ns(10);
+        void sendNotifyExpectedPresentHint(PhysicalDisplayId id) override {
+            surfaceFlinger.sendNotifyExpectedPresentHint(id);
+        }
+
+        bool commit(PhysicalDisplayId, const scheduler::FrameTargets&) override {
+            return committed;
+        }
+
+        CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId,
+                                             const scheduler::FrameTargeters& targeters) override {
+            pacesetterIds.composite = pacesetterId;
+            CompositeResultsPerDisplay results;
+
+            for (const auto& [id, targeter] : targeters) {
+                vsyncIds.composite.emplace_back(id, targeter->target().vsyncId());
+                surfaceFlinger.resetNotifyExpectedPresentHintState(pacesetterId);
+                results.try_emplace(id,
+                                    CompositeResult{.compositionCoverage =
+                                                            CompositionCoverage::Hwc});
+            }
+
+            return results;
+        }
+
+        void sample() override {}
+        void configure() override {}
+
+        struct {
+            PhysicalDisplayId commit;
+            PhysicalDisplayId composite;
+        } pacesetterIds;
+
+        using VsyncIds = std::vector<std::pair<PhysicalDisplayId, VsyncId>>;
+        struct {
+            VsyncIds commit;
+            VsyncIds composite;
+        } vsyncIds;
+
+        bool committed = true;
+        PhysicalDisplayId displayId;
+        TestableSurfaceFlinger& surfaceFlinger;
+    };
+
+    PhysicalDisplayId mPhysicalDisplayId;
+    std::unique_ptr<Compositor> mCompositor;
+    static constexpr hal::HWDisplayId kHwcDisplayId =
+            FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
     static constexpr Fps kFps60Hz = 60_Hz;
     static constexpr int32_t kFrameInterval5HzNs = static_cast<Fps>(5_Hz).getPeriodNsecs();
     static constexpr int32_t kFrameInterval60HzNs = kFps60Hz.getPeriodNsecs();
@@ -49,89 +99,171 @@
     static constexpr Period kVsyncPeriod =
             Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
     static constexpr Period kTimeoutNs = Period::fromNs(kFrameInterval5HzNs);
-    static constexpr auto kLastExpectedPresentTimestamp = TimePoint::fromNs(0);
+};
 
-    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId,
-                                                                  kLastExpectedPresentTimestamp,
-                                                                  kFps60Hz));
-
-    {
-        // Very first ExpectedPresent after idle, no previous timestamp
-        EXPECT_CALL(*mComposer,
-                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
-                                          kFrameInterval60HzNs))
-                .WillOnce(Return(Error::NONE));
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
-                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
-                                                 kTimeoutNs);
-    }
-    {
-        // Absent timeoutNs
+TEST_F(NotifyExpectedPresentTest, noNotifyExpectedPresentHintCall_absentTimeout) {
+    auto expectedPresentTime = systemTime() + ms2ns(10);
+    ASSERT_NO_FATAL_FAILURE(
+            mFlinger.setNotifyExpectedPresentData(mPhysicalDisplayId,
+                                                  TimePoint::fromNs(expectedPresentTime),
+                                                  kFps60Hz));
+    EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+    for (int i = 0; i < 5; i++) {
         expectedPresentTime += 2 * kFrameInterval5HzNs;
-        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  /*timeoutOpt*/ std::nullopt);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
     }
+}
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentHint_zeroTimeout) {
+    auto expectedPresentTime = systemTime() + ms2ns(10);
     {
-        // Timeout is 0
-        expectedPresentTime += kFrameInterval60HzNs;
+        // Very first ExpectedPresent after idle, no previous timestamp.
         EXPECT_CALL(*mComposer,
-                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
-                                          kFrameInterval60HzNs))
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
                 .WillOnce(Return(Error::NONE));
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
-                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
-                                                 Period::fromNs(0));
-    }
-    {
-        // ExpectedPresent is after the timeoutNs
-        expectedPresentTime += 2 * kFrameInterval5HzNs;
-        EXPECT_CALL(*mComposer,
-                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
-                                          kFrameInterval60HzNs))
-                .WillOnce(Return(Error::NONE));
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  kTimeoutNs);
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+
+        // Present frame
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        // Present happens and NotifyExpectedPresentHintStatus is start.
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
+    }
+    {
+        mCompositor->committed = false;
+        expectedPresentTime += kFrameInterval60HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 Period::fromNs(0));
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        // Hint sent
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+    }
+    {
+        expectedPresentTime += kFrameInterval60HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 Period::fromNs(0));
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        // Hint is executed
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+    }
+}
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) {
+    auto expectedPresentTime = systemTime() + ms2ns(10);
+    {
+        // Very first ExpectedPresent after idle, no previous timestamp
+        mCompositor->committed = false;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+    }
+    {
+        // ExpectedPresentTime is after the timeoutNs
+        mCompositor->committed = true;
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        // Present happens notifyExpectedPresentHintStatus is Start
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
+
+        // Another expectedPresent after timeout
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
     }
     {
         // ExpectedPresent has not changed
-        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
     }
     {
-        // ExpectedPresent is after the last reported ExpectedPresent.
+        // ExpectedPresent is after the last reported ExpectedPresent and within timeout.
         expectedPresentTime += kFrameInterval60HzNs;
-        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintStatusIsStart(mPhysicalDisplayId));
     }
     {
         // ExpectedPresent is before the last reported ExpectedPresent but after the timeoutNs,
         // representing we changed our decision and want to present earlier than previously
         // reported.
+        mCompositor->committed = false;
         expectedPresentTime -= kFrameInterval120HzNs;
         EXPECT_CALL(*mComposer,
-                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
-                                          kFrameInterval60HzNs))
+                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
                 .WillOnce(Return(Error::NONE));
-        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
                                                  TimePoint::fromNs(expectedPresentTime), kFps60Hz,
                                                  kTimeoutNs);
+        EXPECT_TRUE(
+                mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime));
+        ASSERT_TRUE(mFlinger.verifyHintIsScheduledOnPresent(mPhysicalDisplayId));
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+        ASSERT_TRUE(mFlinger.verifyHintIsSent(mPhysicalDisplayId));
     }
 }
 
 TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentRenderRateChanged) {
-    const auto physicDisplayId = mDisplay->getPhysicalId();
     const auto now = systemTime();
     auto expectedPresentTime = now;
     static constexpr Period kTimeoutNs = Period::fromNs(static_cast<Fps>(1_Hz).getPeriodNsecs());
 
-    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId,
+    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(mPhysicalDisplayId,
                                                                   TimePoint::fromNs(now),
                                                                   Fps::fromValue(0)));
     static constexpr int32_t kFrameIntervalNs120Hz = static_cast<Fps>(120_Hz).getPeriodNsecs();
@@ -147,7 +279,7 @@
 
     struct FrameRateIntervalTestData {
         int32_t frameIntervalNs;
-        bool callExpectedPresent;
+        bool callNotifyExpectedPresentHint;
     };
     const std::vector<FrameRateIntervalTestData> frameIntervals = {
             {kFrameIntervalNs60Hz, true},  {kFrameIntervalNs96Hz, true},
@@ -159,21 +291,35 @@
             {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs120Hz, true},
     };
 
-    for (const auto& [frameIntervalNs, callExpectedPresent] : frameIntervals) {
-        {
-            expectedPresentTime += frameIntervalNs;
-            if (callExpectedPresent) {
-                EXPECT_CALL(*mComposer,
-                            notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
-                                                  frameIntervalNs))
-                        .WillOnce(Return(Error::NONE));
-            } else {
-                EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
-            }
-            mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
-                                                     TimePoint::fromNs(expectedPresentTime),
-                                                     Fps::fromPeriodNsecs(frameIntervalNs),
-                                                     kTimeoutNs);
+    for (size_t i = 0; i < frameIntervals.size(); i++) {
+        const auto& [frameIntervalNs, callNotifyExpectedPresentHint] = frameIntervals[i];
+        expectedPresentTime += frameIntervalNs;
+        mFlinger.notifyExpectedPresentIfRequired(mPhysicalDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime),
+                                                 Fps::fromPeriodNsecs(frameIntervalNs), kTimeoutNs);
+
+        if (callNotifyExpectedPresentHint) {
+            mCompositor->committed = false;
+            ASSERT_TRUE(mFlinger.verifyHintIsScheduledOnPresent(mPhysicalDisplayId))
+                    << "Hint not scheduled for frameInterval " << frameIntervalNs << " at index "
+                    << i;
+            EXPECT_CALL(*mComposer,
+                        notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, frameIntervalNs))
+                    .WillOnce(Return(Error::NONE));
+        } else {
+            // Only lastExpectedPresentTime is updated
+            EXPECT_TRUE(
+                    mFlinger.verifyLastExpectedPresentTime(mPhysicalDisplayId, expectedPresentTime))
+                    << "LastExpectedPresentTime for frameInterval " << frameIntervalNs
+                    << "at index " << i << " did not match for frameInterval " << frameIntervalNs;
+            EXPECT_CALL(*mComposer, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
+        }
+        mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
+
+        if (callNotifyExpectedPresentHint) {
+            // Present resumes the calls to the notifyExpectedPresentHint.
+            mCompositor->committed = true;
+            mFlinger.scheduler()->doFrameSignal(*mCompositor, VsyncId{42});
         }
     }
 }
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
index b80cb66..c3934e6 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -17,84 +17,18 @@
 #undef LOG_TAG
 #define LOG_TAG "SurfaceFlingerPowerHintTest"
 
-#include <compositionengine/Display.h>
-#include <compositionengine/mock/DisplaySurface.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <renderengine/mock/RenderEngine.h>
-#include <algorithm>
 #include <chrono>
-#include <memory>
-#include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockComposer.h"
-#include "mock/DisplayHardware/MockPowerAdvisor.h"
-#include "mock/MockTimeStats.h"
-#include "mock/system/window/MockNativeWindow.h"
 
-using namespace android;
-using namespace android::Hwc2::mock;
-using namespace android::hardware::power;
+#include "CommitAndCompositeTest.h"
+
 using namespace std::chrono_literals;
-using namespace testing;
+using testing::_;
+using testing::Return;
 
 namespace android {
 namespace {
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
-using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
 
-constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
-constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
-constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
-constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
-
-class SurfaceFlingerPowerHintTest : public Test {
-public:
-    void SetUp() override;
-
-protected:
-    TestableSurfaceFlinger mFlinger;
-    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
-    sp<DisplayDevice> mDisplay;
-    sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
-            sp<compositionengine::mock::DisplaySurface>::make();
-    sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
-    mock::TimeStats* mTimeStats = new mock::TimeStats();
-    Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
-    Hwc2::mock::Composer* mComposer = nullptr;
-};
-
-void SurfaceFlingerPowerHintTest::SetUp() {
-    mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID});
-    mComposer = new Hwc2::mock::Composer();
-    mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
-    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
-    mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
-    mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
-    mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
-    static constexpr bool kIsPrimary = true;
-    FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
-            .setPowerMode(hal::PowerMode::ON)
-            .inject(&mFlinger, mComposer);
-    auto compostionEngineDisplayArgs =
-            compositionengine::DisplayCreationArgsBuilder()
-                    .setId(DEFAULT_DISPLAY_ID)
-                    .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
-                    .setPowerAdvisor(mPowerAdvisor)
-                    .setName("injected display")
-                    .build();
-    auto compositionDisplay =
-            compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
-                                                   std::move(compostionEngineDisplayArgs));
-    mDisplay =
-            FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
-                                      ui::DisplayConnectionType::Internal, HWC_DISPLAY, kIsPrimary)
-                    .setDisplaySurface(mDisplaySurface)
-                    .setNativeWindow(mNativeWindow)
-                    .setPowerMode(hal::PowerMode::ON)
-                    .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector())
-                    .skipRegisterDisplay()
-                    .inject();
-}
+class SurfaceFlingerPowerHintTest : public CommitAndCompositeTest {};
 
 TEST_F(SurfaceFlingerPowerHintTest, sendDurationsIncludingHwcWaitTime) {
     ON_CALL(*mPowerAdvisor, usePowerHintSession()).WillByDefault(Return(true));
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index 15fe600..83e2f98 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -17,11 +17,15 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/test/FlagUtils.h>
 #include "DisplayTransactionTestHelpers.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+using namespace com::android::graphics::surfaceflinger;
+
 namespace android {
 namespace {
 
@@ -453,6 +457,7 @@
 }
 
 TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToOnExternalDisplay) {
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
     transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOffToOnVariant>>();
 }
 
@@ -461,6 +466,7 @@
 }
 
 TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToOffExternalDisplay) {
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
     transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOnToOffVariant>>();
 }
 
@@ -473,6 +479,7 @@
 }
 
 TEST_F(SetPowerModeInternalTest, transitionsDisplayFromDozeSuspendToDozeExternalDisplay) {
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
     transitionDisplayCommon<ExternalDisplayPowerCase<TransitionDozeSuspendToDozeVariant>>();
 }
 
@@ -481,10 +488,12 @@
 }
 
 TEST_F(SetPowerModeInternalTest, transitionsDisplayFromDozeSuspendToOnExternalDisplay) {
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
     transitionDisplayCommon<ExternalDisplayPowerCase<TransitionDozeSuspendToOnVariant>>();
 }
 
 TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToDozeSuspendExternalDisplay) {
+    SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
     transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOnToDozeSuspendVariant>>();
 }
 
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.cpp b/services/surfaceflinger/tests/unittests/TestableScheduler.cpp
index e0b7366..7b92a5b 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.cpp
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.cpp
@@ -21,11 +21,10 @@
 
 TestableScheduler::TestableScheduler(RefreshRateSelectorPtr selectorPtr,
                                      TestableSurfaceFlinger& testableSurfaceFlinger,
-                                     ISchedulerCallback& callback,
-                                     IVsyncTrackerCallback& vsyncTrackerCallback)
+                                     ISchedulerCallback& callback)
       : TestableScheduler(std::make_unique<android::mock::VsyncController>(),
                           std::make_shared<android::mock::VSyncTracker>(), std::move(selectorPtr),
                           testableSurfaceFlinger.getFactory(),
-                          testableSurfaceFlinger.getTimeStats(), callback, vsyncTrackerCallback) {}
+                          testableSurfaceFlinger.getTimeStats(), callback) {}
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 6213713..25a85df 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -41,18 +41,16 @@
 class TestableScheduler : public Scheduler, private ICompositor {
 public:
     TestableScheduler(RefreshRateSelectorPtr selectorPtr,
-                      TestableSurfaceFlinger& testableSurfaceFlinger, ISchedulerCallback& callback,
-                      IVsyncTrackerCallback& vsyncTrackerCallback);
+                      TestableSurfaceFlinger& testableSurfaceFlinger, ISchedulerCallback& callback);
 
     TestableScheduler(std::unique_ptr<VsyncController> controller,
                       std::shared_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
                       surfaceflinger::Factory& factory, TimeStats& timeStats,
-                      ISchedulerCallback& schedulerCallback,
-                      IVsyncTrackerCallback& vsyncTrackerCallback)
+                      ISchedulerCallback& schedulerCallback)
           : Scheduler(*this, schedulerCallback,
                       (FeatureFlags)Feature::kContentDetection |
                               Feature::kSmallDirtyContentDetection,
-                      factory, selectorPtr->getActiveMode().fps, timeStats, vsyncTrackerCallback) {
+                      factory, selectorPtr->getActiveMode().fps, timeStats) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
                         std::move(tracker));
@@ -210,6 +208,7 @@
         return {};
     }
     void sample() override {}
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {}
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 4e0b5af..46a079c 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -53,7 +53,6 @@
 #include "mock/MockFrameTimeline.h"
 #include "mock/MockFrameTracer.h"
 #include "mock/MockSchedulerCallback.h"
-#include "mock/MockVsyncTrackerCallback.h"
 #include "mock/system/window/MockNativeWindow.h"
 
 #include "Scheduler/VSyncTracker.h"
@@ -205,8 +204,6 @@
 
     enum class SchedulerCallbackImpl { kNoOp, kMock };
 
-    enum class VsyncTrackerCallbackImpl { kNoOp, kMock };
-
     struct DefaultDisplayMode {
         // The ID of the injected RefreshRateSelector and its default display mode.
         PhysicalDisplayId displayId;
@@ -220,14 +217,13 @@
 
     TimeStats& getTimeStats() { return *mFlinger->mTimeStats; }
 
-    void setupScheduler(
-            std::unique_ptr<scheduler::VsyncController> vsyncController,
-            std::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
-            std::unique_ptr<EventThread> appEventThread, std::unique_ptr<EventThread> sfEventThread,
-            DisplayModesVariant modesVariant,
-            SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp,
-            VsyncTrackerCallbackImpl vsyncTrackerCallbackImpl = VsyncTrackerCallbackImpl::kNoOp,
-            bool useNiceMock = false) {
+    void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
+                        std::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
+                        std::unique_ptr<EventThread> appEventThread,
+                        std::unique_ptr<EventThread> sfEventThread,
+                        DisplayModesVariant modesVariant,
+                        SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp,
+                        bool useNiceMock = false) {
         RefreshRateSelectorPtr selectorPtr = ftl::match(
                 modesVariant,
                 [](DefaultDisplayMode arg) {
@@ -245,12 +241,6 @@
                 ? static_cast<ISchedulerCallback&>(mNoOpSchedulerCallback)
                 : static_cast<ISchedulerCallback&>(mSchedulerCallback);
 
-        using VsyncTrackerCallback = scheduler::IVsyncTrackerCallback;
-        VsyncTrackerCallback& vsyncTrackerCallback =
-                vsyncTrackerCallbackImpl == VsyncTrackerCallbackImpl::kNoOp
-                ? static_cast<VsyncTrackerCallback&>(mNoOpVsyncTrackerCallback)
-                : static_cast<VsyncTrackerCallback&>(mVsyncTrackerCallback);
-
         if (useNiceMock) {
             mScheduler =
                     new testing::NiceMock<scheduler::TestableScheduler>(std::move(vsyncController),
@@ -258,14 +248,12 @@
                                                                         std::move(selectorPtr),
                                                                         mFactory,
                                                                         *mFlinger->mTimeStats,
-                                                                        schedulerCallback,
-                                                                        vsyncTrackerCallback);
+                                                                        schedulerCallback);
         } else {
             mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
                                                           std::move(vsyncTracker),
                                                           std::move(selectorPtr), mFactory,
-                                                          *mFlinger->mTimeStats, schedulerCallback,
-                                                          vsyncTrackerCallback);
+                                                          *mFlinger->mTimeStats, schedulerCallback);
         }
 
         mScheduler->initVsync(*mTokenManager, 0ms);
@@ -307,8 +295,7 @@
         EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_, _)).WillRepeatedly(Return(0));
         setupScheduler(std::move(vsyncController), std::move(vsyncTracker), std::move(eventThread),
                        std::move(sfEventThread), DefaultDisplayMode{options.displayId},
-                       SchedulerCallbackImpl::kNoOp, VsyncTrackerCallbackImpl::kNoOp,
-                       options.useNiceMock);
+                       SchedulerCallbackImpl::kNoOp, options.useNiceMock);
     }
 
     void resetScheduler(scheduler::Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); }
@@ -698,6 +685,36 @@
                                                   frameInterval, timeoutOpt);
     }
 
+    void sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        mFlinger->sendNotifyExpectedPresentHint(displayId);
+    }
+
+    bool verifyHintIsScheduledOnPresent(PhysicalDisplayId displayId) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus ==
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::ScheduleOnPresent;
+    }
+
+    bool verifyHintIsSent(PhysicalDisplayId displayId) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus ==
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::Sent;
+    }
+
+    bool verifyHintStatusIsStart(PhysicalDisplayId displayId) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus ==
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::Start;
+    }
+
+    bool verifyHintStatusIsScheduledOnTx(PhysicalDisplayId displayId) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus ==
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::ScheduleOnTx;
+    }
+
+    bool verifyLastExpectedPresentTime(PhysicalDisplayId displayId, nsecs_t expectedPresentTime) {
+        return mFlinger->mNotifyExpectedPresentMap.at(displayId)
+                       .lastExpectedPresentTimestamp.ns() == expectedPresentTime;
+    }
+
     void setNotifyExpectedPresentData(PhysicalDisplayId displayId,
                                       TimePoint lastExpectedPresentTimestamp,
                                       Fps lastFrameInterval) {
@@ -706,6 +723,11 @@
         displayData.lastFrameInterval = lastFrameInterval;
     }
 
+    void resetNotifyExpectedPresentHintState(PhysicalDisplayId displayId) {
+        mFlinger->mNotifyExpectedPresentMap.at(displayId).hintStatus =
+                SurfaceFlinger::NotifyExpectedPresentHintStatus::Start;
+    }
+
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
         // not report a leaked object, since the SurfaceFlinger instance may
@@ -750,7 +772,6 @@
         static constexpr int32_t DEFAULT_CONFIG_GROUP = 7;
         static constexpr int32_t DEFAULT_DPI = 320;
         static constexpr hal::HWConfigId DEFAULT_ACTIVE_CONFIG = 0;
-        static constexpr hal::PowerMode DEFAULT_POWER_MODE = hal::PowerMode::ON;
 
         FakeHwcDisplayInjector(HalDisplayId displayId, hal::DisplayType hwcDisplayType,
                                bool isPrimary)
@@ -793,7 +814,7 @@
             return *this;
         }
 
-        auto& setPowerMode(std::optional<hal::PowerMode> mode) {
+        auto& setPowerMode(hal::PowerMode mode) {
             mPowerMode = mode;
             return *this;
         }
@@ -817,9 +838,7 @@
                                                          mHwcDisplayType);
             display->mutableIsConnected() = true;
 
-            if (mPowerMode) {
-                display->setPowerMode(*mPowerMode);
-            }
+            display->setPowerMode(mPowerMode);
 
             flinger->mutableHwcDisplayData()[mDisplayId].hwcDisplay = std::move(display);
 
@@ -885,7 +904,7 @@
         int32_t mDpiY = DEFAULT_DPI;
         int32_t mConfigGroup = DEFAULT_CONFIG_GROUP;
         hal::HWConfigId mActiveConfig = DEFAULT_ACTIVE_CONFIG;
-        std::optional<hal::PowerMode> mPowerMode = DEFAULT_POWER_MODE;
+        hal::PowerMode mPowerMode = hal::PowerMode::ON;
         const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>*
                 mCapabilities = nullptr;
     };
@@ -962,7 +981,7 @@
             return *this;
         }
 
-        auto& setPowerMode(std::optional<hal::PowerMode> mode) {
+        auto& setPowerMode(hal::PowerMode mode) {
             mCreationArgs.initialPowerMode = mode;
             return *this;
         }
@@ -1102,8 +1121,6 @@
     sp<SurfaceFlinger> mFlinger;
     scheduler::mock::SchedulerCallback mSchedulerCallback;
     scheduler::mock::NoOpSchedulerCallback mNoOpSchedulerCallback;
-    scheduler::mock::VsyncTrackerCallback mVsyncTrackerCallback;
-    scheduler::mock::NoOpVsyncTrackerCallback mNoOpVsyncTrackerCallback;
     std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
     scheduler::TestableScheduler* mScheduler = nullptr;
     Hwc2::mock::PowerAdvisor mPowerAdvisor;
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index d952b70..b9f3d70 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -26,7 +26,6 @@
 #include <common/test/FlagUtils.h>
 #include "Scheduler/VSyncPredictor.h"
 #include "mock/DisplayHardware/MockDisplayMode.h"
-#include "mock/MockVsyncTrackerCallback.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -82,14 +81,13 @@
     nsecs_t mNow = 0;
     nsecs_t mPeriod = 1000;
     ftl::NonNull<DisplayModePtr> mMode = displayMode(mPeriod);
-    scheduler::mock::VsyncTrackerCallback mVsyncTrackerCallback;
     static constexpr size_t kHistorySize = 10;
     static constexpr size_t kMinimumSamplesForPrediction = 6;
     static constexpr size_t kOutlierTolerancePercent = 25;
     static constexpr nsecs_t mMaxRoundingError = 100;
 
     VSyncPredictor tracker{mMode, kHistorySize, kMinimumSamplesForPrediction,
-                           kOutlierTolerancePercent, mVsyncTrackerCallback};
+                           kOutlierTolerancePercent};
 };
 
 TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
@@ -410,8 +408,7 @@
 // See b/151146131
 TEST_F(VSyncPredictorTest, hasEnoughPrecision) {
     const auto mode = displayMode(mPeriod);
-    VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent,
-                           mVsyncTrackerCallback};
+    VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent};
     std::vector<nsecs_t> const simulatedVsyncs{840873348817, 840890049444, 840906762675,
                                                840923581635, 840940161584, 840956868096,
                                                840973702473, 840990256277, 841007116851,
@@ -658,48 +655,6 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
 }
 
-TEST_F(VSyncPredictorTest, vsyncTrackerCallback) {
-    SET_FLAG_FOR_TEST(flags::vrr_config, true);
-
-    const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
-    NotifyExpectedPresentConfig notifyExpectedPresentConfig;
-    notifyExpectedPresentConfig.timeoutNs = Period::fromNs(30).ns();
-
-    hal::VrrConfig vrrConfig;
-    vrrConfig.notifyExpectedPresentConfig = notifyExpectedPresentConfig;
-    vrrConfig.minFrameIntervalNs = refreshRate.getPeriodNsecs();
-
-    const int32_t kGroup = 0;
-    const auto kResolution = ui::Size(1920, 1080);
-    const auto mode =
-            ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), refreshRate, vrrConfig, kGroup,
-                                                  kResolution, DEFAULT_DISPLAY_ID));
-
-    tracker.setDisplayModePtr(mode);
-    auto last = mNow;
-    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
-        EXPECT_CALL(mVsyncTrackerCallback,
-                    onVsyncGenerated(TimePoint::fromNs(last + mPeriod), mode,
-                                     FpsMatcher(refreshRate)))
-                .Times(1);
-        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
-        mNow += mPeriod;
-        last = mNow;
-        tracker.addVsyncTimestamp(mNow);
-    }
-
-    tracker.setRenderRate(refreshRate / 2);
-    {
-        // out of render rate phase
-        EXPECT_CALL(mVsyncTrackerCallback,
-                    onVsyncGenerated(TimePoint::fromNs(mNow + 3 * mPeriod), mode,
-                                     FpsMatcher(refreshRate / 2)))
-                .Times(1);
-        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod),
-                    Eq(mNow + 3 * mPeriod));
-    }
-}
-
 TEST_F(VSyncPredictorTest, adjustsVrrTimeline) {
     SET_FLAG_FOR_TEST(flags::vrr_config, true);
 
@@ -716,7 +671,7 @@
                                      .build());
 
     VSyncPredictor vrrTracker{kMode, kHistorySize, kMinimumSamplesForPrediction,
-                              kOutlierTolerancePercent, mVsyncTrackerCallback};
+                              kOutlierTolerancePercent};
 
     vrrTracker.setRenderRate(minFrameRate);
     vrrTracker.addVsyncTimestamp(0);
@@ -733,28 +688,6 @@
     EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500));
     EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
 }
-
-TEST_F(VSyncPredictorTest, absentVrrConfigNoVsyncTrackerCallback) {
-    SET_FLAG_FOR_TEST(flags::vrr_config, true);
-    const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
-    const std::optional<hal::VrrConfig> vrrConfigOpt = std::nullopt;
-    constexpr int32_t kGroup = 0;
-    constexpr auto kResolution = ui::Size(1920, 1080);
-    const auto mode =
-            ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), refreshRate, vrrConfigOpt,
-                                                  kGroup, kResolution, DEFAULT_DISPLAY_ID));
-    tracker.setDisplayModePtr(mode);
-
-    auto last = mNow;
-    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
-        EXPECT_CALL(mVsyncTrackerCallback, onVsyncGenerated(_, _, _)).Times(0);
-        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
-        mNow += mPeriod;
-        last = mNow;
-        tracker.addVsyncTimestamp(mNow);
-    }
-}
-
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 22b2ccc..4ca0542 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -28,6 +28,8 @@
     MOCK_METHOD(void, kernelTimerChanged, (bool), (override));
     MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override));
     MOCK_METHOD(void, onChoreographerAttached, (), (override));
+    MOCK_METHOD(void, onExpectedPresentTimePosted, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps),
+                (override));
 };
 
 struct NoOpSchedulerCallback final : ISchedulerCallback {
@@ -36,6 +38,7 @@
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
     void onChoreographerAttached() override {}
+    void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
 };
 
 } // namespace android::scheduler::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
deleted file mode 100644
index b48529f..0000000
--- a/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <gmock/gmock.h>
-
-#include "Scheduler/VSyncTracker.h"
-
-namespace android::scheduler::mock {
-
-struct VsyncTrackerCallback final : IVsyncTrackerCallback {
-    MOCK_METHOD(void, onVsyncGenerated, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps), (override));
-};
-
-struct NoOpVsyncTrackerCallback final : IVsyncTrackerCallback {
-    void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override{};
-};
-} // namespace android::scheduler::mock
diff --git a/services/vibratorservice/test/Android.bp b/services/vibratorservice/test/Android.bp
index 3294724..be71dc2 100644
--- a/services/vibratorservice/test/Android.bp
+++ b/services/vibratorservice/test/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_haptics_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_native_license"