Merge "Add captureLayersSync function" into main
diff --git a/aidl/gui/android/view/Surface.aidl b/aidl/gui/android/view/Surface.aidl
index bb3faaf..6686717 100644
--- a/aidl/gui/android/view/Surface.aidl
+++ b/aidl/gui/android/view/Surface.aidl
@@ -17,4 +17,4 @@
 
 package android.view;
 
-@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable Surface cpp_header "gui/view/Surface.h" ndk_header "android/native_window_aidl.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable @RustOnlyStableParcelable parcelable Surface cpp_header "gui/view/Surface.h" ndk_header "android/native_window_aidl.h" rust_type "nativewindow::Surface";
diff --git a/cmds/servicemanager/main.cpp b/cmds/servicemanager/main.cpp
index ae56cb0..07908ba 100644
--- a/cmds/servicemanager/main.cpp
+++ b/cmds/servicemanager/main.cpp
@@ -40,15 +40,12 @@
 public:
     static sp<BinderCallback> setupTo(const sp<Looper>& looper) {
         sp<BinderCallback> cb = sp<BinderCallback>::make();
+        cb->mLooper = looper;
 
-        int binder_fd = -1;
-        IPCThreadState::self()->setupPolling(&binder_fd);
-        LOG_ALWAYS_FATAL_IF(binder_fd < 0, "Failed to setupPolling: %d", binder_fd);
+        IPCThreadState::self()->setupPolling(&cb->mBinderFd);
+        LOG_ALWAYS_FATAL_IF(cb->mBinderFd < 0, "Failed to setupPolling: %d", cb->mBinderFd);
 
-        int ret = looper->addFd(binder_fd,
-                                Looper::POLL_CALLBACK,
-                                Looper::EVENT_INPUT,
-                                cb,
+        int ret = looper->addFd(cb->mBinderFd, Looper::POLL_CALLBACK, Looper::EVENT_INPUT, cb,
                                 nullptr /*data*/);
         LOG_ALWAYS_FATAL_IF(ret != 1, "Failed to add binder FD to Looper");
 
@@ -59,13 +56,26 @@
         IPCThreadState::self()->handlePolledCommands();
         return 1;  // Continue receiving callbacks.
     }
+
+    void repoll() {
+        if (!mLooper->repoll(mBinderFd)) {
+            ALOGE("Failed to repoll binder FD.");
+        }
+    }
+
+private:
+    sp<Looper> mLooper;
+    int mBinderFd = -1;
 };
 
 // LooperCallback for IClientCallback
 class ClientCallbackCallback : public LooperCallback {
 public:
-    static sp<ClientCallbackCallback> setupTo(const sp<Looper>& looper, const sp<ServiceManager>& manager) {
+    static sp<ClientCallbackCallback> setupTo(const sp<Looper>& looper,
+                                              const sp<ServiceManager>& manager,
+                                              sp<BinderCallback> binderCallback) {
         sp<ClientCallbackCallback> cb = sp<ClientCallbackCallback>::make(manager);
+        cb->mBinderCallback = binderCallback;
 
         int fdTimer = timerfd_create(CLOCK_MONOTONIC, 0 /*flags*/);
         LOG_ALWAYS_FATAL_IF(fdTimer < 0, "Failed to timerfd_create: fd: %d err: %d", fdTimer, errno);
@@ -102,12 +112,15 @@
         }
 
         mManager->handleClientCallbacks();
+        mBinderCallback->repoll(); // b/316829336
+
         return 1;  // Continue receiving callbacks.
     }
 private:
     friend sp<ClientCallbackCallback>;
     ClientCallbackCallback(const sp<ServiceManager>& manager) : mManager(manager) {}
     sp<ServiceManager> mManager;
+    sp<BinderCallback> mBinderCallback;
 };
 
 int main(int argc, char** argv) {
@@ -139,8 +152,8 @@
 
     sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);
 
-    BinderCallback::setupTo(looper);
-    ClientCallbackCallback::setupTo(looper, manager);
+    sp<BinderCallback> binderCallback = BinderCallback::setupTo(looper);
+    ClientCallbackCallback::setupTo(looper, manager, binderCallback);
 
 #ifndef VENDORSERVICEMANAGER
     if (!SetProperty("servicemanager.ready", "true")) {
diff --git a/include/android/asset_manager.h b/include/android/asset_manager.h
index 2ac7d4d..6420cd0 100644
--- a/include/android/asset_manager.h
+++ b/include/android/asset_manager.h
@@ -29,6 +29,10 @@
 #include <sys/cdefs.h>
 #include <sys/types.h>
 
+#if defined(__APPLE__)
+typedef off_t off64_t; // Mac OSX does not define off64_t
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 30200c7..08d339b 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -240,7 +240,7 @@
  *     the actual GPU duration is not measured.
  *
  * @return 0 on success.
- *         EINVAL if session is nullptr or any duration is an invalid number.
+ *         EINVAL if any duration is an invalid number.
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_reportActualWorkDuration2(
@@ -269,7 +269,7 @@
  *
  * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
  * @param workPeriodStartTimestampNanos The work period start timestamp in nanoseconds based on
- *        CLOCK_MONOTONIC about when the work starts, the timestamp must be positive.
+ *        CLOCK_MONOTONIC about when the work starts. This timestamp must be greater than zero.
  */
 void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* _Nonnull aWorkDuration,
         int64_t workPeriodStartTimestampNanos) __INTRODUCED_IN(__ANDROID_API_V__);
@@ -278,8 +278,8 @@
  * Sets the actual total work duration in nanoseconds.
  *
  * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
- * @param actualTotalDurationNanos The actual total work duration in nanoseconds, the number must be
- *        positive.
+ * @param actualTotalDurationNanos The actual total work duration in nanoseconds. This number must
+ *        be greater than zero.
  */
 void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
         int64_t actualTotalDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
@@ -288,8 +288,8 @@
  * Sets the actual CPU work duration in nanoseconds.
  *
  * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
- * @param actualCpuDurationNanos The actual CPU work duration in nanoseconds, the number must be
- *        positive.
+ * @param actualCpuDurationNanos The actual CPU work duration in nanoseconds. This number must be
+ *        greater than zero.
  */
 void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
         int64_t actualCpuDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
diff --git a/include/powermanager/PowerHalController.h b/include/powermanager/PowerHalController.h
index 9e426d3..c50bc4a 100644
--- a/include/powermanager/PowerHalController.h
+++ b/include/powermanager/PowerHalController.h
@@ -62,7 +62,15 @@
     virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
     createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
                       int64_t durationNanos) override;
+    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
+    createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                                int64_t durationNanos,
+                                aidl::android::hardware::power::SessionTag tag,
+                                aidl::android::hardware::power::SessionConfig* config) override;
     virtual HalResult<int64_t> getHintSessionPreferredRate() override;
+    virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(
+            int tgid, int uid) override;
+    virtual HalResult<void> closeSessionChannel(int tgid, int uid) override;
 
 private:
     std::mutex mConnectedHalMutex;
@@ -75,7 +83,7 @@
 
     std::shared_ptr<HalWrapper> initHal();
     template <typename T>
-    HalResult<T> processHalResult(HalResult<T> result, const char* functionName);
+    HalResult<T> processHalResult(HalResult<T>&& result, const char* functionName);
 };
 
 // -------------------------------------------------------------------------------------------------
diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h
index 4e4a1b0..e2da014 100644
--- a/include/powermanager/PowerHalWrapper.h
+++ b/include/powermanager/PowerHalWrapper.h
@@ -14,19 +14,22 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_POWERHALWRAPPER_H
-#define ANDROID_POWERHALWRAPPER_H
+#pragma once
 
 #include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/ChannelConfig.h>
 #include <aidl/android/hardware/power/IPower.h>
 #include <aidl/android/hardware/power/IPowerHintSession.h>
 #include <aidl/android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/SessionConfig.h>
 #include <android-base/thread_annotations.h>
 #include <android/hardware/power/1.1/IPower.h>
 #include <android/hardware/power/1.2/IPower.h>
 #include <android/hardware/power/1.3/IPower.h>
 #include <binder/Status.h>
 
+#include <utility>
+
 namespace android {
 
 namespace power {
@@ -42,44 +45,63 @@
 template <typename T>
 class HalResult {
 public:
-    static HalResult<T> ok(T value) { return HalResult(value); }
-    static HalResult<T> failed(std::string msg) {
-        return HalResult(std::move(msg), /* unsupported= */ false);
-    }
+    static HalResult<T> ok(T&& value) { return HalResult(std::forward<T>(value)); }
+    static HalResult<T> ok(T& value) { return HalResult<T>::ok(T{value}); }
+    static HalResult<T> failed(std::string msg) { return HalResult(msg, /* unsupported= */ false); }
     static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
 
-    static HalResult<T> fromStatus(const binder::Status& status, T data) {
+    static HalResult<T> fromStatus(const binder::Status& status, T&& data) {
         if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
             return HalResult<T>::unsupported();
         }
         if (status.isOk()) {
-            return HalResult<T>::ok(data);
+            return HalResult<T>::ok(std::forward<T>(data));
         }
         return HalResult<T>::failed(std::string(status.toString8().c_str()));
     }
 
-    static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T data) {
+    static HalResult<T> fromStatus(const binder::Status& status, T& data) {
+        return HalResult<T>::fromStatus(status, T{data});
+    }
+
+    static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T&& data) {
         if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
             return HalResult<T>::unsupported();
         }
         if (status.isOk()) {
-            return HalResult<T>::ok(data);
+            return HalResult<T>::ok(std::forward<T>(data));
         }
         return HalResult<T>::failed(std::string(status.getDescription()));
     }
 
+    static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T& data) {
+        return HalResult<T>::fromStatus(status, T{data});
+    }
+
     template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, T data) {
-        return ret.isOk() ? HalResult<T>::ok(data) : HalResult<T>::failed(ret.description());
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, T&& data) {
+        return ret.isOk() ? HalResult<T>::ok(std::forward<T>(data))
+                          : HalResult<T>::failed(ret.description());
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, T& data) {
+        return HalResult<T>::fromReturn(ret, T{data});
     }
 
     template <typename R>
     static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
-                                   T data) {
-        return ret.isOk() ? HalResult<T>::fromStatus(status, data)
+                                   T&& data) {
+        return ret.isOk() ? HalResult<T>::fromStatus(status, std::forward<T>(data))
                           : HalResult<T>::failed(ret.description());
     }
 
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
+                                   T& data) {
+        return HalResult<T>::fromReturn(ret, status, T{data});
+    }
+
     // This will throw std::bad_optional_access if this result is not ok.
     const T& value() const { return mValue.value(); }
     bool isOk() const { return !mUnsupported && mValue.has_value(); }
@@ -92,8 +114,8 @@
     std::string mErrorMessage;
     bool mUnsupported;
 
-    explicit HalResult(T value)
-          : mValue(std::make_optional(value)), mErrorMessage(), mUnsupported(false) {}
+    explicit HalResult(T&& value)
+          : mValue{std::move(value)}, mErrorMessage(), mUnsupported(false) {}
     explicit HalResult(std::string errorMessage, bool unsupported)
           : mValue(), mErrorMessage(std::move(errorMessage)), mUnsupported(unsupported) {}
 };
@@ -158,7 +180,15 @@
     virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
     createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
                       int64_t durationNanos) = 0;
+    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
+    createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                                int64_t durationNanos,
+                                aidl::android::hardware::power::SessionTag tag,
+                                aidl::android::hardware::power::SessionConfig* config) = 0;
     virtual HalResult<int64_t> getHintSessionPreferredRate() = 0;
+    virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
+                                                                                       int uid) = 0;
+    virtual HalResult<void> closeSessionChannel(int tgid, int uid) = 0;
 };
 
 // Empty Power HAL wrapper that ignores all api calls.
@@ -173,11 +203,22 @@
     HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
             int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
             int64_t durationNanos) override;
+    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
+    createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                                int64_t durationNanos,
+                                aidl::android::hardware::power::SessionTag tag,
+                                aidl::android::hardware::power::SessionConfig* config) override;
     HalResult<int64_t> getHintSessionPreferredRate() override;
+    HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
+                                                                               int uid) override;
+    HalResult<void> closeSessionChannel(int tgid, int uid) override;
+
+protected:
+    virtual const char* getUnsupportedMessage();
 };
 
 // Wrapper for the HIDL Power HAL v1.0.
-class HidlHalWrapperV1_0 : public HalWrapper {
+class HidlHalWrapperV1_0 : public EmptyHalWrapper {
 public:
     explicit HidlHalWrapperV1_0(sp<hardware::power::V1_0::IPower> handleV1_0)
           : mHandleV1_0(std::move(handleV1_0)) {}
@@ -186,14 +227,11 @@
     HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
                              int32_t durationMs) override;
     HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
-    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
-            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-            int64_t durationNanos) override;
-    HalResult<int64_t> getHintSessionPreferredRate() override;
 
 protected:
     const sp<hardware::power::V1_0::IPower> mHandleV1_0;
     virtual HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data);
+    const char* getUnsupportedMessage();
 
 private:
     HalResult<void> setInteractive(bool enabled);
@@ -238,7 +276,7 @@
 };
 
 // Wrapper for the AIDL Power HAL.
-class AidlHalWrapper : public HalWrapper {
+class AidlHalWrapper : public EmptyHalWrapper {
 public:
     explicit AidlHalWrapper(std::shared_ptr<aidl::android::hardware::power::IPower> handle)
           : mHandle(std::move(handle)) {}
@@ -250,7 +288,19 @@
     HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
             int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
             int64_t durationNanos) override;
+    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
+    createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                                int64_t durationNanos,
+                                aidl::android::hardware::power::SessionTag tag,
+                                aidl::android::hardware::power::SessionConfig* config) override;
+
     HalResult<int64_t> getHintSessionPreferredRate() override;
+    HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
+                                                                               int uid) override;
+    HalResult<void> closeSessionChannel(int tgid, int uid) override;
+
+protected:
+    const char* getUnsupportedMessage() override;
 
 private:
     // Control access to the boost and mode supported arrays.
@@ -274,5 +324,3 @@
 }; // namespace power
 
 }; // namespace android
-
-#endif // ANDROID_POWERHALWRAPPER_H
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index e1dc791..6b8e824 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -416,26 +416,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 +456,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/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 576d9d5..0c850fe 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -97,3 +97,10 @@
   description: "Change the acceleration curves for mouse pointer movements to match the touchpad ones"
   bug: "315313622"
 }
+
+flag {
+  name: "rate_limit_user_activity_poke_in_dispatcher"
+  namespace: "input"
+  description: "Move user-activity poke rate-limiting from PowerManagerService to InputDispatcher."
+  bug: "320499729"
+}
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index aab7df0..22ad834 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -16,6 +16,8 @@
 
 extern crate nativewindow_bindgen as ffi;
 
+pub mod surface;
+
 pub use ffi::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
 
 use binder::{
@@ -210,7 +212,7 @@
 }
 
 impl Debug for HardwareBuffer {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
         f.debug_struct("HardwareBuffer").field("id", &self.id()).finish()
     }
 }
diff --git a/libs/nativewindow/rust/src/surface.rs b/libs/nativewindow/rust/src/surface.rs
new file mode 100644
index 0000000..25fea80
--- /dev/null
+++ b/libs/nativewindow/rust/src/surface.rs
@@ -0,0 +1,143 @@
+// 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.
+
+//! Rust wrapper for `ANativeWindow` and related types.
+
+use binder::{
+    binder_impl::{BorrowedParcel, UnstructuredParcelable},
+    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
+    unstable_api::{status_result, AsNative},
+    StatusCode,
+};
+use nativewindow_bindgen::{
+    AHardwareBuffer_Format, ANativeWindow, ANativeWindow_acquire, ANativeWindow_getFormat,
+    ANativeWindow_getHeight, ANativeWindow_getWidth, ANativeWindow_readFromParcel,
+    ANativeWindow_release, ANativeWindow_writeToParcel,
+};
+use std::error::Error;
+use std::fmt::{self, Debug, Display, Formatter};
+use std::ptr::{null_mut, NonNull};
+
+/// Wrapper around an opaque C `ANativeWindow`.
+#[derive(PartialEq, Eq)]
+pub struct Surface(NonNull<ANativeWindow>);
+
+impl Surface {
+    /// Returns the current width in pixels of the window surface.
+    pub fn width(&self) -> Result<u32, ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let width = unsafe { ANativeWindow_getWidth(self.0.as_ptr()) };
+        width.try_into().map_err(|_| ErrorCode(width))
+    }
+
+    /// Returns the current height in pixels of the window surface.
+    pub fn height(&self) -> Result<u32, ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let height = unsafe { ANativeWindow_getHeight(self.0.as_ptr()) };
+        height.try_into().map_err(|_| ErrorCode(height))
+    }
+
+    /// Returns the current pixel format of the window surface.
+    pub fn format(&self) -> Result<AHardwareBuffer_Format::Type, ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let format = unsafe { ANativeWindow_getFormat(self.0.as_ptr()) };
+        format.try_into().map_err(|_| ErrorCode(format))
+    }
+}
+
+impl Drop for Surface {
+    fn drop(&mut self) {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        unsafe { ANativeWindow_release(self.0.as_ptr()) }
+    }
+}
+
+impl Debug for Surface {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        f.debug_struct("Surface")
+            .field("width", &self.width())
+            .field("height", &self.height())
+            .field("format", &self.format())
+            .finish()
+    }
+}
+
+impl Clone for Surface {
+    fn clone(&self) -> Self {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        unsafe { ANativeWindow_acquire(self.0.as_ptr()) };
+        Self(self.0)
+    }
+}
+
+impl UnstructuredParcelable for Surface {
+    fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
+        let status =
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        unsafe { ANativeWindow_writeToParcel(self.0.as_ptr(), parcel.as_native_mut()) };
+        status_result(status)
+    }
+
+    fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
+        let mut buffer = null_mut();
+
+        let status =
+        // SAFETY: Both pointers must be valid because they are obtained from references.
+        // `ANativeWindow_readFromParcel` doesn't store them or do anything else special
+        // with them. If it returns success then it will have allocated a new
+        // `ANativeWindow` and incremented the reference count, so we can use it until we
+        // release it.
+            unsafe { ANativeWindow_readFromParcel(parcel.as_native(), &mut buffer) };
+
+        status_result(status)?;
+
+        Ok(Self(
+            NonNull::new(buffer)
+                .expect("ANativeWindow_readFromParcel returned success but didn't allocate buffer"),
+        ))
+    }
+}
+
+impl_deserialize_for_unstructured_parcelable!(Surface);
+impl_serialize_for_unstructured_parcelable!(Surface);
+
+// 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);
+
+impl Error for ErrorCode {}
+
+impl Display for ErrorCode {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        write!(f, "Error {}", self.0)
+    }
+}
diff --git a/libs/nativewindow/rust/sys/nativewindow_bindings.h b/libs/nativewindow/rust/sys/nativewindow_bindings.h
index 4525a42..5689f7d 100644
--- a/libs/nativewindow/rust/sys/nativewindow_bindings.h
+++ b/libs/nativewindow/rust/sys/nativewindow_bindings.h
@@ -19,3 +19,4 @@
 #include <android/hardware_buffer_aidl.h>
 #include <android/hdr_metadata.h>
 #include <android/native_window.h>
+#include <android/native_window_aidl.h>
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..fc84bbf 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
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..3af85c0 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -596,7 +596,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/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/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 4571ef4..3ac4285 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -109,7 +109,9 @@
     const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
     const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
     pc.move(deltaX, deltaY);
-    pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    if (canUnfadeOnDisplay(displayId)) {
+        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    }
 
     const auto [x, y] = pc.getPosition();
     NotifyMotionArgs newArgs(args);
@@ -131,7 +133,9 @@
         const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
         const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
         pc.move(deltaX, deltaY);
-        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+        if (canUnfadeOnDisplay(displayId)) {
+            pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+        }
 
         const auto [x, y] = pc.getPosition();
         newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -140,7 +144,9 @@
         newArgs.yCursorPosition = y;
     } else {
         // This is a trackpad gesture with fake finger(s) that should not move the mouse pointer.
-        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+        if (canUnfadeOnDisplay(displayId)) {
+            pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+        }
 
         const auto [x, y] = pc.getPosition();
         for (uint32_t i = 0; i < newArgs.getPointerCount(); i++) {
@@ -223,7 +229,7 @@
     if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
         pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
         pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED);
-    } else {
+    } else if (canUnfadeOnDisplay(args.displayId)) {
         pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
     }
 }
@@ -323,6 +329,10 @@
     return it != mInputDeviceInfos.end() ? &(*it) : nullptr;
 }
 
+bool PointerChoreographer::canUnfadeOnDisplay(int32_t displayId) {
+    return mDisplaysWithPointersHidden.find(displayId) == mDisplaysWithPointersHidden.end();
+}
+
 void PointerChoreographer::updatePointerControllersLocked() {
     std::set<int32_t /*displayId*/> mouseDisplaysToKeep;
     std::set<DeviceId> touchDevicesToKeep;
@@ -342,7 +352,7 @@
                     mMousePointersByDisplay.try_emplace(displayId,
                                                         getMouseControllerConstructor(displayId));
             auto [_, isNewMouseDevice] = mMouseDevices.emplace(info.getId());
-            if (isNewMouseDevice || isNewMousePointer) {
+            if ((isNewMouseDevice || isNewMousePointer) && canUnfadeOnDisplay(displayId)) {
                 mousePointerIt->second->unfade(PointerControllerInterface::Transition::IMMEDIATE);
             }
         }
@@ -513,6 +523,28 @@
     return true;
 }
 
+void PointerChoreographer::setPointerIconVisibility(int32_t displayId, bool visible) {
+    std::scoped_lock lock(mLock);
+    if (visible) {
+        mDisplaysWithPointersHidden.erase(displayId);
+        // We do not unfade the icons here, because we don't know when the last event happened.
+        return;
+    }
+
+    mDisplaysWithPointersHidden.emplace(displayId);
+
+    // Hide any icons that are currently visible on the display.
+    if (auto it = mMousePointersByDisplay.find(displayId); it != mMousePointersByDisplay.end()) {
+        const auto& [_, controller] = *it;
+        controller->fade(PointerControllerInterface::Transition::IMMEDIATE);
+    }
+    for (const auto& [_, controller] : mStylusPointersByDevice) {
+        if (controller->getDisplayId() == displayId) {
+            controller->fade(PointerControllerInterface::Transition::IMMEDIATE);
+        }
+    }
+}
+
 PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
         int32_t displayId) {
     std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index f46419e..6aab3aa 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -67,6 +67,11 @@
      */
     virtual bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
                                 int32_t displayId, DeviceId deviceId) = 0;
+    /**
+     * Set whether pointer icons for mice, touchpads, and styluses should be visible on the
+     * given display.
+     */
+    virtual void setPointerIconVisibility(int32_t displayId, bool visible) = 0;
 
     /**
      * This method may be called on any thread (usually by the input manager on a binder thread).
@@ -89,6 +94,7 @@
     void setStylusPointerIconEnabled(bool enabled) override;
     bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
                         int32_t displayId, DeviceId deviceId) override;
+    void setPointerIconVisibility(int32_t displayId, bool visible) override;
 
     void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
     void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
@@ -110,6 +116,7 @@
     std::pair<int32_t, PointerControllerInterface&> getDisplayIdAndMouseControllerLocked(
             int32_t associatedDisplayId) REQUIRES(mLock);
     InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock);
+    bool canUnfadeOnDisplay(int32_t displayId) REQUIRES(mLock);
 
     NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
     NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
@@ -143,6 +150,7 @@
     std::vector<DisplayViewport> mViewports GUARDED_BY(mLock);
     bool mShowTouchesEnabled GUARDED_BY(mLock);
     bool mStylusPointerIconEnabled GUARDED_BY(mLock);
+    std::set<int32_t /*displayId*/> mDisplaysWithPointersHidden;
 };
 
 } // namespace android
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/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 1085c94..c349a58 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -35,7 +35,6 @@
 #include <input/PrintTools.h>
 #include <input/TraceTools.h>
 #include <openssl/mem.h>
-#include <powermanager/PowerManager.h>
 #include <unistd.h>
 #include <utils/Trace.h>
 
@@ -100,6 +99,9 @@
         android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
         HwTimeoutMultiplier());
 
+// The default minimum time gap between two user activity poke events.
+const std::chrono::milliseconds DEFAULT_USER_ACTIVITY_POKE_INTERVAL = 100ms;
+
 const std::chrono::duration STALE_EVENT_TIMEOUT = std::chrono::seconds(10) * HwTimeoutMultiplier();
 
 // Log a warning when an event takes longer than this to process, even if an ANR does not occur.
@@ -778,6 +780,25 @@
     return {};
 }
 
+int32_t getUserActivityEventType(const EventEntry& eventEntry) {
+    switch (eventEntry.type) {
+        case EventEntry::Type::KEY: {
+            return USER_ACTIVITY_EVENT_BUTTON;
+        }
+        case EventEntry::Type::MOTION: {
+            const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
+            if (MotionEvent::isTouchEvent(motionEntry.source, motionEntry.action)) {
+                return USER_ACTIVITY_EVENT_TOUCH;
+            }
+            return USER_ACTIVITY_EVENT_OTHER;
+        }
+        default: {
+            LOG_ALWAYS_FATAL("%s events are not user activity",
+                             ftl::enum_string(eventEntry.type).c_str());
+        }
+    }
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -791,6 +812,7 @@
         mPendingEvent(nullptr),
         mLastDropReason(DropReason::NOT_DROPPED),
         mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER),
+        mMinTimeBetweenUserActivityPokes(DEFAULT_USER_ACTIVITY_POKE_INTERVAL),
         mNextUnblockedEvent(nullptr),
         mMonitorDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT),
         mDispatchEnabled(false),
@@ -813,6 +835,8 @@
     if (traceBackend) {
         // TODO: Create input tracer instance.
     }
+
+    mLastUserActivityTimes.fill(0);
 }
 
 InputDispatcher::~InputDispatcher() {
@@ -3140,6 +3164,21 @@
         // Not poking user activity if the event type does not represent a user activity
         return;
     }
+
+    const int32_t eventType = getUserActivityEventType(eventEntry);
+    if (input_flags::rate_limit_user_activity_poke_in_dispatcher()) {
+        // Note that we're directly getting the time diff between the current event and the previous
+        // event. This is assuming that the first user event always happens at a timestamp that is
+        // greater than `mMinTimeBetweenUserActivityPokes` (otherwise, the first user event will
+        // wrongly be dropped). In real life, `mMinTimeBetweenUserActivityPokes` is a much smaller
+        // value than the potential first user activity event time, so this is ok.
+        std::chrono::nanoseconds timeSinceLastEvent =
+                std::chrono::nanoseconds(eventEntry.eventTime - mLastUserActivityTimes[eventType]);
+        if (timeSinceLastEvent < mMinTimeBetweenUserActivityPokes) {
+            return;
+        }
+    }
+
     int32_t displayId = getTargetDisplayId(eventEntry);
     sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
     const WindowInfo* windowDisablingUserActivityInfo = nullptr;
@@ -3150,7 +3189,6 @@
         }
     }
 
-    int32_t eventType = USER_ACTIVITY_EVENT_OTHER;
     switch (eventEntry.type) {
         case EventEntry::Type::MOTION: {
             const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
@@ -3164,9 +3202,6 @@
                 }
                 return;
             }
-            if (MotionEvent::isTouchEvent(motionEntry.source, motionEntry.action)) {
-                eventType = USER_ACTIVITY_EVENT_TOUCH;
-            }
             break;
         }
         case EventEntry::Type::KEY: {
@@ -3190,7 +3225,6 @@
                 return;
             }
 
-            eventType = USER_ACTIVITY_EVENT_BUTTON;
             break;
         }
         default: {
@@ -3200,6 +3234,7 @@
         }
     }
 
+    mLastUserActivityTimes[eventType] = eventEntry.eventTime;
     auto command = [this, eventTime = eventEntry.eventTime, eventType, displayId]()
                            REQUIRES(mLock) {
                                scoped_unlock unlock(mLock);
@@ -5292,6 +5327,14 @@
     resetNoFocusedWindowTimeoutLocked();
 }
 
+void InputDispatcher::setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) {
+    if (interval.count() < 0) {
+        LOG_ALWAYS_FATAL("Minimum time between user activity pokes should be >= 0");
+    }
+    std::scoped_lock _l(mLock);
+    mMinTimeBetweenUserActivityPokes = interval;
+}
+
 /**
  * Sets the focused display, which is responsible for receiving focus-dispatched input events where
  * the display not specified.
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 1e11b27..e635852 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -41,6 +41,7 @@
 #include <input/Input.h>
 #include <input/InputTransport.h>
 #include <limits.h>
+#include <powermanager/PowerManager.h>
 #include <stddef.h>
 #include <unistd.h>
 #include <utils/BitSet.h>
@@ -116,6 +117,7 @@
             int32_t displayId,
             const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) override;
     void setFocusedDisplay(int32_t displayId) override;
+    void setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) override;
     void setInputDispatchMode(bool enabled, bool frozen) override;
     void setInputFilterEnabled(bool enabled) override;
     bool setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid, bool hasPermission,
@@ -211,6 +213,11 @@
 
     int64_t mWindowInfosVsyncId GUARDED_BY(mLock);
 
+    std::chrono::milliseconds mMinTimeBetweenUserActivityPokes GUARDED_BY(mLock);
+
+    /** Stores the latest user-activity poke event times per user activity types. */
+    std::array<nsecs_t, USER_ACTIVITY_EVENT_LAST + 1> mLastUserActivityTimes GUARDED_BY(mLock);
+
     // With each iteration, InputDispatcher nominally processes one queued event,
     // a timeout, or a response from an input consumer.
     // This method should only be called on the input dispatcher's own thread.
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 001dc6c..c8f3d05 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -101,6 +101,9 @@
      */
     virtual void setFocusedDisplay(int32_t displayId) = 0;
 
+    /** Sets the minimum time between user activity pokes. */
+    virtual void setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) = 0;
+
     /* Sets the input dispatching mode.
      *
      * This method may be called on any thread (usually by the input manager).
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 40359a4..9abfef1 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -131,10 +131,10 @@
     // Currently only used when the enable_new_mouse_pointer_ballistics flag is enabled.
     int32_t mousePointerSpeed;
 
-    // Whether to apply an acceleration curve to pointer movements from mice.
+    // Displays on which an acceleration curve shouldn't be applied for pointer movements from mice.
     //
     // Currently only used when the enable_new_mouse_pointer_ballistics flag is enabled.
-    bool mousePointerAccelerationEnabled;
+    std::set<int32_t> displaysWithMousePointerAccelerationDisabled;
 
     // Velocity control parameters for mouse pointer movements.
     //
@@ -243,7 +243,7 @@
     InputReaderConfiguration()
           : virtualKeyQuietTime(0),
             mousePointerSpeed(0),
-            mousePointerAccelerationEnabled(true),
+            displaysWithMousePointerAccelerationDisabled(),
             pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f,
                                              static_cast<float>(
                                                      android::os::IInputConstants::
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 4cebd64..d207ed1 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -159,15 +159,15 @@
         out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId()));
     }
 
-    if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED) ||
-        configurePointerCapture) {
-        configureOnChangePointerSpeed(readerConfig);
-    }
-
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) ||
         configurePointerCapture) {
         configureOnChangeDisplayInfo(readerConfig);
     }
+
+    if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED) ||
+        configurePointerCapture) {
+        configureOnChangePointerSpeed(readerConfig);
+    }
     return out;
 }
 
@@ -510,7 +510,8 @@
     } else {
         if (mEnableNewMousePointerBallistics) {
             mNewPointerVelocityControl.setAccelerationEnabled(
-                    config.mousePointerAccelerationEnabled);
+                    config.displaysWithMousePointerAccelerationDisabled.count(
+                            mDisplayId.value_or(ADISPLAY_ID_NONE)) == 0);
             mNewPointerVelocityControl.setCurve(
                     createAccelerationCurveForPointerSensitivity(config.mousePointerSpeed));
         } else {
diff --git a/services/inputflinger/reader/mapper/SlopController.cpp b/services/inputflinger/reader/mapper/SlopController.cpp
index f79219f..9ec02a6 100644
--- a/services/inputflinger/reader/mapper/SlopController.cpp
+++ b/services/inputflinger/reader/mapper/SlopController.cpp
@@ -54,11 +54,13 @@
     mCumulativeValue += value;
 
     if (abs(mCumulativeValue) >= mSlopThreshold) {
+        ALOGD("SlopController: did not drop event with value .%3f", value);
         mHasSlopBeenMet = true;
         // Return the amount of value that exceeds the slop.
         return signOf(value) * (abs(mCumulativeValue) - mSlopThreshold);
     }
 
+    ALOGD("SlopController: dropping event with value .%3f", value);
     return 0;
 }
 
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..da72134 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;
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/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index e104543..7b793d8 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -1324,6 +1324,42 @@
     ASSERT_EQ(20, relY2);
 }
 
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, ConfigureAccelerationWithAssociatedViewport) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0);
+    mReaderConfiguration.setDisplayViewports({primaryViewport});
+    createDevice();
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, primaryViewport);
+    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+
+    std::list<NotifyArgs> args;
+
+    // Verify that acceleration is being applied by default by checking that the movement is scaled.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithDisplayId(DISPLAY_ID)))));
+    const auto& coords = get<NotifyMotionArgs>(args.back()).pointerCoords[0];
+    ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X), 10.f);
+    ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y), 20.f);
+
+    // Disable acceleration for the display, and verify that acceleration is no longer applied.
+    mReaderConfiguration.displaysWithMousePointerAccelerationDisabled.emplace(DISPLAY_ID);
+    args += mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                 InputReaderConfiguration::Change::POINTER_SPEED);
+    args.clear();
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(HOVER_MOVE), WithDisplayId(DISPLAY_ID),
+                              WithRelativeMotion(10, 20)))));
+}
+
 namespace {
 
 // Minimum timestamp separation between subsequent input events from a Bluetooth device.
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index b6ae0b3..f1f4a61 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -156,6 +156,20 @@
         sp<IBinder> token{};
         gui::Pid pid{gui::Pid::INVALID};
     };
+    /* Stores data about a user-activity-poke event from the dispatcher. */
+    struct UserActivityPokeEvent {
+        nsecs_t eventTime;
+        int32_t eventType;
+        int32_t displayId;
+
+        bool operator==(const UserActivityPokeEvent& rhs) const = default;
+
+        friend std::ostream& operator<<(std::ostream& os, const UserActivityPokeEvent& ev) {
+            os << "UserActivityPokeEvent[time=" << ev.eventTime << ", eventType=" << ev.eventType
+               << ", displayId=" << ev.displayId << "]";
+            return os;
+        }
+    };
 
 public:
     FakeInputDispatcherPolicy() = default;
@@ -351,14 +365,36 @@
 
     void setStaleEventTimeout(std::chrono::nanoseconds timeout) { mStaleEventTimeout = timeout; }
 
-    void assertUserActivityPoked() {
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(mPokedUserActivity) << "Expected user activity to have been poked";
+    void assertUserActivityNotPoked() {
+        std::unique_lock lock(mLock);
+        base::ScopedLockAssertion assumeLocked(mLock);
+
+        std::optional<UserActivityPokeEvent> pokeEvent =
+                getItemFromStorageLockedInterruptible(500ms, mUserActivityPokeEvents, lock,
+                                                      mNotifyUserActivity);
+
+        ASSERT_FALSE(pokeEvent) << "Expected user activity not to have been poked";
     }
 
-    void assertUserActivityNotPoked() {
-        std::scoped_lock lock(mLock);
-        ASSERT_FALSE(mPokedUserActivity) << "Expected user activity not to have been poked";
+    /**
+     * Asserts that a user activity poke has happened. The earliest recorded poke event will be
+     * cleared after this call.
+     *
+     * If an expected UserActivityPokeEvent is provided, asserts that the given event is the
+     * earliest recorded poke event.
+     */
+    void assertUserActivityPoked(std::optional<UserActivityPokeEvent> expectedPokeEvent = {}) {
+        std::unique_lock lock(mLock);
+        base::ScopedLockAssertion assumeLocked(mLock);
+
+        std::optional<UserActivityPokeEvent> pokeEvent =
+                getItemFromStorageLockedInterruptible(500ms, mUserActivityPokeEvents, lock,
+                                                      mNotifyUserActivity);
+        ASSERT_TRUE(pokeEvent) << "Expected a user poke event";
+
+        if (expectedPokeEvent) {
+            ASSERT_EQ(expectedPokeEvent, *pokeEvent);
+        }
     }
 
     void assertNotifyDeviceInteractionWasCalled(int32_t deviceId, std::set<gui::Uid> uids) {
@@ -414,7 +450,9 @@
 
     sp<IBinder> mDropTargetWindowToken GUARDED_BY(mLock);
     bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false;
-    bool mPokedUserActivity GUARDED_BY(mLock) = false;
+
+    std::condition_variable mNotifyUserActivity;
+    std::queue<UserActivityPokeEvent> mUserActivityPokeEvents;
 
     std::chrono::milliseconds mInterceptKeyTimeout = 0ms;
 
@@ -576,9 +614,10 @@
                 NotifySwitchArgs(InputEvent::nextId(), when, policyFlags, switchValues, switchMask);
     }
 
-    void pokeUserActivity(nsecs_t, int32_t, int32_t) override {
+    void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override {
         std::scoped_lock lock(mLock);
-        mPokedUserActivity = true;
+        mNotifyUserActivity.notify_all();
+        mUserActivityPokeEvents.push({eventTime, eventType, displayId});
     }
 
     bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) override {
@@ -7690,6 +7729,130 @@
                     /*resolvedDeviceId=*/VIRTUAL_KEYBOARD_ID, /*flags=*/0);
 }
 
+class InputDispatcherUserActivityPokeTests : public InputDispatcherTest {
+protected:
+    virtual void SetUp() override {
+        InputDispatcherTest::SetUp();
+
+        std::shared_ptr<FakeApplicationHandle> application =
+                std::make_shared<FakeApplicationHandle>();
+        application->setDispatchingTimeout(100ms);
+        mWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow",
+                                             ADISPLAY_ID_DEFAULT);
+        mWindow->setFrame(Rect(0, 0, 100, 100));
+        mWindow->setDispatchingTimeout(100ms);
+        mWindow->setFocusable(true);
+
+        // Set focused application.
+        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+        mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
+        setFocusedWindow(mWindow);
+        mWindow->consumeFocusEvent(true);
+    }
+
+    void notifyAndConsumeMotion(int32_t action, uint32_t source, int32_t displayId,
+                                nsecs_t eventTime) {
+        mDispatcher->notifyMotion(MotionArgsBuilder(action, source)
+                                          .displayId(displayId)
+                                          .eventTime(eventTime)
+                                          .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                          .build());
+        mWindow->consumeMotionEvent(WithMotionAction(action));
+    }
+
+private:
+    sp<FakeWindowHandle> mWindow;
+};
+
+TEST_F_WITH_FLAGS(
+        InputDispatcherUserActivityPokeTests, MinPokeTimeObserved,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            rate_limit_user_activity_poke_in_dispatcher))) {
+    mDispatcher->setMinTimeBetweenUserActivityPokes(50ms);
+
+    // First event of type TOUCH. Should poke.
+    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                           milliseconds_to_nanoseconds(50));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(50), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}});
+
+    // 80ns > 50ns has passed since previous TOUCH event. Should poke.
+    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                           milliseconds_to_nanoseconds(130));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(130), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}});
+
+    // First event of type OTHER. Should poke (despite being within 50ns of previous TOUCH event).
+    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT,
+                           milliseconds_to_nanoseconds(135));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(135), USER_ACTIVITY_EVENT_OTHER, ADISPLAY_ID_DEFAULT}});
+
+    // Within 50ns of previous TOUCH event. Should NOT poke.
+    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                           milliseconds_to_nanoseconds(140));
+    mFakePolicy->assertUserActivityNotPoked();
+
+    // Within 50ns of previous OTHER event. Should NOT poke.
+    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT,
+                           milliseconds_to_nanoseconds(150));
+    mFakePolicy->assertUserActivityNotPoked();
+
+    // Within 50ns of previous TOUCH event (which was at time 130). Should NOT poke.
+    // Note that STYLUS is mapped to TOUCH user activity, since it's a pointer-type source.
+    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT,
+                           milliseconds_to_nanoseconds(160));
+    mFakePolicy->assertUserActivityNotPoked();
+
+    // 65ns > 50ns has passed since previous OTHER event. Should poke.
+    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT,
+                           milliseconds_to_nanoseconds(200));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_OTHER, ADISPLAY_ID_DEFAULT}});
+
+    // 170ns > 50ns has passed since previous TOUCH event. Should poke.
+    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT,
+                           milliseconds_to_nanoseconds(300));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(300), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}});
+
+    // Assert that there's no more user activity poke event.
+    mFakePolicy->assertUserActivityNotPoked();
+}
+
+TEST_F_WITH_FLAGS(
+        InputDispatcherUserActivityPokeTests, DefaultMinPokeTimeOf100MsUsed,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            rate_limit_user_activity_poke_in_dispatcher))) {
+    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                           milliseconds_to_nanoseconds(200));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}});
+
+    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                           milliseconds_to_nanoseconds(280));
+    mFakePolicy->assertUserActivityNotPoked();
+
+    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                           milliseconds_to_nanoseconds(340));
+    mFakePolicy->assertUserActivityPoked(
+            {{milliseconds_to_nanoseconds(340), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}});
+}
+
+TEST_F_WITH_FLAGS(
+        InputDispatcherUserActivityPokeTests, ZeroMinPokeTimeDisablesRateLimiting,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            rate_limit_user_activity_poke_in_dispatcher))) {
+    mDispatcher->setMinTimeBetweenUserActivityPokes(0ms);
+
+    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, 20);
+    mFakePolicy->assertUserActivityPoked();
+
+    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, 30);
+    mFakePolicy->assertUserActivityPoked();
+}
+
 class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest {
     virtual void SetUp() override {
         InputDispatcherTest::SetUp();
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 1ac043c..e9e5061 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -1557,4 +1557,122 @@
     mousePc->assertPointerIconNotSet();
 }
 
+TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerOnDisplay) {
+    // Make sure there are two PointerControllers on different displays.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+    auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId());
+    auto secondMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(ANOTHER_DISPLAY_ID, secondMousePc->getDisplayId());
+
+    // Both pointers should be visible.
+    ASSERT_TRUE(firstMousePc->isPointerShown());
+    ASSERT_TRUE(secondMousePc->isPointerShown());
+
+    // Hide the icon on the second display.
+    mChoreographer.setPointerIconVisibility(ANOTHER_DISPLAY_ID, false);
+    ASSERT_TRUE(firstMousePc->isPointerShown());
+    ASSERT_FALSE(secondMousePc->isPointerShown());
+
+    // Move and set pointer icons for both mice. The second pointer should still be hidden.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, ANOTHER_DISPLAY_ID,
+                                              SECOND_DEVICE_ID));
+    firstMousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+    secondMousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+    ASSERT_TRUE(firstMousePc->isPointerShown());
+    ASSERT_FALSE(secondMousePc->isPointerShown());
+
+    // Allow the icon to be visible on the second display, and move the mouse.
+    mChoreographer.setPointerIconVisibility(ANOTHER_DISPLAY_ID, true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+    ASSERT_TRUE(firstMousePc->isPointerShown());
+    ASSERT_TRUE(secondMousePc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerWhenDeviceConnected) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // Hide the pointer on the display, and then connect the mouse.
+    mChoreographer.setPointerIconVisibility(DISPLAY_ID, false);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, mousePc->getDisplayId());
+
+    // The pointer should not be visible.
+    ASSERT_FALSE(mousePc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerForTouchpad) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // Hide the pointer on the display.
+    mChoreographer.setPointerIconVisibility(DISPLAY_ID, false);
+
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                                     ADISPLAY_ID_NONE)}});
+    auto touchpadPc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, touchpadPc->getDisplayId());
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                  AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD)
+                                        .pointer(TOUCHPAD_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+
+    // The pointer should not be visible.
+    ASSERT_FALSE(touchpadPc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerForStylus) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setStylusPointerIconEnabled(true);
+
+    // Hide the pointer on the display.
+    mChoreographer.setPointerIconVisibility(DISPLAY_ID, false);
+
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    pc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+
+    // The pointer should not be visible.
+    ASSERT_FALSE(pc->isPointerShown());
+}
+
 } // namespace android
diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp
index 9a23c84..bc178bc 100644
--- a/services/powermanager/PowerHalController.cpp
+++ b/services/powermanager/PowerHalController.cpp
@@ -80,7 +80,7 @@
 // Check if a call to Power HAL function failed; if so, log the failure and
 // invalidate the current Power HAL handle.
 template <typename T>
-HalResult<T> PowerHalController::processHalResult(HalResult<T> result, const char* fnName) {
+HalResult<T> PowerHalController::processHalResult(HalResult<T>&& result, const char* fnName) {
     if (result.isFailed()) {
         ALOGE("%s failed: %s", fnName, result.errorMessage());
         std::lock_guard<std::mutex> lock(mConnectedHalMutex);
@@ -88,21 +88,19 @@
         mConnectedHal = nullptr;
         mHalConnector->reset();
     }
-    return result;
+    return std::move(result);
 }
 
 HalResult<void> PowerHalController::setBoost(aidl::android::hardware::power::Boost boost,
                                              int32_t durationMs) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    auto result = handle->setBoost(boost, durationMs);
-    return processHalResult(result, "setBoost");
+    return processHalResult(handle->setBoost(boost, durationMs), "setBoost");
 }
 
 HalResult<void> PowerHalController::setMode(aidl::android::hardware::power::Mode mode,
                                             bool enabled) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    auto result = handle->setMode(mode, enabled);
-    return processHalResult(result, "setMode");
+    return processHalResult(handle->setMode(mode, enabled), "setMode");
 }
 
 HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
@@ -110,14 +108,35 @@
                                       const std::vector<int32_t>& threadIds,
                                       int64_t durationNanos) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    auto result = handle->createHintSession(tgid, uid, threadIds, durationNanos);
-    return processHalResult(result, "createHintSession");
+    return processHalResult(handle->createHintSession(tgid, uid, threadIds, durationNanos),
+                            "createHintSession");
+}
+
+HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
+PowerHalController::createHintSessionWithConfig(
+        int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+        aidl::android::hardware::power::SessionTag tag,
+        aidl::android::hardware::power::SessionConfig* config) {
+    std::shared_ptr<HalWrapper> handle = initHal();
+    return processHalResult(handle->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos,
+                                                                tag, config),
+                            "createHintSessionWithConfig");
 }
 
 HalResult<int64_t> PowerHalController::getHintSessionPreferredRate() {
     std::shared_ptr<HalWrapper> handle = initHal();
-    auto result = handle->getHintSessionPreferredRate();
-    return processHalResult(result, "getHintSessionPreferredRate");
+    return processHalResult(handle->getHintSessionPreferredRate(), "getHintSessionPreferredRate");
+}
+
+HalResult<aidl::android::hardware::power::ChannelConfig> PowerHalController::getSessionChannel(
+        int tgid, int uid) {
+    std::shared_ptr<HalWrapper> handle = initHal();
+    return processHalResult(handle->getSessionChannel(tgid, uid), "getSessionChannel");
+}
+
+HalResult<void> PowerHalController::closeSessionChannel(int tgid, int uid) {
+    std::shared_ptr<HalWrapper> handle = initHal();
+    return processHalResult(handle->closeSessionChannel(tgid, uid), "closeSessionChannel");
 }
 
 } // namespace power
diff --git a/services/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp
index 76afbfc..1009100 100644
--- a/services/powermanager/PowerHalWrapper.cpp
+++ b/services/powermanager/PowerHalWrapper.cpp
@@ -42,37 +42,58 @@
 // -------------------------------------------------------------------------------------------------
 
 HalResult<void> EmptyHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) {
-    ALOGV("Skipped setBoost %s with duration %dms because Power HAL not available",
-          toString(boost).c_str(), durationMs);
+    ALOGV("Skipped setBoost %s with duration %dms because %s", toString(boost).c_str(), durationMs,
+          getUnsupportedMessage());
     return HalResult<void>::unsupported();
 }
 
 HalResult<void> EmptyHalWrapper::setMode(Aidl::Mode mode, bool enabled) {
-    ALOGV("Skipped setMode %s to %s because Power HAL not available", toString(mode).c_str(),
-          enabled ? "true" : "false");
+    ALOGV("Skipped setMode %s to %s because %s", toString(mode).c_str(), enabled ? "true" : "false",
+          getUnsupportedMessage());
     return HalResult<void>::unsupported();
 }
 
 HalResult<std::shared_ptr<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSession(
         int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) {
-    ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available",
-          threadIds.size());
+    ALOGV("Skipped createHintSession(task num=%zu) because %s", threadIds.size(),
+          getUnsupportedMessage());
+    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
+}
+
+HalResult<std::shared_ptr<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSessionWithConfig(
+        int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t, Aidl::SessionTag,
+        Aidl::SessionConfig*) {
+    ALOGV("Skipped createHintSessionWithConfig(task num=%zu) because %s", threadIds.size(),
+          getUnsupportedMessage());
     return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
 }
 
 HalResult<int64_t> EmptyHalWrapper::getHintSessionPreferredRate() {
-    ALOGV("Skipped getHintSessionPreferredRate because Power HAL not available");
+    ALOGV("Skipped getHintSessionPreferredRate because %s", getUnsupportedMessage());
     return HalResult<int64_t>::unsupported();
 }
 
+HalResult<Aidl::ChannelConfig> EmptyHalWrapper::getSessionChannel(int, int) {
+    ALOGV("Skipped getSessionChannel because %s", getUnsupportedMessage());
+    return HalResult<Aidl::ChannelConfig>::unsupported();
+}
+
+HalResult<void> EmptyHalWrapper::closeSessionChannel(int, int) {
+    ALOGV("Skipped closeSessionChannel because %s", getUnsupportedMessage());
+    return HalResult<void>::unsupported();
+}
+
+const char* EmptyHalWrapper::getUnsupportedMessage() {
+    return "Power HAL is not supported";
+}
+
 // -------------------------------------------------------------------------------------------------
 
 HalResult<void> HidlHalWrapperV1_0::setBoost(Aidl::Boost boost, int32_t durationMs) {
     if (boost == Aidl::Boost::INTERACTION) {
         return sendPowerHint(V1_3::PowerHint::INTERACTION, durationMs);
     } else {
-        ALOGV("Skipped setBoost %s because Power HAL AIDL not available", toString(boost).c_str());
-        return HalResult<void>::unsupported();
+        return EmptyHalWrapper::setBoost(boost, durationMs);
     }
 }
 
@@ -92,9 +113,7 @@
         case Aidl::Mode::DOUBLE_TAP_TO_WAKE:
             return setFeature(V1_0::Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE, enabled);
         default:
-            ALOGV("Skipped setMode %s because Power HAL AIDL not available",
-                  toString(mode).c_str());
-            return HalResult<void>::unsupported();
+            return EmptyHalWrapper::setMode(mode, enabled);
     }
 }
 
@@ -113,16 +132,8 @@
     return HalResult<void>::fromReturn(ret);
 }
 
-HalResult<std::shared_ptr<Aidl::IPowerHintSession>> HidlHalWrapperV1_0::createHintSession(
-        int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) {
-    ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available",
-          threadIds.size());
-    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
-}
-
-HalResult<int64_t> HidlHalWrapperV1_0::getHintSessionPreferredRate() {
-    ALOGV("Skipped getHintSessionPreferredRate because Power HAL not available");
-    return HalResult<int64_t>::unsupported();
+const char* HidlHalWrapperV1_0::getUnsupportedMessage() {
+    return "Power HAL AIDL is not supported";
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -191,7 +202,7 @@
 
     // Quick return if boost is not supported by HAL
     if (idx >= mBoostSupportedArray.size() || mBoostSupportedArray[idx] == HalSupport::OFF) {
-        ALOGV("Skipped setBoost %s because Power HAL doesn't support it", toString(boost).c_str());
+        ALOGV("Skipped setBoost %s because %s", toString(boost).c_str(), getUnsupportedMessage());
         return HalResult<void>::unsupported();
     }
 
@@ -207,8 +218,8 @@
 
         mBoostSupportedArray[idx] = isSupported ? HalSupport::ON : HalSupport::OFF;
         if (!isSupported) {
-            ALOGV("Skipped setBoost %s because Power HAL doesn't support it",
-                  toString(boost).c_str());
+            ALOGV("Skipped setBoost %s because %s", toString(boost).c_str(),
+                  getUnsupportedMessage());
             return HalResult<void>::unsupported();
         }
     }
@@ -223,7 +234,7 @@
 
     // Quick return if mode is not supported by HAL
     if (idx >= mModeSupportedArray.size() || mModeSupportedArray[idx] == HalSupport::OFF) {
-        ALOGV("Skipped setMode %s because Power HAL doesn't support it", toString(mode).c_str());
+        ALOGV("Skipped setMode %s because %s", toString(mode).c_str(), getUnsupportedMessage());
         return HalResult<void>::unsupported();
     }
 
@@ -236,8 +247,7 @@
 
         mModeSupportedArray[idx] = isSupported ? HalSupport::ON : HalSupport::OFF;
         if (!isSupported) {
-            ALOGV("Skipped setMode %s because Power HAL doesn't support it",
-                  toString(mode).c_str());
+            ALOGV("Skipped setMode %s because %s", toString(mode).c_str(), getUnsupportedMessage());
             return HalResult<void>::unsupported();
         }
     }
@@ -251,7 +261,17 @@
     std::shared_ptr<Aidl::IPowerHintSession> appSession;
     return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::
             fromStatus(mHandle->createHintSession(tgid, uid, threadIds, durationNanos, &appSession),
-                       appSession);
+                       std::move(appSession));
+}
+
+HalResult<std::shared_ptr<Aidl::IPowerHintSession>> AidlHalWrapper::createHintSessionWithConfig(
+        int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+        Aidl::SessionTag tag, Aidl::SessionConfig* config) {
+    std::shared_ptr<Aidl::IPowerHintSession> appSession;
+    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::
+            fromStatus(mHandle->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos,
+                                                            tag, config, &appSession),
+                       std::move(appSession));
 }
 
 HalResult<int64_t> AidlHalWrapper::getHintSessionPreferredRate() {
@@ -260,6 +280,20 @@
     return HalResult<int64_t>::fromStatus(result, rate);
 }
 
+HalResult<Aidl::ChannelConfig> AidlHalWrapper::getSessionChannel(int tgid, int uid) {
+    Aidl::ChannelConfig config;
+    auto result = mHandle->getSessionChannel(tgid, uid, &config);
+    return HalResult<Aidl::ChannelConfig>::fromStatus(result, std::move(config));
+}
+
+HalResult<void> AidlHalWrapper::closeSessionChannel(int tgid, int uid) {
+    return toHalResult(mHandle->closeSessionChannel(tgid, uid));
+}
+
+const char* AidlHalWrapper::getUnsupportedMessage() {
+    return "Power HAL doesn't support it";
+}
+
 // -------------------------------------------------------------------------------------------------
 
 } // namespace power
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index 3d2cf29..a720296 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -256,6 +256,23 @@
     ASSERT_TRUE(result.isOk());
 }
 
+TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionWithConfigSuccessful) {
+    std::vector<int> threadIds{gettid()};
+    int32_t tgid = 999;
+    int32_t uid = 1001;
+    int64_t durationNanos = 16666666L;
+    SessionTag tag = SessionTag::OTHER;
+    SessionConfig out;
+    EXPECT_CALL(*mMockHal.get(),
+                createHintSessionWithConfig(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos),
+                                            Eq(tag), _, _))
+            .Times(Exactly(1))
+            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+    auto result =
+            mWrapper->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, tag, &out);
+    ASSERT_TRUE(result.isOk());
+}
+
 TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionFailed) {
     int32_t tgid = 999;
     int32_t uid = 1001;
@@ -279,3 +296,18 @@
     int64_t rate = result.value();
     ASSERT_GE(0, rate);
 }
+
+TEST_F(PowerHalWrapperAidlTest, TestSessionChannel) {
+    int32_t tgid = 999;
+    int32_t uid = 1001;
+    EXPECT_CALL(*mMockHal.get(), getSessionChannel(Eq(tgid), Eq(uid), _))
+            .Times(Exactly(1))
+            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+    EXPECT_CALL(*mMockHal.get(), closeSessionChannel(Eq(tgid), Eq(uid)))
+            .Times(Exactly(1))
+            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+    auto createResult = mWrapper->getSessionChannel(tgid, uid);
+    ASSERT_TRUE(createResult.isOk());
+    auto closeResult = mWrapper->closeSessionChannel(tgid, uid);
+    ASSERT_TRUE(closeResult.isOk());
+}
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/Display/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h
index c0e77bb..d07cdf5 100644
--- a/services/surfaceflinger/Display/DisplayModeRequest.h
+++ b/services/surfaceflinger/Display/DisplayModeRequest.h
@@ -27,9 +27,6 @@
 
     // 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 b96264b..4f81482 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -24,7 +24,6 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
-#include <common/FlagManager.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/DisplayColorProfile.h>
@@ -68,6 +67,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)),
@@ -106,9 +106,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);
@@ -173,6 +171,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);
@@ -182,33 +181,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) {
@@ -222,17 +214,6 @@
 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;
 
@@ -538,7 +519,8 @@
     }
 }
 
-auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode) -> DesiredModeAction {
+auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode, bool force)
+        -> DesiredModeAction {
     ATRACE_CALL();
 
     const auto& desiredModePtr = desiredMode.mode.modePtr;
@@ -546,26 +528,20 @@
     LOG_ALWAYS_FATAL_IF(getPhysicalId() != desiredModePtr->getPhysicalDisplayId(),
                         "DisplayId mismatch");
 
-    // TODO (b/318533819): Stringize DisplayModeRequest.
-    ALOGD("%s(%s, force=%s)", __func__, to_string(*desiredModePtr).c_str(),
-          desiredMode.force ? "true" : "false");
+    ALOGV("%s(%s)", __func__, to_string(*desiredModePtr).c_str());
 
     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 (!desiredMode.force && activeMode.modePtr->getId() == desiredModePtr->getId()) {
+    if (!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 46534de..4ab6321 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,7 +189,8 @@
 
     enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch };
 
-    DesiredModeAction setDesiredMode(display::DisplayModeRequest&&) EXCLUDES(mDesiredModeLock);
+    DesiredModeAction setDesiredMode(display::DisplayModeRequest&&, bool force = false)
+            EXCLUDES(mDesiredModeLock);
 
     using DisplayModeRequestOpt = ftl::Optional<display::DisplayModeRequest>;
 
@@ -270,9 +271,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;
@@ -362,7 +361,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..17f6f31 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) {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index db66f5b..704ece5 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -27,7 +27,6 @@
 #include "HWC2.h"
 
 #include <android/configuration.h>
-#include <common/FlagManager.h>
 #include <ui/Fence.h>
 #include <ui/FloatRect.h>
 #include <ui/GraphicBuffer.h>
@@ -417,19 +416,7 @@
                                               VsyncPeriodChangeTimeline* outTimeline) {
     ALOGV("[%" PRIu64 "] setActiveConfigWithConstraints", mId);
 
-    // 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)) {
+    if (isVsyncPeriodSwitchSupported()) {
         Hwc2::IComposerClient::VsyncPeriodChangeConstraints hwc2Constraints;
         hwc2Constraints.desiredTimeNanos = constraints.desiredTimeNanos;
         hwc2Constraints.seamlessRequired = constraints.seamlessRequired;
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/FpsReporter.cpp b/services/surfaceflinger/FpsReporter.cpp
index 155cf4d..a47348f 100644
--- a/services/surfaceflinger/FpsReporter.cpp
+++ b/services/surfaceflinger/FpsReporter.cpp
@@ -26,13 +26,12 @@
 
 namespace android {
 
-FpsReporter::FpsReporter(frametimeline::FrameTimeline& frameTimeline, SurfaceFlinger& flinger,
-                         std::unique_ptr<Clock> clock)
-      : mFrameTimeline(frameTimeline), mFlinger(flinger), mClock(std::move(clock)) {
+FpsReporter::FpsReporter(frametimeline::FrameTimeline& frameTimeline, std::unique_ptr<Clock> clock)
+      : mFrameTimeline(frameTimeline), mClock(std::move(clock)) {
     LOG_ALWAYS_FATAL_IF(mClock == nullptr, "Passed in null clock when constructing FpsReporter!");
 }
 
-void FpsReporter::dispatchLayerFps() {
+void FpsReporter::dispatchLayerFps(const frontend::LayerHierarchy& layerHierarchy) {
     const auto now = mClock->now();
     if (now - mLastDispatch < kMinDispatchDuration) {
         return;
@@ -52,31 +51,42 @@
     }
 
     std::unordered_set<int32_t> seenTasks;
-    std::vector<std::pair<TrackedListener, sp<Layer>>> listenersAndLayersToReport;
+    std::vector<std::pair<TrackedListener, const frontend::LayerHierarchy*>>
+            listenersAndLayersToReport;
 
-    mFlinger.mCurrentState.traverse([&](Layer* layer) {
-        auto& currentState = layer->getDrawingState();
-        if (currentState.metadata.has(gui::METADATA_TASK_ID)) {
-            int32_t taskId = currentState.metadata.getInt32(gui::METADATA_TASK_ID, 0);
+    layerHierarchy.traverse([&](const frontend::LayerHierarchy& hierarchy,
+                                const frontend::LayerHierarchy::TraversalPath& traversalPath) {
+        if (traversalPath.variant == frontend::LayerHierarchy::Variant::Detached) {
+            return false;
+        }
+        const auto& metadata = hierarchy.getLayer()->metadata;
+        if (metadata.has(gui::METADATA_TASK_ID)) {
+            int32_t taskId = metadata.getInt32(gui::METADATA_TASK_ID, 0);
             if (seenTasks.count(taskId) == 0) {
                 // localListeners is expected to be tiny
                 for (TrackedListener& listener : localListeners) {
                     if (listener.taskId == taskId) {
                         seenTasks.insert(taskId);
-                        listenersAndLayersToReport.push_back(
-                                {listener, sp<Layer>::fromExisting(layer)});
+                        listenersAndLayersToReport.push_back({listener, &hierarchy});
                         break;
                     }
                 }
             }
         }
+        return true;
     });
 
-    for (const auto& [listener, layer] : listenersAndLayersToReport) {
+    for (const auto& [listener, hierarchy] : listenersAndLayersToReport) {
         std::unordered_set<int32_t> layerIds;
 
-        layer->traverse(LayerVector::StateSet::Current,
-                        [&](Layer* layer) { layerIds.insert(layer->getSequence()); });
+        hierarchy->traverse([&](const frontend::LayerHierarchy& hierarchy,
+                                const frontend::LayerHierarchy::TraversalPath& traversalPath) {
+            if (traversalPath.variant == frontend::LayerHierarchy::Variant::Detached) {
+                return false;
+            }
+            layerIds.insert(static_cast<int32_t>(hierarchy.getLayer()->id));
+            return true;
+        });
 
         listener.listener->onFpsReported(mFrameTimeline.computeFps(layerIds));
     }
diff --git a/services/surfaceflinger/FpsReporter.h b/services/surfaceflinger/FpsReporter.h
index 438b1aa..01f1e07 100644
--- a/services/surfaceflinger/FpsReporter.h
+++ b/services/surfaceflinger/FpsReporter.h
@@ -24,6 +24,7 @@
 
 #include "Clock.h"
 #include "FrameTimeline/FrameTimeline.h"
+#include "FrontEnd/LayerHierarchy.h"
 #include "WpHash.h"
 
 namespace android {
@@ -33,13 +34,13 @@
 
 class FpsReporter : public IBinder::DeathRecipient {
 public:
-    FpsReporter(frametimeline::FrameTimeline& frameTimeline, SurfaceFlinger& flinger,
+    FpsReporter(frametimeline::FrameTimeline& frameTimeline,
                 std::unique_ptr<Clock> clock = std::make_unique<SteadyClock>());
 
     // Dispatches updated layer fps values for the registered listeners
     // This method promotes Layer weak pointers and performs layer stack traversals, so mStateLock
     // must be held when calling this method.
-    void dispatchLayerFps() EXCLUDES(mMutex);
+    void dispatchLayerFps(const frontend::LayerHierarchy&) EXCLUDES(mMutex);
 
     // Override for IBinder::DeathRecipient
     void binderDied(const wp<IBinder>&) override;
@@ -58,7 +59,6 @@
     };
 
     frametimeline::FrameTimeline& mFrameTimeline;
-    SurfaceFlinger& mFlinger;
     static const constexpr std::chrono::steady_clock::duration kMinDispatchDuration =
             std::chrono::milliseconds(500);
     std::unique_ptr<Clock> mClock;
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index cf8b3bf..a145e59 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -75,9 +75,9 @@
     mHandler->dispatchFrame(vsyncId, expectedVsyncTime);
 }
 
-void MessageQueue::initVsync(std::shared_ptr<scheduler::VSyncDispatch> dispatch,
-                             frametimeline::TokenManager& tokenManager,
-                             std::chrono::nanoseconds workDuration) {
+void MessageQueue::initVsyncInternal(std::shared_ptr<scheduler::VSyncDispatch> dispatch,
+                                     frametimeline::TokenManager& tokenManager,
+                                     std::chrono::nanoseconds workDuration) {
     std::unique_ptr<scheduler::VSyncCallbackRegistration> oldRegistration;
     {
         std::lock_guard lock(mVsync.mutex);
@@ -87,7 +87,7 @@
     }
 
     // See comments in onNewVsyncSchedule. Today, oldRegistration should be
-    // empty, but nothing prevents us from calling initVsync multiple times, so
+    // empty, but nothing prevents us from calling initVsyncInternal multiple times, so
     // go ahead and destruct it outside the lock for safety.
     oldRegistration.reset();
 }
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index a523147..edb424b 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -65,8 +65,9 @@
 public:
     virtual ~MessageQueue() = default;
 
-    virtual void initVsync(std::shared_ptr<scheduler::VSyncDispatch>, frametimeline::TokenManager&,
-                           std::chrono::nanoseconds workDuration) = 0;
+    virtual void initVsyncInternal(std::shared_ptr<scheduler::VSyncDispatch>,
+                                   frametimeline::TokenManager&,
+                                   std::chrono::nanoseconds workDuration) = 0;
     virtual void destroyVsync() = 0;
     virtual void setDuration(std::chrono::nanoseconds workDuration) = 0;
     virtual void waitMessage() = 0;
@@ -137,8 +138,8 @@
 public:
     explicit MessageQueue(ICompositor&);
 
-    void initVsync(std::shared_ptr<scheduler::VSyncDispatch>, frametimeline::TokenManager&,
-                   std::chrono::nanoseconds workDuration) override;
+    void initVsyncInternal(std::shared_ptr<scheduler::VSyncDispatch>, frametimeline::TokenManager&,
+                           std::chrono::nanoseconds workDuration) override;
     void destroyVsync() override;
     void setDuration(std::chrono::nanoseconds workDuration) override;
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index 67e1b9c..d51af9a 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -49,10 +49,10 @@
 
 public:
     // TODO(b/185535769): Inject clock to avoid sleeping in tests.
-    RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate, PowerMode currentPowerMode)
+    RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate)
           : mTimeStats(timeStats),
             mCurrentRefreshRate(currentRefreshRate),
-            mCurrentPowerMode(currentPowerMode) {}
+            mCurrentPowerMode(PowerMode::OFF) {}
 
     void setPowerMode(PowerMode mode) {
         if (mCurrentPowerMode == mode) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 27ca17f..c76d4bd 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -77,8 +77,7 @@
         mFeatures(features),
         mVsyncConfiguration(factory.createVsyncConfiguration(activeRefreshRate)),
         mVsyncModulator(sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs())),
-        mRefreshRateStats(std::make_unique<RefreshRateStats>(timeStats, activeRefreshRate,
-                                                             hal::PowerMode::OFF)),
+        mRefreshRateStats(std::make_unique<RefreshRateStats>(timeStats, activeRefreshRate)),
         mSchedulerCallback(callback),
         mVsyncTrackerCallback(vsyncTrackerCallback) {}
 
@@ -96,6 +95,11 @@
     demotePacesetterDisplay();
 }
 
+void Scheduler::initVsync(frametimeline::TokenManager& tokenManager,
+                          std::chrono::nanoseconds workDuration) {
+    Impl::initVsyncInternal(getVsyncSchedule()->getDispatch(), tokenManager, workDuration);
+}
+
 void Scheduler::startTimers() {
     using namespace sysprop;
     using namespace std::string_literals;
@@ -642,13 +646,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 f62f1ba..9912622 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -131,7 +131,7 @@
 
     void run();
 
-    using Impl::initVsync;
+    void initVsync(frametimeline::TokenManager&, std::chrono::nanoseconds workDuration);
 
     using Impl::getScheduledFrameTime;
     using Impl::setDuration;
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index db6a187..3491600 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -89,8 +89,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 {
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 722ea0b..6f656d2 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -63,7 +63,8 @@
 
     // 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
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/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 178828a..47e7474 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -799,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);
     }
 }
 
@@ -840,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 =
@@ -1228,10 +1230,8 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& desiredMode) {
-    const auto mode = desiredMode.mode;
-    const auto displayId = mode.modePtr->getPhysicalDisplayId();
-
+void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& request, bool force) {
+    const auto displayId = request.mode.modePtr->getPhysicalDisplayId();
     ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
     const auto display = getDisplayDeviceLocked(displayId);
@@ -1240,9 +1240,10 @@
         return;
     }
 
-    const bool emitEvent = desiredMode.emitEvent;
+    const auto mode = request.mode;
+    const bool emitEvent = request.emitEvent;
 
-    switch (display->setDesiredMode(std::move(desiredMode))) {
+    switch (display->setDesiredMode(std::move(request), force)) {
         case DisplayDevice::DesiredModeAction::InitiateDisplayModeSwitch:
             // DisplayDevice::setDesiredMode updated the render rate, so inform Scheduler.
             mScheduler->setRenderRate(displayId,
@@ -1428,8 +1429,7 @@
               to_string(displayModePtrOpt->get()->getVsyncRate()).c_str(),
               to_string(display->getId()).c_str());
 
-        if ((!FlagManager::getInstance().connected_display() || !desiredModeOpt->force) &&
-            display->getActiveMode() == desiredModeOpt->mode) {
+        if (display->getActiveMode() == desiredModeOpt->mode) {
             applyActiveMode(display);
             continue;
         }
@@ -1953,6 +1953,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(
@@ -2002,7 +2003,6 @@
                                                      Hwc2::Composer::DisplayBrightnessOptions{
                                                              .applyImmediately = true});
                    }
-
                } else {
                    ALOGE("%s: Invalid display token %p", whence, displayToken.get());
                    return ftl::yield<status_t>(NAME_NOT_FOUND);
@@ -3098,7 +3098,7 @@
     {
         Mutex::Autolock lock(mStateLock);
         if (mFpsReporter) {
-            mFpsReporter->dispatchLayerFps();
+            mFpsReporter->dispatchLayerFps(mLayerHierarchyBuilder.getHierarchy());
         }
 
         if (mTunnelModeEnabledReporter) {
@@ -3295,88 +3295,14 @@
     std::vector<HWComposer::HWCDisplayMode> hwcModes;
     std::optional<hal::HWDisplayId> activeModeHwcId;
 
-    const bool isExternalDisplay = FlagManager::getInstance().connected_display() &&
-            getHwComposer().getDisplayConnectionType(displayId) ==
-                    ui::DisplayConnectionType::External;
-
     int attempt = 0;
     constexpr int kMaxAttempts = 3;
     do {
         hwcModes = getHwComposer().getModes(displayId,
                                             scheduler::RefreshRateSelector::kMinSupportedFrameRate
                                                     .getPeriodNsecs());
-
         activeModeHwcId = getHwComposer().getActiveMode(displayId);
 
-        if (isExternalDisplay) {
-            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()) {
-                activeModeHwcId = 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()) {
-                activeModeHwcId = 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()) {
-                    activeModeHwcId = iter->hwcId;
-                    break;
-                }
-
-                // hwcModeOpts was empty, use hwcModes[0] as the last resort
-                activeModeHwcId = hwcModes[0].hwcId;
-            }
-        }
-
         const auto isActiveMode = [activeModeHwcId](const HWComposer::HWCDisplayMode& mode) {
             return mode.hwcId == activeModeHwcId;
         };
@@ -3436,10 +3362,6 @@
                 return pair.second->getHwcId() == activeModeHwcId;
             })->second;
 
-    if (isExternalDisplay) {
-        ALOGI("External display %s initial mode: {%s}", to_string(displayId).c_str(),
-              to_string(*activeMode).c_str());
-    }
     return {modes, activeMode};
 }
 
@@ -3625,9 +3547,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;
 
@@ -3746,27 +3666,6 @@
     }
 
     mDisplays.try_emplace(displayToken, std::move(display));
-
-    // For an external display, loadDisplayModes already selected 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) {
@@ -4335,13 +4234,12 @@
                                           /* workDuration */ activeRefreshRate.getPeriod(),
                                           /* readyDuration */ configs.late.sfWorkDuration);
 
-    mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(),
-                          *mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration);
+    mScheduler->initVsync(*mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration);
 
     mRegionSamplingThread =
             sp<RegionSamplingThread>::make(*this,
                                            RegionSamplingThread::EnvironmentTimingTunables());
-    mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline, *this);
+    mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline);
 }
 
 void SurfaceFlinger::doCommitTransactions() {
@@ -5913,12 +5811,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();
@@ -5929,18 +5821,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);
@@ -5953,12 +5837,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;
     }
@@ -5966,8 +5863,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;
     }
 
@@ -5984,7 +5881,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
@@ -6029,7 +5926,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()) {
@@ -6070,7 +5967,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.");
@@ -8258,13 +8155,8 @@
     //
     // 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)
@@ -8441,7 +8333,7 @@
         return INVALID_OPERATION;
     }
 
-    setDesiredMode({std::move(preferredMode), .emitEvent = true, .force = force});
+    setDesiredMode({std::move(preferredMode), .emitEvent = true}, force);
 
     // Update the frameRateOverride list as the display render rate might have changed
     if (mScheduler->updateFrameRateOverrides(scheduler::GlobalSignals{}, preferredFps)) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 24a1a10..c8e2a4d 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -719,7 +719,7 @@
     // Show hdr sdr ratio overlay
     bool mHdrSdrRatioOverlay = false;
 
-    void setDesiredMode(display::DisplayModeRequest&&) REQUIRES(mStateLock);
+    void setDesiredMode(display::DisplayModeRequest&&, bool force = false) REQUIRES(mStateLock);
 
     status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps,
                                        Fps maxFps);
@@ -904,7 +904,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
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index a27e100..255b517 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -128,6 +128,7 @@
     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(vulkan_renderengine);
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
 #undef DUMP_FLAG_INTERVAL
@@ -202,6 +203,7 @@
 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(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..15938c0 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -68,6 +68,7 @@
     bool fp16_client_target() const;
     bool game_default_frame_rate() const;
     bool enable_layer_command_batching() const;
+    bool vulkan_renderengine() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index 8a5500f..a6b12d0 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -405,8 +405,8 @@
                                              Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()));
 
     android::mock::TimeStats timeStats;
-    RefreshRateStats refreshRateStats(timeStats, Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
-                                      PowerMode::OFF);
+    RefreshRateStats refreshRateStats(timeStats,
+                                      Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()));
 
     const auto fpsOpt = displayModes.get(modeId).transform(
             [](const DisplayModePtr& mode) { return mode->getVsyncRate(); });
@@ -441,7 +441,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/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/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 99fef7e..387d2f2 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,11 +461,9 @@
                     ? IComposerClient::DisplayConnectionType::INTERNAL
                     : IComposerClient::DisplayConnectionType::EXTERNAL;
 
-            using ::testing::AtLeast;
             EXPECT_CALL(*test->mComposer, getDisplayConnectionType(HWC_DISPLAY_ID, _))
-                    .Times(AtLeast(1))
-                    .WillRepeatedly(DoAll(SetArgPointee<1>(CONNECTION_TYPE),
-                                          Return(hal::V2_4::Error::NONE)));
+                    .WillOnce(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/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
index f695b09..9e8e306 100644
--- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
@@ -24,7 +24,11 @@
 #include <gtest/gtest.h>
 #include <gui/LayerMetadata.h>
 
+#include "Client.h" // temporarily needed for LayerCreationArgs
 #include "FpsReporter.h"
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerLifecycleManager.h"
 #include "Layer.h"
 #include "TestableSurfaceFlinger.h"
 #include "fake/FakeClock.h"
@@ -76,7 +80,15 @@
 
     sp<Layer> createBufferStateLayer(LayerMetadata metadata);
 
-    TestableSurfaceFlinger mFlinger;
+    LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, uint32_t parentId,
+                                 LayerMetadata metadata);
+
+    void createRootLayer(uint32_t id, LayerMetadata metadata);
+
+    void createLayer(uint32_t id, uint32_t parentId, LayerMetadata metadata);
+
+    frontend::LayerLifecycleManager mLifecycleManager;
+
     mock::FrameTimeline mFrameTimeline =
             mock::FrameTimeline(std::make_shared<impl::TimeStats>(), 0);
 
@@ -89,8 +101,8 @@
 
     sp<TestableFpsListener> mFpsListener;
     fake::FakeClock* mClock = new fake::FakeClock();
-    sp<FpsReporter> mFpsReporter = sp<FpsReporter>::make(mFrameTimeline, *(mFlinger.flinger()),
-                                                         std::unique_ptr<Clock>(mClock));
+    sp<FpsReporter> mFpsReporter =
+            sp<FpsReporter>::make(mFrameTimeline, std::unique_ptr<Clock>(mClock));
 };
 
 FpsReporterTest::FpsReporterTest() {
@@ -98,9 +110,6 @@
             ::testing::UnitTest::GetInstance()->current_test_info();
     ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
 
-    mFlinger.setupMockScheduler();
-    mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
-
     mFpsListener = sp<TestableFpsListener>::make();
 }
 
@@ -110,76 +119,94 @@
     ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
 }
 
-sp<Layer> FpsReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) {
+LayerCreationArgs FpsReporterTest::createArgs(uint32_t id, bool canBeRoot, uint32_t parentId,
+                                              LayerMetadata metadata) {
     sp<Client> client;
-    LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, metadata);
-    return sp<Layer>::make(args);
+    LayerCreationArgs args(std::make_optional(id));
+    args.name = "testlayer";
+    args.addToRoot = canBeRoot;
+    args.flags = LAYER_FLAGS;
+    args.metadata = metadata;
+    args.parentId = parentId;
+    return args;
+}
+
+void FpsReporterTest::createRootLayer(uint32_t id, LayerMetadata metadata = LayerMetadata()) {
+    std::vector<std::unique_ptr<frontend::RequestedLayerState>> layers;
+    layers.emplace_back(std::make_unique<frontend::RequestedLayerState>(
+            createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/UNASSIGNED_LAYER_ID,
+                       /*metadata=*/metadata)));
+    mLifecycleManager.addLayers(std::move(layers));
+}
+
+void FpsReporterTest::createLayer(uint32_t id, uint32_t parentId,
+                                  LayerMetadata metadata = LayerMetadata()) {
+    std::vector<std::unique_ptr<frontend::RequestedLayerState>> layers;
+    layers.emplace_back(std::make_unique<frontend::RequestedLayerState>(
+            createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentId,
+                       /*mirror=*/metadata)));
+    mLifecycleManager.addLayers(std::move(layers));
 }
 
 namespace {
 
 TEST_F(FpsReporterTest, callsListeners) {
-    mParent = createBufferStateLayer();
     constexpr int32_t kTaskId = 12;
     LayerMetadata targetMetadata;
     targetMetadata.setInt32(gui::METADATA_TASK_ID, kTaskId);
-    mTarget = createBufferStateLayer(targetMetadata);
-    mChild = createBufferStateLayer();
-    mGrandChild = createBufferStateLayer();
-    mUnrelated = createBufferStateLayer();
-    mParent->addChild(mTarget);
-    mTarget->addChild(mChild);
-    mChild->addChild(mGrandChild);
-    mParent->commitChildList();
-    mFlinger.mutableCurrentState().layersSortedByZ.add(mParent);
-    mFlinger.mutableCurrentState().layersSortedByZ.add(mTarget);
-    mFlinger.mutableCurrentState().layersSortedByZ.add(mChild);
-    mFlinger.mutableCurrentState().layersSortedByZ.add(mGrandChild);
+
+    createRootLayer(1, targetMetadata);
+    createLayer(11, 1);
+    createLayer(111, 11);
+
+    frontend::LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     float expectedFps = 44.0;
 
-    EXPECT_CALL(mFrameTimeline,
-                computeFps(UnorderedElementsAre(mTarget->getSequence(), mChild->getSequence(),
-                                                mGrandChild->getSequence())))
+    EXPECT_CALL(mFrameTimeline, computeFps(UnorderedElementsAre(1, 11, 111)))
             .WillOnce(Return(expectedFps));
 
     mFpsReporter->addListener(mFpsListener, kTaskId);
     mClock->advanceTime(600ms);
-    mFpsReporter->dispatchLayerFps();
+    mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
     EXPECT_EQ(expectedFps, mFpsListener->lastReportedFps);
     mFpsReporter->removeListener(mFpsListener);
     Mock::VerifyAndClearExpectations(&mFrameTimeline);
 
     EXPECT_CALL(mFrameTimeline, computeFps(_)).Times(0);
-    mFpsReporter->dispatchLayerFps();
+    mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
 }
 
 TEST_F(FpsReporterTest, rateLimits) {
     const constexpr int32_t kTaskId = 12;
     LayerMetadata targetMetadata;
     targetMetadata.setInt32(gui::METADATA_TASK_ID, kTaskId);
-    mTarget = createBufferStateLayer(targetMetadata);
-    mFlinger.mutableCurrentState().layersSortedByZ.add(mTarget);
+    createRootLayer(1);
+    createLayer(11, 1, targetMetadata);
+
+    frontend::LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
 
     float firstFps = 44.0;
     float secondFps = 53.0;
 
-    EXPECT_CALL(mFrameTimeline, computeFps(UnorderedElementsAre(mTarget->getSequence())))
+    EXPECT_CALL(mFrameTimeline, computeFps(UnorderedElementsAre(11)))
             .WillOnce(Return(firstFps))
             .WillOnce(Return(secondFps));
 
     mFpsReporter->addListener(mFpsListener, kTaskId);
     mClock->advanceTime(600ms);
-    mFpsReporter->dispatchLayerFps();
+    mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
     EXPECT_EQ(firstFps, mFpsListener->lastReportedFps);
     mClock->advanceTime(200ms);
-    mFpsReporter->dispatchLayerFps();
+    mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
     EXPECT_EQ(firstFps, mFpsListener->lastReportedFps);
     mClock->advanceTime(200ms);
-    mFpsReporter->dispatchLayerFps();
+    mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
     EXPECT_EQ(firstFps, mFpsListener->lastReportedFps);
     mClock->advanceTime(200ms);
-    mFpsReporter->dispatchLayerFps();
+    mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
     EXPECT_EQ(secondFps, mFpsListener->lastReportedFps);
 }
 
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index e9c4d80..249ed40 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -72,7 +72,8 @@
 struct MessageQueueTest : testing::Test {
     void SetUp() override {
         EXPECT_CALL(*mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken));
-        EXPECT_NO_FATAL_FAILURE(mEventQueue.initVsync(mVSyncDispatch, mTokenManager, kDuration));
+        EXPECT_NO_FATAL_FAILURE(
+                mEventQueue.initVsyncInternal(mVSyncDispatch, mTokenManager, kDuration));
         EXPECT_CALL(*mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
     }
 
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
index e2e3d7b..fba77e9 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
@@ -37,7 +37,7 @@
     ~RefreshRateStatsTest();
 
     void resetStats(Fps fps) {
-        mRefreshRateStats = std::make_unique<RefreshRateStats>(mTimeStats, fps, PowerMode::OFF);
+        mRefreshRateStats = std::make_unique<RefreshRateStats>(mTimeStats, fps);
     }
 
     mock::TimeStats mTimeStats;
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 82b4ad0..8b16a8a 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -21,13 +21,9 @@
 #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 {
 
@@ -364,13 +360,6 @@
 }
 
 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());
@@ -440,12 +429,6 @@
 }
 
 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());
@@ -529,13 +512,6 @@
 }
 
 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..93c2829 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.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,35 +25,9 @@
 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.
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/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index f00eacc..9ef0749 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -268,7 +268,7 @@
                                                           vsyncTrackerCallback);
         }
 
-        mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), *mTokenManager, 0ms);
+        mScheduler->initVsync(*mTokenManager, 0ms);
 
         mScheduler->mutableAppConnectionHandle() =
                 mScheduler->createConnection(std::move(appEventThread));
@@ -750,7 +750,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 +792,7 @@
             return *this;
         }
 
-        auto& setPowerMode(std::optional<hal::PowerMode> mode) {
+        auto& setPowerMode(hal::PowerMode mode) {
             mPowerMode = mode;
             return *this;
         }
@@ -817,9 +816,7 @@
                                                          mHwcDisplayType);
             display->mutableIsConnected() = true;
 
-            if (mPowerMode) {
-                display->setPowerMode(*mPowerMode);
-            }
+            display->setPowerMode(mPowerMode);
 
             flinger->mutableHwcDisplayData()[mDisplayId].hwcDisplay = std::move(display);
 
@@ -885,7 +882,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 +959,7 @@
             return *this;
         }
 
-        auto& setPowerMode(std::optional<hal::PowerMode> mode) {
+        auto& setPowerMode(hal::PowerMode mode) {
             mCreationArgs.initialPowerMode = mode;
             return *this;
         }
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
index b17c8ad..ae41e7e 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
@@ -34,8 +34,6 @@
 
 namespace android::Hwc2::mock {
 
-using aidl::android::hardware::power::Boost;
-using aidl::android::hardware::power::Mode;
 using android::power::HalResult;
 
 class MockPowerHalController : public power::PowerHalController {
@@ -43,12 +41,22 @@
     MockPowerHalController();
     ~MockPowerHalController() override;
     MOCK_METHOD(void, init, (), (override));
-    MOCK_METHOD(HalResult<void>, setBoost, (Boost, int32_t), (override));
-    MOCK_METHOD(HalResult<void>, setMode, (Mode, bool), (override));
+    MOCK_METHOD(HalResult<void>, setBoost, (aidl::android::hardware::power::Boost, int32_t),
+                (override));
+    MOCK_METHOD(HalResult<void>, setMode, (aidl::android::hardware::power::Mode, bool), (override));
     MOCK_METHOD(HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>,
                 createHintSession, (int32_t, int32_t, const std::vector<int32_t>&, int64_t),
                 (override));
+    MOCK_METHOD(HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>,
+                createHintSessionWithConfig,
+                (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+                 int64_t durationNanos, aidl::android::hardware::power::SessionTag tag,
+                 aidl::android::hardware::power::SessionConfig* config),
+                (override));
     MOCK_METHOD(HalResult<int64_t>, getHintSessionPreferredRate, (), (override));
+    MOCK_METHOD(HalResult<aidl::android::hardware::power::ChannelConfig>, getSessionChannel,
+                (int tgid, int uid), (override));
+    MOCK_METHOD(HalResult<void>, closeSessionChannel, (int tgid, int uid), (override));
 };
 
 } // namespace android::Hwc2::mock
\ No newline at end of file