Merge changes I1c9370fd,Ibe54154b,I9d75ee51,I68818430 into udc-dev

* changes:
  [sf] support region sampling with new frontend
  [sf] provide a unique id for snapshots
  [sf] write layer snapshots to layer trace
  [sf] fix boot animation with new frontend
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index 2e0c95a..69ca793 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -297,8 +297,25 @@
     write /sys/kernel/debug/tracing/synthetic_events "rss_stat_throttled unsigned int mm_id; unsigned int curr; int member; long size"
 
     # allow creating event triggers
-    chmod 0666 /sys/kernel/debug/tracing/events/kmem/rss_stat/trigger
     chmod 0666 /sys/kernel/tracing/events/kmem/rss_stat/trigger
+    chmod 0666 /sys/kernel/debug/tracing/events/kmem/rss_stat/trigger
+
+    # allow enabling rss_stat_throttled
+    chmod 0666 /sys/kernel/tracing/events/synthetic/rss_stat_throttled/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/synthetic/rss_stat_throttled/enable
+
+on late-init && property:ro.boot.fastboot.boottrace=enabled
+    setprop debug.atrace.tags.enableflags 802922
+    setprop persist.traced.enable 0
+    write /sys/kernel/tracing/events/binder/binder_transaction/enable 1
+    write /sys/kernel/tracing/events/binder/binder_transaction_received/enable 1
+    write /sys/kernel/tracing/events/binder/binder_transaction_alloc_buf/enable 1
+    write /sys/kernel/tracing/events/binder/binder_set_priority/enable 1
+    write /sys/kernel/tracing/events/binder/binder_lock/enable 1
+    write /sys/kernel/tracing/events/binder/binder_locked/enable 1
+    write /sys/kernel/tracing/events/binder/binder_unlock/enable 1
+    write /sys/kernel/debug/tracing/tracing_on 1
+    write /sys/kernel/tracing/tracing_on 1
 
 on late-init && property:ro.boot.fastboot.boottrace=enabled
     setprop debug.atrace.tags.enableflags 802922
@@ -405,6 +422,13 @@
 on property:sys.boot_completed=1 && property:ro.boot.fastboot.boottrace=enabled
     setprop debug.atrace.tags.enableflags 0
     setprop persist.traced.enable 1
+    write /sys/kernel/tracing/events/binder/binder_transaction/enable 0
+    write /sys/kernel/tracing/events/binder/binder_transaction_received/enable 0
+    write /sys/kernel/tracing/events/binder/binder_transaction_alloc_buf/enable 0
+    write /sys/kernel/tracing/events/binder/binder_set_priority/enable 0
+    write /sys/kernel/tracing/events/binder/binder_lock/enable 0
+    write /sys/kernel/tracing/events/binder/binder_locked/enable 0
+    write /sys/kernel/tracing/events/binder/binder_unlock/enable 0
     write /sys/kernel/debug/tracing/tracing_on 0
     write /sys/kernel/tracing/tracing_on 0
 
diff --git a/include/android/choreographer.h b/include/android/choreographer.h
index cd8e63d..f999708 100644
--- a/include/android/choreographer.h
+++ b/include/android/choreographer.h
@@ -219,12 +219,16 @@
  *
  * Note that this time should \b not be used to advance animation clocks.
  * Instead, see AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos().
+ *
+ * Available since API level 33.
  */
 int64_t AChoreographerFrameCallbackData_getFrameTimeNanos(
         const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33);
 
 /**
  * The number of possible frame timelines.
+ *
+ * Available since API level 33.
  */
 size_t AChoreographerFrameCallbackData_getFrameTimelinesLength(
         const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33);
@@ -233,15 +237,20 @@
  * Gets the index of the platform-preferred frame timeline.
  * The preferred frame timeline is the default
  * by which the platform scheduled the app, based on the device configuration.
+ *
+ * Available since API level 33.
  */
 size_t AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(
         const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33);
 
 /**
  * Gets the token used by the platform to identify the frame timeline at the given \c index.
+ * q
+ * Available since API level 33.
  *
  * \param index index of a frame timeline, in \f( [0, FrameTimelinesLength) \f). See
  * AChoreographerFrameCallbackData_getFrameTimelinesLength()
+ *
  */
 AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
         const AChoreographerFrameCallbackData* data, size_t index) __INTRODUCED_IN(33);
@@ -250,6 +259,8 @@
  * Gets the time in nanoseconds at which the frame described at the given \c index is expected to
  * be presented. This time should be used to advance any animation clocks.
  *
+ * Available since API level 33.
+ *
  * \param index index of a frame timeline, in \f( [0, FrameTimelinesLength) \f). See
  * AChoreographerFrameCallbackData_getFrameTimelinesLength()
  */
@@ -260,6 +271,8 @@
  * Gets the time in nanoseconds at which the frame described at the given \c index needs to be
  * ready by in order to be presented on time.
  *
+ * Available since API level 33.
+ *
  * \param index index of a frame timeline, in \f( [0, FrameTimelinesLength) \f). See
  * AChoreographerFrameCallbackData_getFrameTimelinesLength()
  */
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 79c59f2..e4926a6 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -524,6 +524,8 @@
  * Sets the desired extended range brightness for the layer. This only applies for layers whose
  * dataspace has RANGE_EXTENDED set on it.
  *
+ * Available since API level 34.
+ *
  * @param surface_control The layer whose extended range brightness is being specified
  * @param currentBufferRatio The current hdr/sdr ratio of the current buffer as represented as
  *                           peakHdrBrightnessInNits / targetSdrWhitePointInNits. For example if the
@@ -653,6 +655,8 @@
  * and pushing buffers earlier for server side queuing will be advantageous
  * in such cases.
  *
+ * Available since API level 31.
+ *
  * \param transaction The transaction in which to make the change.
  * \param surface_control The ASurfaceControl on which to control buffer backpressure behavior.
  * \param enableBackPressure Whether to enable back pressure.
@@ -674,6 +678,8 @@
  * AChoreographer_postVsyncCallback(). The \c vsyncId can then be extracted from the
  * callback payload using AChoreographerFrameCallbackData_getFrameTimelineVsyncId().
  *
+ * Available since API level 33.
+ *
  * \param vsyncId The vsync ID received from AChoreographer, setting the frame's presentation target
  * to the corresponding expected presentation time and deadline from the frame to be rendered. A
  * stale or invalid value will be ignored.
diff --git a/include/input/InputEventLabels.h b/include/input/InputEventLabels.h
index 4668fce..9dedd2b 100644
--- a/include/input/InputEventLabels.h
+++ b/include/input/InputEventLabels.h
@@ -30,6 +30,12 @@
     int value;
 };
 
+struct EvdevEventLabel {
+    std::string type;
+    std::string code;
+    std::string value;
+};
+
 //   NOTE: If you want a new key code, axis code, led code or flag code in keylayout file,
 //   then you must add it to InputEventLabels.cpp.
 
@@ -52,6 +58,8 @@
 
     static std::optional<int> getLedByLabel(const char* label);
 
+    static EvdevEventLabel getLinuxEvdevLabel(int32_t type, int32_t code, int32_t value);
+
 private:
     static const std::unordered_map<std::string, int> KEYCODES;
 
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 1c52792..a1be542 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -38,6 +38,7 @@
 #include <binder/IBinder.h>
 #include <binder/Parcelable.h>
 #include <input/Input.h>
+#include <input/InputVerifier.h>
 #include <sys/stat.h>
 #include <ui/Transform.h>
 #include <utils/BitSet.h>
@@ -444,6 +445,7 @@
 
 private:
     std::shared_ptr<InputChannel> mChannel;
+    InputVerifier mInputVerifier;
 };
 
 /*
diff --git a/include/input/InputVerifier.h b/include/input/InputVerifier.h
new file mode 100644
index 0000000..d4589f5
--- /dev/null
+++ b/include/input/InputVerifier.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 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 <input/Input.h>
+#include <map>
+
+namespace android {
+
+/*
+ * Crash if the provided touch stream is inconsistent.
+ *
+ * TODO(b/211379801): Add support for hover events:
+ * - No hover move without enter
+ * - No touching pointers when hover enter
+ * - No hovering pointers when touching
+ * - Only 1 hovering pointer max
+ */
+class InputVerifier {
+public:
+    InputVerifier(const std::string& name);
+
+    void processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount,
+                         const PointerProperties* pointerProperties,
+                         const PointerCoords* pointerCoords, int32_t flags);
+
+private:
+    const std::string mName;
+    std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTER_ID + 1>> mTouchingPointerIdsByDevice;
+    void ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount,
+                                     const PointerProperties* pointerProperties,
+                                     const char* action) const;
+};
+
+} // namespace android
diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h
new file mode 100644
index 0000000..13ffb58
--- /dev/null
+++ b/include/input/VirtualInputDevice.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 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 <android-base/unique_fd.h>
+
+namespace android {
+
+enum class UinputAction {
+    RELEASE = 0,
+    PRESS = 1,
+    MOVE = 2,
+    CANCEL = 3,
+};
+
+class VirtualInputDevice {
+public:
+    VirtualInputDevice(android::base::unique_fd fd);
+    virtual ~VirtualInputDevice();
+
+protected:
+    const android::base::unique_fd mFd;
+    bool writeInputEvent(uint16_t type, uint16_t code, int32_t value);
+    bool writeEvKeyEvent(int32_t androidCode, int32_t androidAction,
+                         const std::map<int, int>& evKeyCodeMapping,
+                         const std::map<int, UinputAction>& actionMapping);
+};
+
+class VirtualKeyboard : public VirtualInputDevice {
+public:
+    static const std::map<int, int> KEY_CODE_MAPPING;
+    // Expose to share with VirtualDpad.
+    static const std::map<int, UinputAction> KEY_ACTION_MAPPING;
+    VirtualKeyboard(android::base::unique_fd fd);
+    virtual ~VirtualKeyboard() override;
+    bool writeKeyEvent(int32_t androidKeyCode, int32_t androidAction);
+};
+
+class VirtualDpad : public VirtualInputDevice {
+public:
+    static const std::map<int, int> DPAD_KEY_CODE_MAPPING;
+    VirtualDpad(android::base::unique_fd fd);
+    virtual ~VirtualDpad() override;
+    bool writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction);
+};
+
+class VirtualMouse : public VirtualInputDevice {
+public:
+    VirtualMouse(android::base::unique_fd fd);
+    virtual ~VirtualMouse() override;
+    bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction);
+    // TODO(b/259554911): changing float parameters to int32_t.
+    bool writeRelativeEvent(float relativeX, float relativeY);
+    bool writeScrollEvent(float xAxisMovement, float yAxisMovement);
+
+private:
+    static const std::map<int, UinputAction> BUTTON_ACTION_MAPPING;
+    static const std::map<int, int> BUTTON_CODE_MAPPING;
+};
+
+class VirtualTouchscreen : public VirtualInputDevice {
+public:
+    VirtualTouchscreen(android::base::unique_fd fd);
+    virtual ~VirtualTouchscreen() override;
+    // TODO(b/259554911): changing float parameters to int32_t.
+    bool writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action, float locationX,
+                         float locationY, float pressure, float majorAxisSize);
+
+private:
+    static const std::map<int, UinputAction> TOUCH_ACTION_MAPPING;
+    static const std::map<int, int> TOOL_TYPE_MAPPING;
+
+    /* The set of active touch pointers on this device.
+     * We only allow pointer id to go up to MAX_POINTERS because the maximum slots of virtual
+     * touchscreen is set up with MAX_POINTERS. Note that in other cases Android allows pointer id
+     * to go up to MAX_POINTERS_ID.
+     */
+    std::bitset<MAX_POINTERS> mActivePointers{};
+    bool isValidPointerId(int32_t pointerId, UinputAction uinputAction);
+    bool handleTouchDown(int32_t pointerId);
+    bool handleTouchUp(int32_t pointerId);
+};
+} // namespace android
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index c4c8ffb..6a354b4 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -74,9 +74,6 @@
     name: "libbinder_common_defaults",
     host_supported: true,
 
-    // for vndbinder and binderRpcTest
-    vendor_available: true,
-
     srcs: [
         "Binder.cpp",
         "BpBinder.cpp",
@@ -200,7 +197,6 @@
 
 cc_library_headers {
     name: "trusty_mock_headers",
-    vendor_available: true,
     host_supported: true,
 
     export_include_dirs: [
@@ -215,7 +211,6 @@
 
 cc_defaults {
     name: "trusty_mock_defaults",
-    vendor_available: true,
     host_supported: true,
 
     header_libs: [
@@ -309,6 +304,8 @@
 
     version_script: "libbinder.map",
 
+    // for vndbinder
+    vendor_available: true,
     vndk: {
         enabled: true,
     },
@@ -467,7 +464,6 @@
 cc_library_static {
     name: "libbinder_tls_static",
     defaults: ["libbinder_tls_defaults"],
-    vendor_available: true,
     visibility: [
         ":__subpackages__",
     ],
@@ -547,8 +543,10 @@
     // Do not expand the visibility.
     visibility: [
         ":__subpackages__",
-        "//packages/modules/Virtualization:__subpackages__",
+        "//packages/modules/Virtualization/javalib/jni",
+        "//packages/modules/Virtualization/vm_payload",
         "//device/google/cuttlefish/shared/minidroid:__subpackages__",
+        "//system/software_defined_vehicle:__subpackages__",
     ],
 }
 
diff --git a/libs/binder/OWNERS b/libs/binder/OWNERS
index f954e74..bb17683 100644
--- a/libs/binder/OWNERS
+++ b/libs/binder/OWNERS
@@ -1,6 +1,4 @@
 # Bug component: 32456
-ctate@google.com
-hackbod@google.com
 maco@google.com
 smoreland@google.com
 tkjos@google.com
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index 86d5ed2..43159d8 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -22,6 +22,16 @@
 
 __BEGIN_DECLS
 
+enum AServiceManager_AddServiceFlag : uint32_t {
+    /**
+     * This allows processes with AID_ISOLATED to get the binder of the service added.
+     *
+     * Services with methods that perform file IO, web socket creation or ways to egress data must
+     * not be added with this flag for privacy concerns.
+     */
+    ADD_SERVICE_ALLOW_ISOLATED = 1,
+};
+
 /**
  * This registers the service with the default service manager under this instance name. This does
  * not take ownership of binder.
@@ -46,12 +56,13 @@
  *
  * \param binder object to register globally with the service manager.
  * \param instance identifier of the service. This will be used to lookup the service.
- * \param allowIsolated allows if this service can be isolated.
+ * \param flag an AServiceManager_AddServiceFlag enum to denote how the service should be added.
  *
  * \return EX_NONE on success.
  */
-__attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServiceWithAllowIsolated(
-        AIBinder* binder, const char* instance, bool allowIsolated) __INTRODUCED_IN(34);
+__attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServiceWithFlag(
+        AIBinder* binder, const char* instance, const AServiceManager_AddServiceFlag flag)
+        __INTRODUCED_IN(34);
 
 /**
  * Gets a binder object with this specific instance name. Will return nullptr immediately if the
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 5f2f617..1078fb2 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -158,12 +158,12 @@
     AServiceManager_getUpdatableApexName; # systemapi
     AServiceManager_registerForServiceNotifications; # systemapi llndk
     AServiceManager_NotificationRegistration_delete; # systemapi llndk
+    AServiceManager_addServiceWithFlag; # systemapi llndk
 };
 
 LIBBINDER_NDK_PLATFORM {
   global:
     AParcel_getAllowFds;
-    AServiceManager_addServiceWithAllowIsolated;
     extern "C++" {
         AIBinder_fromPlatformBinder*;
         AIBinder_toPlatformBinder*;
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index 2763ddb..84da459 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -42,14 +42,15 @@
     return PruneException(exception);
 }
 
-binder_exception_t AServiceManager_addServiceWithAllowIsolated(AIBinder* binder,
-                                                               const char* instance,
-                                                               bool allowIsolated) {
+binder_exception_t AServiceManager_addServiceWithFlag(AIBinder* binder, const char* instance,
+                                                      const AServiceManager_AddServiceFlag flag) {
     if (binder == nullptr || instance == nullptr) {
         return EX_ILLEGAL_ARGUMENT;
     }
 
     sp<IServiceManager> sm = defaultServiceManager();
+
+    bool allowIsolated = flag & AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED;
     status_t exception = sm->addService(String16(instance), binder->getBinder(), allowIsolated);
     return PruneException(exception);
 }
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index da9797b..0067a20 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -26,6 +26,7 @@
     visibility: [
         "//device/google/cuttlefish/shared/minidroid/sample",
         "//packages/modules/Virtualization:__subpackages__",
+        "//system/software_defined_vehicle:__subpackages__",
     ],
     apex_available: [
         "//apex_available:platform",
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 2a4fc48..0f0d64a 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -138,7 +138,6 @@
 
 aidl_interface {
     name: "binderRpcTestIface",
-    vendor_available: true,
     host_supported: true,
     unstable: true,
     srcs: [
@@ -159,7 +158,6 @@
 
 cc_library_static {
     name: "libbinder_tls_test_utils",
-    vendor_available: true,
     host_supported: true,
     target: {
         darwin: {
@@ -213,7 +211,6 @@
     defaults: [
         "binderRpcTest_common_defaults",
     ],
-    vendor_available: true,
     gtest: false,
     auto_gen_config: false,
     srcs: [
@@ -224,18 +221,10 @@
 
 cc_defaults {
     name: "binderRpcTest_defaults",
-    vendor_available: true,
     target: {
         android: {
             test_suites: ["vts"],
         },
-
-        vendor: {
-            shared_libs: [
-                "libbinder_trusty",
-                "libtrusty",
-            ],
-        },
     },
     defaults: [
         "binderRpcTest_common_defaults",
@@ -397,11 +386,6 @@
 
 cc_test {
     name: "binderRpcTest",
-    // TODO(b/269799024)
-    test_options: {
-        unit_test: false,
-    },
-
     defaults: [
         "binderRpcTest_defaults",
         "binderRpcTest_shared_defaults",
diff --git a/libs/binder/tests/binderRpcTestService.cpp b/libs/binder/tests/binderRpcTestService.cpp
index 714f063..a27bd2f 100644
--- a/libs/binder/tests/binderRpcTestService.cpp
+++ b/libs/binder/tests/binderRpcTestService.cpp
@@ -85,7 +85,9 @@
     }
 };
 
-int main(int argc, const char* argv[]) {
+int main(int argc, char* argv[]) {
+    android::base::InitLogging(argv, android::base::StderrLogger, android::base::DefaultAborter);
+
     LOG_ALWAYS_FATAL_IF(argc != 3, "Invalid number of arguments: %d", argc);
     base::unique_fd writeEnd(atoi(argv[1]));
     base::unique_fd readEnd(atoi(argv[2]));
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 9d82c14..cf8b13f 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -486,6 +486,17 @@
     mSyncedFrameNumbers.erase(callbackId.framenumber);
 }
 
+static ui::Size getBufferSize(const BufferItem& item) {
+    uint32_t bufWidth = item.mGraphicBuffer->getWidth();
+    uint32_t bufHeight = item.mGraphicBuffer->getHeight();
+
+    // Take the buffer's orientation into account
+    if (item.mTransform & ui::Transform::ROT_90) {
+        std::swap(bufWidth, bufHeight);
+    }
+    return ui::Size(bufWidth, bufHeight);
+}
+
 status_t BLASTBufferQueue::acquireNextBufferLocked(
         const std::optional<SurfaceComposerClient::Transaction*> transaction) {
     // Check if we have frames available and we have not acquired the maximum number of buffers.
@@ -563,7 +574,12 @@
     // Ensure BLASTBufferQueue stays alive until we receive the transaction complete callback.
     incStrong((void*)transactionCallbackThunk);
 
-    mSize = mRequestedSize;
+    // Only update mSize for destination bounds if the incoming buffer matches the requested size.
+    // Otherwise, it could cause stretching since the destination bounds will update before the
+    // buffer with the new size is acquired.
+    if (mRequestedSize == getBufferSize(bufferItem)) {
+        mSize = mRequestedSize;
+    }
     Rect crop = computeCrop(bufferItem);
     mLastBufferInfo.update(true /* hasBuffer */, bufferItem.mGraphicBuffer->getWidth(),
                            bufferItem.mGraphicBuffer->getHeight(), bufferItem.mTransform,
@@ -834,14 +850,7 @@
         return false;
     }
 
-    uint32_t bufWidth = item.mGraphicBuffer->getWidth();
-    uint32_t bufHeight = item.mGraphicBuffer->getHeight();
-
-    // Take the buffer's orientation into account
-    if (item.mTransform & ui::Transform::ROT_90) {
-        std::swap(bufWidth, bufHeight);
-    }
-    ui::Size bufferSize(bufWidth, bufHeight);
+    ui::Size bufferSize = getBufferSize(item);
     if (mRequestedSize != mSize && mRequestedSize == bufferSize) {
         return false;
     }
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 99bf6ba..46fb068 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -15,8 +15,10 @@
  */
 
 // #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <gui/Choreographer.h>
+#include <gui/TraceUtils.h>
 #include <jni.h>
 
 #undef LOG_TAG
@@ -297,6 +299,8 @@
     mLastVsyncEventData = vsyncEventData;
     for (const auto& cb : callbacks) {
         if (cb.vsyncCallback != nullptr) {
+            ATRACE_FORMAT("AChoreographer_vsyncCallback %" PRId64,
+                          vsyncEventData.preferredVsyncId());
             const ChoreographerFrameCallbackDataImpl frameCallbackData =
                     createFrameCallbackData(timestamp);
             registerStartTime();
@@ -306,8 +310,10 @@
                              cb.data);
             mInCallback = false;
         } else if (cb.callback64 != nullptr) {
+            ATRACE_FORMAT("AChoreographer_frameCallback64");
             cb.callback64(timestamp, cb.data);
         } else if (cb.callback != nullptr) {
+            ATRACE_FORMAT("AChoreographer_frameCallback");
             cb.callback(timestamp, cb.data);
         }
     }
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index fd4fc16..f38dd98 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -47,6 +47,7 @@
         "Input.cpp",
         "InputDevice.cpp",
         "InputEventLabels.cpp",
+        "InputVerifier.cpp",
         "Keyboard.cpp",
         "KeyCharacterMap.cpp",
         "KeyLayoutMap.cpp",
@@ -57,6 +58,7 @@
         "TouchVideoFrame.cpp",
         "VelocityControl.cpp",
         "VelocityTracker.cpp",
+        "VirtualInputDevice.cpp",
         "VirtualKeyMap.cpp",
     ],
 
@@ -67,6 +69,10 @@
     ],
     export_header_lib_headers: ["jni_headers"],
 
+    generated_headers: [
+        "toolbox_input_labels",
+    ],
+
     shared_libs: [
         "libbase",
         "libcutils",
@@ -124,6 +130,8 @@
                 enabled: false,
             },
             include_dirs: [
+                "bionic/libc/kernel/android/uapi/",
+                "bionic/libc/kernel/uapi",
                 "frameworks/native/libs/arect/include",
             ],
         },
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index d97c1bb..4a19227 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -16,6 +16,9 @@
 
 #include <input/InputEventLabels.h>
 
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+
 #define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }
 #define DEFINE_AXIS(axis) { #axis, AMOTION_EVENT_AXIS_##axis }
 #define DEFINE_LED(led) { #led, ALED_##led }
@@ -480,4 +483,85 @@
     return lookupValueByLabel(LEDS, label);
 }
 
+namespace {
+
+struct label {
+    const char* name;
+    int value;
+};
+
+#define LABEL(constant) \
+    { #constant, constant }
+#define LABEL_END \
+    { nullptr, -1 }
+
+static struct label ev_key_value_labels[] = {
+        {"UP", 0},
+        {"DOWN", 1},
+        {"REPEAT", 2},
+        LABEL_END,
+};
+
+#include "input.h-labels.h"
+
+#undef LABEL
+#undef LABEL_END
+
+std::string getLabel(const label* labels, int value) {
+    if (labels == nullptr) return std::to_string(value);
+    while (labels->name != nullptr && value != labels->value) {
+        labels++;
+    }
+    return labels->name != nullptr ? labels->name : std::to_string(value);
+}
+
+const label* getCodeLabelsForType(int32_t type) {
+    switch (type) {
+        case EV_SYN:
+            return syn_labels;
+        case EV_KEY:
+            return key_labels;
+        case EV_REL:
+            return rel_labels;
+        case EV_ABS:
+            return abs_labels;
+        case EV_SW:
+            return sw_labels;
+        case EV_MSC:
+            return msc_labels;
+        case EV_LED:
+            return led_labels;
+        case EV_REP:
+            return rep_labels;
+        case EV_SND:
+            return snd_labels;
+        case EV_FF:
+            return ff_labels;
+        case EV_FF_STATUS:
+            return ff_status_labels;
+        default:
+            return nullptr;
+    }
+}
+
+const label* getValueLabelsForTypeAndCode(int32_t type, int32_t code) {
+    if (type == EV_KEY) {
+        return ev_key_value_labels;
+    }
+    if (type == EV_MSC && code == ABS_MT_TOOL_TYPE) {
+        return mt_tool_labels;
+    }
+    return nullptr;
+}
+
+} // namespace
+
+EvdevEventLabel InputEventLookup::getLinuxEvdevLabel(int32_t type, int32_t code, int32_t value) {
+    return {
+            .type = getLabel(ev_labels, type),
+            .code = getLabel(getCodeLabelsForType(type), code),
+            .value = getLabel(getValueLabelsForTypeAndCode(type, code), value),
+    };
+}
+
 } // namespace android
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 9f0a314..311b244 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -5,20 +5,6 @@
 //
 #define LOG_TAG "InputTransport"
 
-//#define LOG_NDEBUG 0
-
-// Log debug messages about channel messages (send message, receive message)
-#define DEBUG_CHANNEL_MESSAGES 0
-
-// Log debug messages whenever InputChannel objects are created/destroyed
-static constexpr bool DEBUG_CHANNEL_LIFECYCLE = false;
-
-// Log debug messages about transport actions
-static constexpr bool DEBUG_TRANSPORT_ACTIONS = false;
-
-// Log debug messages about touch event resampling
-#define DEBUG_RESAMPLING 0
-
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
@@ -27,6 +13,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <binder/Parcel.h>
 #include <cutils/properties.h>
@@ -36,6 +23,63 @@
 
 #include <input/InputTransport.h>
 
+namespace {
+
+/**
+ * Log debug messages about channel messages (send message, receive message).
+ * Enable this via "adb shell setprop log.tag.InputTransportMessages DEBUG"
+ * (requires restart)
+ */
+const bool DEBUG_CHANNEL_MESSAGES =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Messages", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages whenever InputChannel objects are created/destroyed.
+ * Enable this via "adb shell setprop log.tag.InputTransportLifecycle DEBUG"
+ * (requires restart)
+ */
+const bool DEBUG_CHANNEL_LIFECYCLE =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Lifecycle", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages relating to the consumer end of the transport channel.
+ * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
+ */
+
+const bool DEBUG_TRANSPORT_CONSUMER =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
+
+const bool IS_DEBUGGABLE_BUILD =
+#if defined(__ANDROID__)
+        android::base::GetBoolProperty("ro.debuggable", false);
+#else
+        true;
+#endif
+
+/**
+ * Log debug messages relating to the producer end of the transport channel.
+ * Enable this via "adb shell setprop log.tag.InputTransportPublisher DEBUG".
+ * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
+ * on debuggable builds (e.g. userdebug).
+ */
+bool debugTransportPublisher() {
+    if (!IS_DEBUGGABLE_BUILD) {
+        static const bool DEBUG_TRANSPORT_PUBLISHER =
+                __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Publisher", ANDROID_LOG_INFO);
+        return DEBUG_TRANSPORT_PUBLISHER;
+    }
+    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Publisher", ANDROID_LOG_INFO);
+}
+
+/**
+ * Log debug messages about touch event resampling.
+ * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG" (requires restart)
+ */
+const bool DEBUG_RESAMPLING =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
+
+} // namespace
+
 using android::base::StringPrintf;
 
 namespace android {
@@ -76,6 +120,14 @@
  */
 static const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling";
 
+/**
+ * Crash if the events that are getting sent to the InputPublisher are inconsistent.
+ * Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG"
+ */
+static bool verifyEvents() {
+    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
+}
+
 template<typename T>
 inline static T min(const T& a, const T& b) {
     return a < b ? a : b;
@@ -132,7 +184,7 @@
             return valid;
         }
     }
-    ALOGE("Invalid message type: %" PRIu32, header.type);
+    ALOGE("Invalid message type: %s", ftl::enum_string(header.type).c_str());
     return false;
 }
 
@@ -322,15 +374,13 @@
 
 InputChannel::InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token)
       : mName(std::move(name)), mFd(std::move(fd)), mToken(std::move(token)) {
-    if (DEBUG_CHANNEL_LIFECYCLE) {
-        ALOGD("Input channel constructed: name='%s', fd=%d", getName().c_str(), getFd().get());
-    }
+    ALOGD_IF(DEBUG_CHANNEL_LIFECYCLE, "Input channel constructed: name='%s', fd=%d",
+             getName().c_str(), getFd().get());
 }
 
 InputChannel::~InputChannel() {
-    if (DEBUG_CHANNEL_LIFECYCLE) {
-        ALOGD("Input channel destroyed: name='%s', fd=%d", getName().c_str(), getFd().get());
-    }
+    ALOGD_IF(DEBUG_CHANNEL_LIFECYCLE, "Input channel destroyed: name='%s', fd=%d",
+             getName().c_str(), getFd().get());
 }
 
 status_t InputChannel::openInputChannelPair(const std::string& name,
@@ -375,10 +425,8 @@
 
     if (nWrite < 0) {
         int error = errno;
-#if DEBUG_CHANNEL_MESSAGES
-        ALOGD("channel '%s' ~ error sending message of type %d, %s", mName.c_str(),
-              msg->header.type, strerror(error));
-#endif
+        ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ error sending message of type %s, %s",
+                 mName.c_str(), ftl::enum_string(msg->header.type).c_str(), strerror(error));
         if (error == EAGAIN || error == EWOULDBLOCK) {
             return WOULD_BLOCK;
         }
@@ -389,16 +437,14 @@
     }
 
     if (size_t(nWrite) != msgLength) {
-#if DEBUG_CHANNEL_MESSAGES
-        ALOGD("channel '%s' ~ error sending message type %d, send was incomplete",
-                mName.c_str(), msg->header.type);
-#endif
+        ALOGD_IF(DEBUG_CHANNEL_MESSAGES,
+                 "channel '%s' ~ error sending message type %s, send was incomplete", mName.c_str(),
+                 ftl::enum_string(msg->header.type).c_str());
         return DEAD_OBJECT;
     }
 
-#if DEBUG_CHANNEL_MESSAGES
-    ALOGD("channel '%s' ~ sent message of type %d", mName.c_str(), msg->header.type);
-#endif
+    ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ sent message of type %s", mName.c_str(),
+             ftl::enum_string(msg->header.type).c_str());
     return OK;
 }
 
@@ -410,9 +456,8 @@
 
     if (nRead < 0) {
         int error = errno;
-#if DEBUG_CHANNEL_MESSAGES
-        ALOGD("channel '%s' ~ receive message failed, errno=%d", mName.c_str(), errno);
-#endif
+        ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ receive message failed, errno=%d",
+                 mName.c_str(), errno);
         if (error == EAGAIN || error == EWOULDBLOCK) {
             return WOULD_BLOCK;
         }
@@ -423,9 +468,8 @@
     }
 
     if (nRead == 0) { // check for EOF
-#if DEBUG_CHANNEL_MESSAGES
-        ALOGD("channel '%s' ~ receive message failed because peer was closed", mName.c_str());
-#endif
+        ALOGD_IF(DEBUG_CHANNEL_MESSAGES,
+                 "channel '%s' ~ receive message failed because peer was closed", mName.c_str());
         return DEAD_OBJECT;
     }
 
@@ -434,9 +478,8 @@
         return BAD_VALUE;
     }
 
-#if DEBUG_CHANNEL_MESSAGES
-    ALOGD("channel '%s' ~ received message of type %d", mName.c_str(), msg->header.type);
-#endif
+    ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", mName.c_str(),
+             ftl::enum_string(msg->header.type).c_str());
     return OK;
 }
 
@@ -492,7 +535,8 @@
 
 // --- InputPublisher ---
 
-InputPublisher::InputPublisher(const std::shared_ptr<InputChannel>& channel) : mChannel(channel) {}
+InputPublisher::InputPublisher(const std::shared_ptr<InputChannel>& channel)
+      : mChannel(channel), mInputVerifier(channel->getName()) {}
 
 InputPublisher::~InputPublisher() {
 }
@@ -504,17 +548,19 @@
                                          int32_t metaState, int32_t repeatCount, nsecs_t downTime,
                                          nsecs_t eventTime) {
     if (ATRACE_ENABLED()) {
-        std::string message = StringPrintf("publishKeyEvent(inputChannel=%s, keyCode=%" PRId32 ")",
-                mChannel->getName().c_str(), keyCode);
+        std::string message =
+                StringPrintf("publishKeyEvent(inputChannel=%s, action=%s, keyCode=%s)",
+                             mChannel->getName().c_str(), KeyEvent::actionToString(action),
+                             KeyEvent::getLabel(keyCode));
         ATRACE_NAME(message.c_str());
     }
-    if (DEBUG_TRANSPORT_ACTIONS) {
-        ALOGD("channel '%s' publisher ~ publishKeyEvent: seq=%u, deviceId=%d, source=0x%x, "
-              "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d,"
-              "downTime=%" PRId64 ", eventTime=%" PRId64,
-              mChannel->getName().c_str(), seq, deviceId, source, action, flags, keyCode, scanCode,
-              metaState, repeatCount, downTime, eventTime);
-    }
+    ALOGD_IF(debugTransportPublisher(),
+             "channel '%s' publisher ~ %s: seq=%u, id=%d, deviceId=%d, source=%s, "
+             "action=%s, flags=0x%x, keyCode=%s, scanCode=%d, metaState=0x%x, repeatCount=%d,"
+             "downTime=%" PRId64 ", eventTime=%" PRId64,
+             mChannel->getName().c_str(), __func__, seq, eventId, deviceId,
+             inputEventSourceToString(source).c_str(), KeyEvent::actionToString(action), flags,
+             KeyEvent::getLabel(keyCode), scanCode, metaState, repeatCount, downTime, eventTime);
 
     if (!seq) {
         ALOGE("Attempted to publish a key event with sequence number 0.");
@@ -550,24 +596,29 @@
         uint32_t pointerCount, const PointerProperties* pointerProperties,
         const PointerCoords* pointerCoords) {
     if (ATRACE_ENABLED()) {
-        std::string message = StringPrintf(
-                "publishMotionEvent(inputChannel=%s, action=%" PRId32 ")",
-                mChannel->getName().c_str(), action);
+        std::string message = StringPrintf("publishMotionEvent(inputChannel=%s, action=%s)",
+                                           mChannel->getName().c_str(),
+                                           MotionEvent::actionToString(action).c_str());
         ATRACE_NAME(message.c_str());
     }
-    if (DEBUG_TRANSPORT_ACTIONS) {
+    if (verifyEvents()) {
+        mInputVerifier.processMovement(deviceId, action, pointerCount, pointerProperties,
+                                       pointerCoords, flags);
+    }
+    if (debugTransportPublisher()) {
         std::string transformString;
         transform.dump(transformString, "transform", "        ");
-        ALOGD("channel '%s' publisher ~ publishMotionEvent: seq=%u, deviceId=%d, source=0x%x, "
+        ALOGD("channel '%s' publisher ~ %s: seq=%u, id=%d, deviceId=%d, source=%s, "
               "displayId=%" PRId32 ", "
-              "action=0x%x, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, "
+              "action=%s, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, "
               "metaState=0x%x, buttonState=0x%x, classification=%s,"
               "xPrecision=%f, yPrecision=%f, downTime=%" PRId64 ", eventTime=%" PRId64 ", "
               "pointerCount=%" PRIu32 " \n%s",
-              mChannel->getName().c_str(), seq, deviceId, source, displayId, action, actionButton,
-              flags, edgeFlags, metaState, buttonState,
-              motionClassificationToString(classification), xPrecision, yPrecision, downTime,
-              eventTime, pointerCount, transformString.c_str());
+              mChannel->getName().c_str(), __func__, seq, eventId, deviceId,
+              inputEventSourceToString(source).c_str(), displayId,
+              MotionEvent::actionToString(action).c_str(), actionButton, flags, edgeFlags,
+              metaState, buttonState, motionClassificationToString(classification), xPrecision,
+              yPrecision, downTime, eventTime, pointerCount, transformString.c_str());
     }
 
     if (!seq) {
@@ -629,6 +680,8 @@
                                            mChannel->getName().c_str(), toString(hasFocus));
         ATRACE_NAME(message.c_str());
     }
+    ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: seq=%u, id=%d, hasFocus=%s",
+             mChannel->getName().c_str(), __func__, seq, eventId, toString(hasFocus));
 
     InputMessage msg;
     msg.header.type = InputMessage::Type::FOCUS;
@@ -646,6 +699,9 @@
                              mChannel->getName().c_str(), toString(pointerCaptureEnabled));
         ATRACE_NAME(message.c_str());
     }
+    ALOGD_IF(debugTransportPublisher(),
+             "channel '%s' publisher ~ %s: seq=%u, id=%d, pointerCaptureEnabled=%s",
+             mChannel->getName().c_str(), __func__, seq, eventId, toString(pointerCaptureEnabled));
 
     InputMessage msg;
     msg.header.type = InputMessage::Type::CAPTURE;
@@ -663,6 +719,9 @@
                              mChannel->getName().c_str(), x, y, toString(isExiting));
         ATRACE_NAME(message.c_str());
     }
+    ALOGD_IF(debugTransportPublisher(),
+             "channel '%s' publisher ~ %s: seq=%u, id=%d, x=%f, y=%f, isExiting=%s",
+             mChannel->getName().c_str(), __func__, seq, eventId, x, y, toString(isExiting));
 
     InputMessage msg;
     msg.header.type = InputMessage::Type::DRAG;
@@ -681,6 +740,9 @@
                              mChannel->getName().c_str(), toString(isInTouchMode));
         ATRACE_NAME(message.c_str());
     }
+    ALOGD_IF(debugTransportPublisher(),
+             "channel '%s' publisher ~ %s: seq=%u, id=%d, isInTouchMode=%s",
+             mChannel->getName().c_str(), __func__, seq, eventId, toString(isInTouchMode));
 
     InputMessage msg;
     msg.header.type = InputMessage::Type::TOUCH_MODE;
@@ -691,16 +753,18 @@
 }
 
 android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveConsumerResponse() {
-    if (DEBUG_TRANSPORT_ACTIONS) {
-        ALOGD("channel '%s' publisher ~ %s", mChannel->getName().c_str(), __func__);
-    }
-
     InputMessage msg;
     status_t result = mChannel->receiveMessage(&msg);
     if (result) {
+        ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: %s",
+                 mChannel->getName().c_str(), __func__, strerror(result));
         return android::base::Error(result);
     }
     if (msg.header.type == InputMessage::Type::FINISHED) {
+        ALOGD_IF(debugTransportPublisher(),
+                 "channel '%s' publisher ~ %s: finished: seq=%u, handled=%s",
+                 mChannel->getName().c_str(), __func__, msg.header.seq,
+                 toString(msg.body.finished.handled));
         return Finished{
                 .seq = msg.header.seq,
                 .handled = msg.body.finished.handled,
@@ -709,6 +773,8 @@
     }
 
     if (msg.header.type == InputMessage::Type::TIMELINE) {
+        ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: timeline: id=%d",
+                 mChannel->getName().c_str(), __func__, msg.body.timeline.eventId);
         return Timeline{
                 .inputEventId = msg.body.timeline.eventId,
                 .graphicsTimeline = msg.body.timeline.graphicsTimeline,
@@ -738,10 +804,9 @@
 
 status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
                                 nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
-    if (DEBUG_TRANSPORT_ACTIONS) {
-        ALOGD("channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
-              mChannel->getName().c_str(), toString(consumeBatches), frameTime);
-    }
+    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+             "channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
+             mChannel->getName().c_str(), toString(consumeBatches), frameTime);
 
     *outSeq = 0;
     *outEvent = nullptr;
@@ -767,10 +832,9 @@
                 if (consumeBatches || result != WOULD_BLOCK) {
                     result = consumeBatch(factory, frameTime, outSeq, outEvent);
                     if (*outEvent) {
-                        if (DEBUG_TRANSPORT_ACTIONS) {
-                            ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
-                                  mChannel->getName().c_str(), *outSeq);
-                        }
+                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                                 "channel '%s' consumer ~ consumed batch event, seq=%u",
+                                 mChannel->getName().c_str(), *outSeq);
                         break;
                     }
                 }
@@ -786,11 +850,10 @@
                 initializeKeyEvent(keyEvent, &mMsg);
                 *outSeq = mMsg.header.seq;
                 *outEvent = keyEvent;
-                if (DEBUG_TRANSPORT_ACTIONS) {
-                    ALOGD("channel '%s' consumer ~ consumed key event, seq=%u",
-                          mChannel->getName().c_str(), *outSeq);
-                }
-            break;
+                ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                         "channel '%s' consumer ~ consumed key event, seq=%u",
+                         mChannel->getName().c_str(), *outSeq);
+                break;
             }
 
             case InputMessage::Type::MOTION: {
@@ -799,11 +862,10 @@
                     Batch& batch = mBatches[batchIndex];
                     if (canAddSample(batch, &mMsg)) {
                         batch.samples.push_back(mMsg);
-                        if (DEBUG_TRANSPORT_ACTIONS) {
-                            ALOGD("channel '%s' consumer ~ appended to batch event",
-                                  mChannel->getName().c_str());
-                        }
-                    break;
+                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                                 "channel '%s' consumer ~ appended to batch event",
+                                 mChannel->getName().c_str());
+                        break;
                     } else if (isPointerEvent(mMsg.body.motion.source) &&
                                mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) {
                         // No need to process events that we are going to cancel anyways
@@ -824,12 +886,11 @@
                         if (result) {
                             return result;
                         }
-                        if (DEBUG_TRANSPORT_ACTIONS) {
-                            ALOGD("channel '%s' consumer ~ consumed batch event and "
-                                  "deferred current event, seq=%u",
-                                  mChannel->getName().c_str(), *outSeq);
-                        }
-                    break;
+                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                                 "channel '%s' consumer ~ consumed batch event and "
+                                 "deferred current event, seq=%u",
+                                 mChannel->getName().c_str(), *outSeq);
+                        break;
                     }
                 }
 
@@ -839,10 +900,9 @@
                     Batch batch;
                     batch.samples.push_back(mMsg);
                     mBatches.push_back(batch);
-                    if (DEBUG_TRANSPORT_ACTIONS) {
-                        ALOGD("channel '%s' consumer ~ started batch event",
-                              mChannel->getName().c_str());
-                    }
+                    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                             "channel '%s' consumer ~ started batch event",
+                             mChannel->getName().c_str());
                     break;
                 }
 
@@ -854,10 +914,9 @@
                 *outSeq = mMsg.header.seq;
                 *outEvent = motionEvent;
 
-                if (DEBUG_TRANSPORT_ACTIONS) {
-                    ALOGD("channel '%s' consumer ~ consumed motion event, seq=%u",
-                          mChannel->getName().c_str(), *outSeq);
-                }
+                ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                         "channel '%s' consumer ~ consumed motion event, seq=%u",
+                         mChannel->getName().c_str(), *outSeq);
                 break;
             }
 
@@ -1074,11 +1133,9 @@
                     state.recentCoordinatesAreIdentical(id)) {
                 PointerCoords& msgCoords = msg.body.motion.pointers[i].coords;
                 const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
-#if DEBUG_RESAMPLING
-                ALOGD("[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
-                        resampleCoords.getX(), resampleCoords.getY(),
-                        msgCoords.getX(), msgCoords.getY());
-#endif
+                ALOGD_IF(DEBUG_RESAMPLING, "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
+                         resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(),
+                         msgCoords.getY());
                 msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
                 msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
                 msgCoords.isResampled = true;
@@ -1099,17 +1156,13 @@
 
     ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
     if (index < 0) {
-#if DEBUG_RESAMPLING
-        ALOGD("Not resampled, no touch state for device.");
-#endif
+        ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, no touch state for device.");
         return;
     }
 
     TouchState& touchState = mTouchStates[index];
     if (touchState.historySize < 1) {
-#if DEBUG_RESAMPLING
-        ALOGD("Not resampled, no history for device.");
-#endif
+        ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, no history for device.");
         return;
     }
 
@@ -1119,9 +1172,7 @@
     for (size_t i = 0; i < pointerCount; i++) {
         uint32_t id = event->getPointerId(i);
         if (!current->idBits.hasBit(id)) {
-#if DEBUG_RESAMPLING
-            ALOGD("Not resampled, missing id %d", id);
-#endif
+            ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, missing id %d", id);
             return;
         }
     }
@@ -1137,9 +1188,8 @@
         other = &future;
         nsecs_t delta = future.eventTime - current->eventTime;
         if (delta < RESAMPLE_MIN_DELTA) {
-#if DEBUG_RESAMPLING
-            ALOGD("Not resampled, delta time is too small: %" PRId64 " ns.", delta);
-#endif
+            ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, delta time is too small: %" PRId64 " ns.",
+                     delta);
             return;
         }
         alpha = float(sampleTime - current->eventTime) / delta;
@@ -1149,30 +1199,25 @@
         other = touchState.getHistory(1);
         nsecs_t delta = current->eventTime - other->eventTime;
         if (delta < RESAMPLE_MIN_DELTA) {
-#if DEBUG_RESAMPLING
-            ALOGD("Not resampled, delta time is too small: %" PRId64 " ns.", delta);
-#endif
+            ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, delta time is too small: %" PRId64 " ns.",
+                     delta);
             return;
         } else if (delta > RESAMPLE_MAX_DELTA) {
-#if DEBUG_RESAMPLING
-            ALOGD("Not resampled, delta time is too large: %" PRId64 " ns.", delta);
-#endif
+            ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, delta time is too large: %" PRId64 " ns.",
+                     delta);
             return;
         }
         nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION);
         if (sampleTime > maxPredict) {
-#if DEBUG_RESAMPLING
-            ALOGD("Sample time is too far in the future, adjusting prediction "
-                    "from %" PRId64 " to %" PRId64 " ns.",
-                    sampleTime - current->eventTime, maxPredict - current->eventTime);
-#endif
+            ALOGD_IF(DEBUG_RESAMPLING,
+                     "Sample time is too far in the future, adjusting prediction "
+                     "from %" PRId64 " to %" PRId64 " ns.",
+                     sampleTime - current->eventTime, maxPredict - current->eventTime);
             sampleTime = maxPredict;
         }
         alpha = float(current->eventTime - sampleTime) / delta;
     } else {
-#if DEBUG_RESAMPLING
-        ALOGD("Not resampled, insufficient data.");
-#endif
+        ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, insufficient data.");
         return;
     }
 
@@ -1207,28 +1252,22 @@
         PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
         const PointerCoords& currentCoords = current->getPointerById(id);
         resampledCoords.copyFrom(currentCoords);
-        if (other->idBits.hasBit(id)
-                && shouldResampleTool(event->getToolType(i))) {
+        if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) {
             const PointerCoords& otherCoords = other->getPointerById(id);
             resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
-                    lerp(currentCoords.getX(), otherCoords.getX(), alpha));
+                                         lerp(currentCoords.getX(), otherCoords.getX(), alpha));
             resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
-                    lerp(currentCoords.getY(), otherCoords.getY(), alpha));
+                                         lerp(currentCoords.getY(), otherCoords.getY(), alpha));
             resampledCoords.isResampled = true;
-#if DEBUG_RESAMPLING
-            ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
-                    "other (%0.3f, %0.3f), alpha %0.3f",
-                    id, resampledCoords.getX(), resampledCoords.getY(),
-                    currentCoords.getX(), currentCoords.getY(),
-                    otherCoords.getX(), otherCoords.getY(),
-                    alpha);
-#endif
+            ALOGD_IF(DEBUG_RESAMPLING,
+                     "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
+                     "other (%0.3f, %0.3f), alpha %0.3f",
+                     id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
+                     currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
         } else {
-#if DEBUG_RESAMPLING
-            ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)",
-                    id, resampledCoords.getX(), resampledCoords.getY(),
-                    currentCoords.getX(), currentCoords.getY());
-#endif
+            ALOGD_IF(DEBUG_RESAMPLING, "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id,
+                     resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
+                     currentCoords.getY());
         }
     }
 
@@ -1241,10 +1280,9 @@
 }
 
 status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
-    if (DEBUG_TRANSPORT_ACTIONS) {
-        ALOGD("channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
-              mChannel->getName().c_str(), seq, toString(handled));
-    }
+    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+             "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
+             mChannel->getName().c_str(), seq, toString(handled));
 
     if (!seq) {
         ALOGE("Attempted to send a finished signal with sequence number 0.");
@@ -1291,13 +1329,12 @@
 
 status_t InputConsumer::sendTimeline(int32_t inputEventId,
                                      std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
-    if (DEBUG_TRANSPORT_ACTIONS) {
-        ALOGD("channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32
-              ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
-              mChannel->getName().c_str(), inputEventId,
-              graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
-              graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
-    }
+    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+             "channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32
+             ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
+             mChannel->getName().c_str(), inputEventId,
+             graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
+             graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
 
     InputMessage msg;
     msg.header.type = InputMessage::Type::TIMELINE;
diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp
new file mode 100644
index 0000000..eb75804
--- /dev/null
+++ b/libs/input/InputVerifier.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#define LOG_TAG "InputVerifier"
+
+#include <android-base/logging.h>
+#include <input/InputVerifier.h>
+
+namespace android {
+
+/**
+ * Log all of the movements that are sent to this verifier. Helps to identify the streams that lead
+ * to inconsistent events.
+ * Enable this via "adb shell setprop log.tag.InputVerifierLogEvents DEBUG"
+ */
+static bool logEvents() {
+    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "LogEvents", ANDROID_LOG_INFO);
+}
+
+// --- InputVerifier ---
+
+InputVerifier::InputVerifier(const std::string& name) : mName(name){};
+
+void InputVerifier::processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount,
+                                    const PointerProperties* pointerProperties,
+                                    const PointerCoords* pointerCoords, int32_t flags) {
+    if (logEvents()) {
+        LOG(ERROR) << "Processing " << MotionEvent::actionToString(action) << " for device "
+                   << deviceId << " (" << pointerCount << " pointer"
+                   << (pointerCount == 1 ? "" : "s") << ") on " << mName;
+    }
+
+    switch (MotionEvent::getActionMasked(action)) {
+        case AMOTION_EVENT_ACTION_DOWN: {
+            auto [it, inserted] = mTouchingPointerIdsByDevice.insert({deviceId, {}});
+            if (!inserted) {
+                LOG(FATAL) << "Got ACTION_DOWN, but already have touching pointers " << it->second
+                           << " for device " << deviceId << " on " << mName;
+            }
+            it->second.set(pointerProperties[0].id);
+            break;
+        }
+        case AMOTION_EVENT_ACTION_POINTER_DOWN: {
+            auto it = mTouchingPointerIdsByDevice.find(deviceId);
+            if (it == mTouchingPointerIdsByDevice.end()) {
+                LOG(FATAL) << "Got POINTER_DOWN, but no touching pointers for device " << deviceId
+                           << " on " << mName;
+            }
+            it->second.set(pointerProperties[MotionEvent::getActionIndex(action)].id);
+            break;
+        }
+        case AMOTION_EVENT_ACTION_MOVE: {
+            ensureTouchingPointersMatch(deviceId, pointerCount, pointerProperties, "MOVE");
+            break;
+        }
+        case AMOTION_EVENT_ACTION_POINTER_UP: {
+            auto it = mTouchingPointerIdsByDevice.find(deviceId);
+            if (it == mTouchingPointerIdsByDevice.end()) {
+                LOG(FATAL) << "Got POINTER_UP, but no touching pointers for device " << deviceId
+                           << " on " << mName;
+            }
+            it->second.reset(pointerProperties[MotionEvent::getActionIndex(action)].id);
+            break;
+        }
+        case AMOTION_EVENT_ACTION_UP: {
+            auto it = mTouchingPointerIdsByDevice.find(deviceId);
+            if (it == mTouchingPointerIdsByDevice.end()) {
+                LOG(FATAL) << "Got ACTION_UP, but no record for deviceId " << deviceId << " on "
+                           << mName;
+            }
+            const auto& [_, touchingPointerIds] = *it;
+            if (touchingPointerIds.count() != 1) {
+                LOG(FATAL) << "Got ACTION_UP, but we have pointers: " << touchingPointerIds
+                           << " for deviceId " << deviceId << " on " << mName;
+            }
+            const int32_t pointerId = pointerProperties[0].id;
+            if (!touchingPointerIds.test(pointerId)) {
+                LOG(FATAL) << "Got ACTION_UP, but pointerId " << pointerId
+                           << " is not touching. Touching pointers: " << touchingPointerIds
+                           << " for deviceId " << deviceId << " on " << mName;
+            }
+            mTouchingPointerIdsByDevice.erase(it);
+            break;
+        }
+        case AMOTION_EVENT_ACTION_CANCEL: {
+            if ((flags & AMOTION_EVENT_FLAG_CANCELED) != AMOTION_EVENT_FLAG_CANCELED) {
+                LOG(FATAL) << "For ACTION_CANCEL, must set FLAG_CANCELED";
+            }
+            ensureTouchingPointersMatch(deviceId, pointerCount, pointerProperties, "CANCEL");
+            mTouchingPointerIdsByDevice.erase(deviceId);
+            break;
+        }
+    }
+}
+
+void InputVerifier::ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount,
+                                                const PointerProperties* pointerProperties,
+                                                const char* action) const {
+    auto it = mTouchingPointerIdsByDevice.find(deviceId);
+    if (it == mTouchingPointerIdsByDevice.end()) {
+        LOG(FATAL) << "Got " << action << ", but no touching pointers for device " << deviceId
+                   << " on " << mName;
+    }
+    const auto& [_, touchingPointerIds] = *it;
+    for (size_t i = 0; i < pointerCount; i++) {
+        const int32_t pointerId = pointerProperties[i].id;
+        if (!touchingPointerIds.test(pointerId)) {
+            LOG(FATAL) << "Got " << action << " for pointerId " << pointerId
+                       << " but the touching pointers are " << touchingPointerIds << " on "
+                       << mName;
+        }
+    }
+};
+
+} // namespace android
diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
new file mode 100644
index 0000000..3c1f2b6
--- /dev/null
+++ b/libs/input/VirtualInputDevice.cpp
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#define LOG_TAG "VirtualInputDevice"
+
+#include <android/input.h>
+#include <android/keycodes.h>
+#include <fcntl.h>
+#include <input/Input.h>
+#include <input/VirtualInputDevice.h>
+#include <linux/uinput.h>
+#include <math.h>
+#include <utils/Log.h>
+
+#include <map>
+#include <string>
+
+using android::base::unique_fd;
+
+/**
+ * Log debug messages about native virtual input devices.
+ * Enable this via "adb shell setprop log.tag.VirtualInputDevice DEBUG"
+ */
+static bool isDebug() {
+    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
+}
+
+namespace android {
+VirtualInputDevice::VirtualInputDevice(unique_fd fd) : mFd(std::move(fd)) {}
+VirtualInputDevice::~VirtualInputDevice() {
+    ioctl(mFd, UI_DEV_DESTROY);
+}
+
+bool VirtualInputDevice::writeInputEvent(uint16_t type, uint16_t code, int32_t value) {
+    struct input_event ev = {.type = type, .code = code, .value = value};
+    return TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(struct input_event))) == sizeof(ev);
+}
+
+/** Utility method to write keyboard key events or mouse button events. */
+bool VirtualInputDevice::writeEvKeyEvent(int32_t androidCode, int32_t androidAction,
+                                         const std::map<int, int>& evKeyCodeMapping,
+                                         const std::map<int, UinputAction>& actionMapping) {
+    auto evKeyCodeIterator = evKeyCodeMapping.find(androidCode);
+    if (evKeyCodeIterator == evKeyCodeMapping.end()) {
+        ALOGE("Unsupported native EV keycode for android code %d", androidCode);
+        return false;
+    }
+    auto actionIterator = actionMapping.find(androidAction);
+    if (actionIterator == actionMapping.end()) {
+        return false;
+    }
+    if (!writeInputEvent(EV_KEY, static_cast<uint16_t>(evKeyCodeIterator->second),
+                         static_cast<int32_t>(actionIterator->second))) {
+        return false;
+    }
+    if (!writeInputEvent(EV_SYN, SYN_REPORT, 0)) {
+        return false;
+    }
+    return true;
+}
+
+// --- VirtualKeyboard ---
+const std::map<int, UinputAction> VirtualKeyboard::KEY_ACTION_MAPPING = {
+        {AKEY_EVENT_ACTION_DOWN, UinputAction::PRESS},
+        {AKEY_EVENT_ACTION_UP, UinputAction::RELEASE},
+};
+// Keycode mapping from https://source.android.com/devices/input/keyboard-devices
+const std::map<int, int> VirtualKeyboard::KEY_CODE_MAPPING = {
+        {AKEYCODE_0, KEY_0},
+        {AKEYCODE_1, KEY_1},
+        {AKEYCODE_2, KEY_2},
+        {AKEYCODE_3, KEY_3},
+        {AKEYCODE_4, KEY_4},
+        {AKEYCODE_5, KEY_5},
+        {AKEYCODE_6, KEY_6},
+        {AKEYCODE_7, KEY_7},
+        {AKEYCODE_8, KEY_8},
+        {AKEYCODE_9, KEY_9},
+        {AKEYCODE_A, KEY_A},
+        {AKEYCODE_B, KEY_B},
+        {AKEYCODE_C, KEY_C},
+        {AKEYCODE_D, KEY_D},
+        {AKEYCODE_E, KEY_E},
+        {AKEYCODE_F, KEY_F},
+        {AKEYCODE_G, KEY_G},
+        {AKEYCODE_H, KEY_H},
+        {AKEYCODE_I, KEY_I},
+        {AKEYCODE_J, KEY_J},
+        {AKEYCODE_K, KEY_K},
+        {AKEYCODE_L, KEY_L},
+        {AKEYCODE_M, KEY_M},
+        {AKEYCODE_N, KEY_N},
+        {AKEYCODE_O, KEY_O},
+        {AKEYCODE_P, KEY_P},
+        {AKEYCODE_Q, KEY_Q},
+        {AKEYCODE_R, KEY_R},
+        {AKEYCODE_S, KEY_S},
+        {AKEYCODE_T, KEY_T},
+        {AKEYCODE_U, KEY_U},
+        {AKEYCODE_V, KEY_V},
+        {AKEYCODE_W, KEY_W},
+        {AKEYCODE_X, KEY_X},
+        {AKEYCODE_Y, KEY_Y},
+        {AKEYCODE_Z, KEY_Z},
+        {AKEYCODE_GRAVE, KEY_GRAVE},
+        {AKEYCODE_MINUS, KEY_MINUS},
+        {AKEYCODE_EQUALS, KEY_EQUAL},
+        {AKEYCODE_LEFT_BRACKET, KEY_LEFTBRACE},
+        {AKEYCODE_RIGHT_BRACKET, KEY_RIGHTBRACE},
+        {AKEYCODE_BACKSLASH, KEY_BACKSLASH},
+        {AKEYCODE_SEMICOLON, KEY_SEMICOLON},
+        {AKEYCODE_APOSTROPHE, KEY_APOSTROPHE},
+        {AKEYCODE_COMMA, KEY_COMMA},
+        {AKEYCODE_PERIOD, KEY_DOT},
+        {AKEYCODE_SLASH, KEY_SLASH},
+        {AKEYCODE_ALT_LEFT, KEY_LEFTALT},
+        {AKEYCODE_ALT_RIGHT, KEY_RIGHTALT},
+        {AKEYCODE_CTRL_LEFT, KEY_LEFTCTRL},
+        {AKEYCODE_CTRL_RIGHT, KEY_RIGHTCTRL},
+        {AKEYCODE_SHIFT_LEFT, KEY_LEFTSHIFT},
+        {AKEYCODE_SHIFT_RIGHT, KEY_RIGHTSHIFT},
+        {AKEYCODE_META_LEFT, KEY_LEFTMETA},
+        {AKEYCODE_META_RIGHT, KEY_RIGHTMETA},
+        {AKEYCODE_CAPS_LOCK, KEY_CAPSLOCK},
+        {AKEYCODE_SCROLL_LOCK, KEY_SCROLLLOCK},
+        {AKEYCODE_NUM_LOCK, KEY_NUMLOCK},
+        {AKEYCODE_ENTER, KEY_ENTER},
+        {AKEYCODE_TAB, KEY_TAB},
+        {AKEYCODE_SPACE, KEY_SPACE},
+        {AKEYCODE_DPAD_DOWN, KEY_DOWN},
+        {AKEYCODE_DPAD_UP, KEY_UP},
+        {AKEYCODE_DPAD_LEFT, KEY_LEFT},
+        {AKEYCODE_DPAD_RIGHT, KEY_RIGHT},
+        {AKEYCODE_MOVE_END, KEY_END},
+        {AKEYCODE_MOVE_HOME, KEY_HOME},
+        {AKEYCODE_PAGE_DOWN, KEY_PAGEDOWN},
+        {AKEYCODE_PAGE_UP, KEY_PAGEUP},
+        {AKEYCODE_DEL, KEY_BACKSPACE},
+        {AKEYCODE_FORWARD_DEL, KEY_DELETE},
+        {AKEYCODE_INSERT, KEY_INSERT},
+        {AKEYCODE_ESCAPE, KEY_ESC},
+        {AKEYCODE_BREAK, KEY_PAUSE},
+        {AKEYCODE_F1, KEY_F1},
+        {AKEYCODE_F2, KEY_F2},
+        {AKEYCODE_F3, KEY_F3},
+        {AKEYCODE_F4, KEY_F4},
+        {AKEYCODE_F5, KEY_F5},
+        {AKEYCODE_F6, KEY_F6},
+        {AKEYCODE_F7, KEY_F7},
+        {AKEYCODE_F8, KEY_F8},
+        {AKEYCODE_F9, KEY_F9},
+        {AKEYCODE_F10, KEY_F10},
+        {AKEYCODE_F11, KEY_F11},
+        {AKEYCODE_F12, KEY_F12},
+        {AKEYCODE_BACK, KEY_BACK},
+        {AKEYCODE_FORWARD, KEY_FORWARD},
+        {AKEYCODE_NUMPAD_1, KEY_KP1},
+        {AKEYCODE_NUMPAD_2, KEY_KP2},
+        {AKEYCODE_NUMPAD_3, KEY_KP3},
+        {AKEYCODE_NUMPAD_4, KEY_KP4},
+        {AKEYCODE_NUMPAD_5, KEY_KP5},
+        {AKEYCODE_NUMPAD_6, KEY_KP6},
+        {AKEYCODE_NUMPAD_7, KEY_KP7},
+        {AKEYCODE_NUMPAD_8, KEY_KP8},
+        {AKEYCODE_NUMPAD_9, KEY_KP9},
+        {AKEYCODE_NUMPAD_0, KEY_KP0},
+        {AKEYCODE_NUMPAD_ADD, KEY_KPPLUS},
+        {AKEYCODE_NUMPAD_SUBTRACT, KEY_KPMINUS},
+        {AKEYCODE_NUMPAD_MULTIPLY, KEY_KPASTERISK},
+        {AKEYCODE_NUMPAD_DIVIDE, KEY_KPSLASH},
+        {AKEYCODE_NUMPAD_DOT, KEY_KPDOT},
+        {AKEYCODE_NUMPAD_ENTER, KEY_KPENTER},
+        {AKEYCODE_NUMPAD_EQUALS, KEY_KPEQUAL},
+        {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA},
+};
+VirtualKeyboard::VirtualKeyboard(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
+VirtualKeyboard::~VirtualKeyboard() {}
+
+bool VirtualKeyboard::writeKeyEvent(int32_t androidKeyCode, int32_t androidAction) {
+    return writeEvKeyEvent(androidKeyCode, androidAction, KEY_CODE_MAPPING, KEY_ACTION_MAPPING);
+}
+
+// --- VirtualDpad ---
+// Dpad keycode mapping from https://source.android.com/devices/input/keyboard-devices
+const std::map<int, int> VirtualDpad::DPAD_KEY_CODE_MAPPING = {
+        // clang-format off
+        {AKEYCODE_DPAD_DOWN, KEY_DOWN},
+        {AKEYCODE_DPAD_UP, KEY_UP},
+        {AKEYCODE_DPAD_LEFT, KEY_LEFT},
+        {AKEYCODE_DPAD_RIGHT, KEY_RIGHT},
+        {AKEYCODE_DPAD_CENTER, KEY_SELECT},
+        {AKEYCODE_BACK, KEY_BACK},
+        // clang-format on
+};
+
+VirtualDpad::VirtualDpad(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
+
+VirtualDpad::~VirtualDpad() {}
+
+bool VirtualDpad::writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction) {
+    return writeEvKeyEvent(androidKeyCode, androidAction, DPAD_KEY_CODE_MAPPING,
+                           VirtualKeyboard::KEY_ACTION_MAPPING);
+}
+
+// --- VirtualMouse ---
+const std::map<int, UinputAction> VirtualMouse::BUTTON_ACTION_MAPPING = {
+        {AMOTION_EVENT_ACTION_BUTTON_PRESS, UinputAction::PRESS},
+        {AMOTION_EVENT_ACTION_BUTTON_RELEASE, UinputAction::RELEASE},
+};
+
+// Button code mapping from https://source.android.com/devices/input/touch-devices
+const std::map<int, int> VirtualMouse::BUTTON_CODE_MAPPING = {
+        // clang-format off
+        {AMOTION_EVENT_BUTTON_PRIMARY, BTN_LEFT},
+        {AMOTION_EVENT_BUTTON_SECONDARY, BTN_RIGHT},
+        {AMOTION_EVENT_BUTTON_TERTIARY, BTN_MIDDLE},
+        {AMOTION_EVENT_BUTTON_BACK, BTN_BACK},
+        {AMOTION_EVENT_BUTTON_FORWARD, BTN_FORWARD},
+        // clang-format on
+};
+
+VirtualMouse::VirtualMouse(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
+
+VirtualMouse::~VirtualMouse() {}
+
+bool VirtualMouse::writeButtonEvent(int32_t androidButtonCode, int32_t androidAction) {
+    return writeEvKeyEvent(androidButtonCode, androidAction, BUTTON_CODE_MAPPING,
+                           BUTTON_ACTION_MAPPING);
+}
+
+bool VirtualMouse::writeRelativeEvent(float relativeX, float relativeY) {
+    return writeInputEvent(EV_REL, REL_X, relativeX) && writeInputEvent(EV_REL, REL_Y, relativeY) &&
+            writeInputEvent(EV_SYN, SYN_REPORT, 0);
+}
+
+bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement) {
+    return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement) &&
+            writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement) &&
+            writeInputEvent(EV_SYN, SYN_REPORT, 0);
+}
+
+// --- VirtualTouchscreen ---
+const std::map<int, UinputAction> VirtualTouchscreen::TOUCH_ACTION_MAPPING = {
+        {AMOTION_EVENT_ACTION_DOWN, UinputAction::PRESS},
+        {AMOTION_EVENT_ACTION_UP, UinputAction::RELEASE},
+        {AMOTION_EVENT_ACTION_MOVE, UinputAction::MOVE},
+        {AMOTION_EVENT_ACTION_CANCEL, UinputAction::CANCEL},
+};
+// Tool type mapping from https://source.android.com/devices/input/touch-devices
+const std::map<int, int> VirtualTouchscreen::TOOL_TYPE_MAPPING = {
+        {AMOTION_EVENT_TOOL_TYPE_FINGER, MT_TOOL_FINGER},
+        {AMOTION_EVENT_TOOL_TYPE_PALM, MT_TOOL_PALM},
+};
+
+VirtualTouchscreen::VirtualTouchscreen(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
+
+VirtualTouchscreen::~VirtualTouchscreen() {}
+
+bool VirtualTouchscreen::isValidPointerId(int32_t pointerId, UinputAction uinputAction) {
+    if (pointerId < -1 || pointerId >= (int)MAX_POINTERS) {
+        ALOGE("Virtual touch event has invalid pointer id %d; value must be between -1 and %zu",
+              pointerId, MAX_POINTERS - 0);
+        return false;
+    }
+
+    if (uinputAction == UinputAction::PRESS && mActivePointers.test(pointerId)) {
+        ALOGE("Repetitive action DOWN event received on a pointer %d that is already down.",
+              pointerId);
+        return false;
+    }
+    if (uinputAction == UinputAction::RELEASE && !mActivePointers.test(pointerId)) {
+        ALOGE("PointerId %d action UP received with no prior action DOWN on touchscreen %d.",
+              pointerId, mFd.get());
+        return false;
+    }
+    return true;
+}
+
+bool VirtualTouchscreen::writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action,
+                                         float locationX, float locationY, float pressure,
+                                         float majorAxisSize) {
+    auto actionIterator = TOUCH_ACTION_MAPPING.find(action);
+    if (actionIterator == TOUCH_ACTION_MAPPING.end()) {
+        return false;
+    }
+    UinputAction uinputAction = actionIterator->second;
+    if (!isValidPointerId(pointerId, uinputAction)) {
+        return false;
+    }
+    if (!writeInputEvent(EV_ABS, ABS_MT_SLOT, pointerId)) {
+        return false;
+    }
+    auto toolTypeIterator = TOOL_TYPE_MAPPING.find(toolType);
+    if (toolTypeIterator == TOOL_TYPE_MAPPING.end()) {
+        return false;
+    }
+    if (!writeInputEvent(EV_ABS, ABS_MT_TOOL_TYPE,
+                         static_cast<int32_t>(toolTypeIterator->second))) {
+        return false;
+    }
+    if (uinputAction == UinputAction::PRESS && !handleTouchDown(pointerId)) {
+        return false;
+    }
+    if (uinputAction == UinputAction::RELEASE && !handleTouchUp(pointerId)) {
+        return false;
+    }
+    if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_X, locationX)) {
+        return false;
+    }
+    if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_Y, locationY)) {
+        return false;
+    }
+    if (!isnan(pressure)) {
+        if (!writeInputEvent(EV_ABS, ABS_MT_PRESSURE, pressure)) {
+            return false;
+        }
+    }
+    if (!isnan(majorAxisSize)) {
+        if (!writeInputEvent(EV_ABS, ABS_MT_TOUCH_MAJOR, majorAxisSize)) {
+            return false;
+        }
+    }
+    return writeInputEvent(EV_SYN, SYN_REPORT, 0);
+}
+
+bool VirtualTouchscreen::handleTouchUp(int32_t pointerId) {
+    if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(-1))) {
+        return false;
+    }
+    // When a pointer is no longer in touch, remove the pointer id from the corresponding
+    // entry in the unreleased touches map.
+    mActivePointers.reset(pointerId);
+    ALOGD_IF(isDebug(), "Pointer %d erased from the touchscreen %d", pointerId, mFd.get());
+
+    // Only sends the BTN UP event when there's no pointers on the touchscreen.
+    if (mActivePointers.none()) {
+        if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::RELEASE))) {
+            return false;
+        }
+        ALOGD_IF(isDebug(), "No pointers on touchscreen %d, BTN UP event sent.", mFd.get());
+    }
+    return true;
+}
+
+bool VirtualTouchscreen::handleTouchDown(int32_t pointerId) {
+    // When a new pointer is down on the touchscreen, add the pointer id in the corresponding
+    // entry in the unreleased touches map.
+    if (mActivePointers.none()) {
+        // Only sends the BTN Down event when the first pointer on the touchscreen is down.
+        if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::PRESS))) {
+            return false;
+        }
+        ALOGD_IF(isDebug(), "First pointer %d down under touchscreen %d, BTN DOWN event sent",
+                 pointerId, mFd.get());
+    }
+
+    mActivePointers.set(pointerId);
+    ALOGD_IF(isDebug(), "Added pointer %d under touchscreen %d in the map", pointerId, mFd.get());
+    if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(pointerId))) {
+        return false;
+    }
+    return true;
+}
+
+} // namespace android
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index 78d1912..0c03ede 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -32,6 +32,7 @@
         "jpegr.cpp",
         "recoverymapmath.cpp",
         "jpegrutils.cpp",
+        "multipictureformat.cpp",
     ],
 
     shared_libs: [
@@ -40,6 +41,7 @@
         "libjpegencoder",
         "libjpegdecoder",
         "liblog",
+        "libutils",
     ],
 
     static_libs: ["libskia"],
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
index 581806c..4145853 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
@@ -18,6 +18,7 @@
 #define ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H
 
 #include <jpegrecoverymap/jpegr.h>
+#include <utils/RefBase.h>
 
 #include <sstream>
 #include <stdint.h>
@@ -27,6 +28,26 @@
 namespace android::jpegrecoverymap {
 
 struct jpegr_metadata;
+/*
+ * Mutable data structure. Holds information for metadata.
+ */
+class DataStruct : public RefBase {
+private:
+    void* data;
+    int writePos;
+    int length;
+    ~DataStruct();
+
+public:
+    DataStruct(int s);
+    void* getData();
+    int getLength();
+    int getBytesWritten();
+    bool write8(uint8_t value);
+    bool write16(uint16_t value);
+    bool write32(uint32_t value);
+    bool write(const void* src, int size);
+};
 
 /*
  * Helper function used for writing data to destination.
@@ -51,12 +72,10 @@
 bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata);
 
 /*
- * This method generates XMP metadata.
+ * This method generates XMP metadata for the primary image.
  *
  * below is an example of the XMP metadata that this function generates where
  * secondary_image_length = 1000
- * max_content_boost = 8.0
- * min_content_boost = 0.5
  *
  * <x:xmpmeta
  *   xmlns:x="adobe:ns:meta/"
@@ -65,8 +84,7 @@
  *     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  *     <rdf:Description
  *       xmlns:Container="http://ns.google.com/photos/1.0/container/"
- *       xmlns:Item="http://ns.google.com/photos/1.0/container/item/"
- *       xmlns:RecoveryMap="http://ns.google.com/photos/1.0/recoverymap/">
+ *       xmlns:Item="http://ns.google.com/photos/1.0/container/item/">
  *       <Container:Directory>
  *         <rdf:Seq>
  *           <rdf:li>
@@ -78,10 +96,7 @@
  *             <Container:Item
  *               Item:Semantic="RecoveryMap"
  *               Item:Mime="image/jpeg"
- *               Item:Length="1000"
- *               RecoveryMap:Version="1"
- *               RecoveryMap:MaxContentBoost="8.0"
- *               RecoveryMap:MinContentBoost="0.5"/>
+ *               Item:Length="1000"/>
  *           </rdf:li>
  *         </rdf:Seq>
  *       </Container:Directory>
@@ -90,10 +105,40 @@
  * </x:xmpmeta>
  *
  * @param secondary_image_length length of secondary image
+ * @return XMP metadata in type of string
+ */
+std::string generateXmpForPrimaryImage(int secondary_image_length);
+
+/*
+ * This method generates XMP metadata for the recovery map image.
+ *
+ * below is an example of the XMP metadata that this function generates where
+ * max_content_boost = 8.0
+ * min_content_boost = 0.5
+ *
+ * <x:xmpmeta
+ *   xmlns:x="adobe:ns:meta/"
+ *   x:xmptk="Adobe XMP Core 5.1.2">
+ *   <rdf:RDF
+ *     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ *     <rdf:Description
+ *       xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
+ *       hdrgm:Version="1"
+ *       hdrgm:GainMapMin="0.5"
+ *       hdrgm:GainMapMax="8.5"
+ *       hdrgm:Gamma="1"
+ *       hdrgm:OffsetSDR="0"
+ *       hdrgm:OffsetHDR="0"
+ *       hdrgm:HDRCapacityMin="0.5"
+ *       hdrgm:HDRCapacityMax="8.5"
+ *       hdrgm:BaseRendition="SDR"/>
+ *   </rdf:RDF>
+ * </x:xmpmeta>
+ *
  * @param metadata JPEG/R metadata to encode as XMP
  * @return XMP metadata in type of string
  */
-std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata);
+ std::string generateXmpForSecondaryImage(jpegr_metadata& metadata);
 }  // namespace android::jpegrecoverymap
 
 #endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h b/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h
new file mode 100644
index 0000000..7dca916
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H
+#define ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H
+
+#include <jpegrecoverymap/jpegrutils.h>
+
+namespace android::jpegrecoverymap {
+static constexpr uint32_t EndianSwap32(uint32_t value) {
+    return ((value & 0xFF) << 24) |
+           ((value & 0xFF00) << 8) |
+           ((value & 0xFF0000) >> 8) |
+           (value >> 24);
+}
+static inline uint16_t EndianSwap16(uint16_t value) {
+    return static_cast<uint16_t>((value >> 8) | ((value & 0xFF) << 8));
+}
+#define USE_BIG_ENDIAN true
+#if USE_BIG_ENDIAN
+    #define Endian_SwapBE32(n) EndianSwap32(n)
+    #define Endian_SwapBE16(n) EndianSwap16(n)
+#else
+    #define Endian_SwapBE32(n) (n)
+    #define Endian_SwapBE16(n) (n)
+#endif
+
+constexpr size_t kNumPictures = 2;
+constexpr size_t kMpEndianSize = 4;
+constexpr uint16_t kTagSerializedCount = 3;
+constexpr uint32_t kTagSize = 12;
+
+constexpr uint16_t kTypeLong = 0x4;
+constexpr uint16_t kTypeUndefined = 0x7;
+
+static constexpr uint8_t kMpfSig[] = {'M', 'P', 'F', '\0'};
+constexpr uint8_t kMpLittleEndian[kMpEndianSize] = {0x49, 0x49, 0x2A, 0x00};
+constexpr uint8_t kMpBigEndian[kMpEndianSize] = {0x4D, 0x4D, 0x00, 0x2A};
+
+constexpr uint16_t kVersionTag = 0xB000;
+constexpr uint16_t kVersionType = kTypeUndefined;
+constexpr uint32_t kVersionCount = 4;
+constexpr size_t kVersionSize = 4;
+constexpr uint8_t kVersionExpected[kVersionSize] = {'0', '1', '0', '0'};
+
+constexpr uint16_t kNumberOfImagesTag = 0xB001;
+constexpr uint16_t kNumberOfImagesType = kTypeLong;
+constexpr uint32_t kNumberOfImagesCount = 1;
+
+constexpr uint16_t kMPEntryTag = 0xB002;
+constexpr uint16_t kMPEntryType = kTypeUndefined;
+constexpr uint32_t kMPEntrySize = 16;
+
+constexpr uint32_t kMPEntryAttributeFormatJpeg = 0x0000000;
+constexpr uint32_t kMPEntryAttributeTypePrimary = 0x030000;
+
+size_t calculateMpfSize();
+sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset,
+                           int secondary_image_size, int secondary_image_offset);
+
+}  // namespace android::jpegrecoverymap
+
+#endif //ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H
diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp
index 828af2d..c22020a 100644
--- a/libs/jpegrecoverymap/jpegr.cpp
+++ b/libs/jpegrecoverymap/jpegr.cpp
@@ -19,6 +19,7 @@
 #include <jpegrecoverymap/jpegdecoderhelper.h>
 #include <jpegrecoverymap/recoverymapmath.h>
 #include <jpegrecoverymap/jpegrutils.h>
+#include <jpegrecoverymap/multipictureformat.h>
 
 #include <image_io/jpeg/jpeg_marker.h>
 #include <image_io/jpeg/jpeg_info.h>
@@ -105,10 +106,10 @@
 
 /* Encode API-0 */
 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                                  jpegr_transfer_function hdr_tf,
-                                  jr_compressed_ptr dest,
-                                  int quality,
-                                  jr_exif_ptr exif) {
+                            jpegr_transfer_function hdr_tf,
+                            jr_compressed_ptr dest,
+                            int quality,
+                            jr_exif_ptr exif) {
   if (uncompressed_p010_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
@@ -167,11 +168,11 @@
 
 /* Encode API-1 */
 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                                  jr_uncompressed_ptr uncompressed_yuv_420_image,
-                                  jpegr_transfer_function hdr_tf,
-                                  jr_compressed_ptr dest,
-                                  int quality,
-                                  jr_exif_ptr exif) {
+                            jr_uncompressed_ptr uncompressed_yuv_420_image,
+                            jpegr_transfer_function hdr_tf,
+                            jr_compressed_ptr dest,
+                            int quality,
+                            jr_exif_ptr exif) {
   if (uncompressed_p010_image == nullptr
    || uncompressed_yuv_420_image == nullptr
    || dest == nullptr) {
@@ -231,10 +232,10 @@
 
 /* Encode API-2 */
 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                                  jr_uncompressed_ptr uncompressed_yuv_420_image,
-                                  jr_compressed_ptr compressed_jpeg_image,
-                                  jpegr_transfer_function hdr_tf,
-                                  jr_compressed_ptr dest) {
+                            jr_uncompressed_ptr uncompressed_yuv_420_image,
+                            jr_compressed_ptr compressed_jpeg_image,
+                            jpegr_transfer_function hdr_tf,
+                            jr_compressed_ptr dest) {
   if (uncompressed_p010_image == nullptr
    || uncompressed_yuv_420_image == nullptr
    || compressed_jpeg_image == nullptr
@@ -276,9 +277,9 @@
 
 /* Encode API-3 */
 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
-                                  jr_compressed_ptr compressed_jpeg_image,
-                                  jpegr_transfer_function hdr_tf,
-                                  jr_compressed_ptr dest) {
+                            jr_compressed_ptr compressed_jpeg_image,
+                            jpegr_transfer_function hdr_tf,
+                            jr_compressed_ptr dest) {
   if (uncompressed_p010_image == nullptr
    || compressed_jpeg_image == nullptr
    || dest == nullptr) {
@@ -327,8 +328,7 @@
   return NO_ERROR;
 }
 
-status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
-                                   jr_info_ptr jpegr_info) {
+status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) {
   if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
@@ -349,9 +349,9 @@
 
 /* Decode API */
 status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
-                                  jr_uncompressed_ptr dest,
-                                  jr_exif_ptr exif,
-                                  bool request_sdr) {
+                            jr_uncompressed_ptr dest,
+                            jr_exif_ptr exif,
+                            bool request_sdr) {
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
@@ -399,8 +399,8 @@
   uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
   uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
 
-  if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
-                          jpeg_decoder.getXMPSize(), &metadata)) {
+  if (!getMetadataFromXMP(static_cast<uint8_t*>(recovery_map_decoder.getXMPPtr()),
+                          recovery_map_decoder.getXMPSize(), &metadata)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
@@ -409,7 +409,7 @@
 }
 
 status_t JpegR::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
-                                          jr_compressed_ptr dest) {
+                                    jr_compressed_ptr dest) {
   if (uncompressed_recovery_map == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
@@ -493,10 +493,10 @@
 }
 
 status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
-                                          jr_uncompressed_ptr uncompressed_p010_image,
-                                          jpegr_transfer_function hdr_tf,
-                                          jr_metadata_ptr metadata,
-                                          jr_uncompressed_ptr dest) {
+                                    jr_uncompressed_ptr uncompressed_p010_image,
+                                    jpegr_transfer_function hdr_tf,
+                                    jr_metadata_ptr metadata,
+                                    jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_p010_image == nullptr
    || metadata == nullptr
@@ -637,9 +637,9 @@
 }
 
 status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
-                                       jr_uncompressed_ptr uncompressed_recovery_map,
-                                       jr_metadata_ptr metadata,
-                                       jr_uncompressed_ptr dest) {
+                                 jr_uncompressed_ptr uncompressed_recovery_map,
+                                 jr_metadata_ptr metadata,
+                                 jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_recovery_map == nullptr
    || metadata == nullptr
@@ -721,8 +721,8 @@
 }
 
 status_t JpegR::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                                        jr_compressed_ptr primary_image,
-                                                        jr_compressed_ptr recovery_map) {
+                                                  jr_compressed_ptr primary_image,
+                                                  jr_compressed_ptr recovery_map) {
   if (compressed_jpegr_image == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
@@ -771,7 +771,7 @@
 
 
 status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
-                                         jr_compressed_ptr dest) {
+                                   jr_compressed_ptr dest) {
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
@@ -790,11 +790,22 @@
 // (Required, XMP package) APP1 (ff e1)
 // 2 bytes of length (2 + 29 + length of xmp package)
 // name space ("http://ns.adobe.com/xap/1.0/\0")
-// xmp
+// XMP
+//
+// (Required, MPF package) APP2 (ff e2)
+// 2 bytes of length
+// MPF
 //
 // (Required) primary image (without the first two bytes (SOI), may have other packages)
 //
-// (Required) secondary image (the recovery map)
+// SOI (ff d8)
+//
+// (Required, XMP package) APP1 (ff e1)
+// 2 bytes of length (2 + 29 + length of xmp package)
+// name space ("http://ns.adobe.com/xap/1.0/\0")
+// XMP
+//
+// (Required) secondary image (the recovery map, without the first two bytes (SOI))
 //
 // Metadata versions we are using:
 // ECMA TR-98 for JFIF marker
@@ -802,10 +813,10 @@
 // Adobe XMP spec part 3 for XMP marker
 // ICC v4.3 spec for ICC
 status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
-                                        jr_compressed_ptr compressed_recovery_map,
-                                        jr_exif_ptr exif,
-                                        jr_metadata_ptr metadata,
-                                        jr_compressed_ptr dest) {
+                                  jr_compressed_ptr compressed_recovery_map,
+                                  jr_exif_ptr exif,
+                                  jr_metadata_ptr metadata,
+                                  jr_compressed_ptr dest) {
   if (compressed_jpeg_image == nullptr
    || compressed_recovery_map == nullptr
    || metadata == nullptr
@@ -813,8 +824,25 @@
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  int pos = 0;
+  const string nameSpace = "http://ns.adobe.com/xap/1.0/";
+  const int nameSpaceLength = nameSpace.size() + 1;  // need to count the null terminator
 
+  // calculate secondary image length first, because the length will be written into the primary
+  // image xmp
+  const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
+  const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
+                                 + nameSpaceLength /* 29 bytes length of name space including \0 */
+                                 + xmp_secondary.size(); /* length of xmp packet */
+  const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
+                                 + xmp_secondary_length
+                                 + compressed_recovery_map->length;
+  // primary image
+  const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size);
+  // same as primary
+  const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
+
+  int pos = 0;
+  // Begin primary image
   // Write SOI
   JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
   JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
@@ -833,13 +861,7 @@
 
   // Prepare and write XMP
   {
-    const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
-    const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
-    const int nameSpaceLength = nameSpace.size() + 1;  // need to count the null terminator
-    // 2 bytes: representing the length of the package
-    // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
-    // x bytes: length of xmp packet
-    const int length = 2 + nameSpaceLength + xmp.size();
+    const int length = xmp_primary_length;
     const uint8_t lengthH = ((length >> 8) & 0xff);
     const uint8_t lengthL = (length & 0xff);
     JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
@@ -847,15 +869,57 @@
     JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
     JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
     JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
-    JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
+    JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
+  }
+
+  // Prepare and write MPF
+  {
+      const int length = 2 + calculateMpfSize();
+      const uint8_t lengthH = ((length >> 8) & 0xff);
+      const uint8_t lengthL = (length & 0xff);
+      int primary_image_size = pos + length + compressed_jpeg_image->length;
+      // between APP2 + package size + signature
+      // ff e2 00 58 4d 50 46 00
+      // 2 + 2 + 4 = 8 (bytes)
+      // and ff d8 sign of the secondary image
+      int secondary_image_offset = primary_image_size - pos - 8;
+      sp<DataStruct> mpf = generateMpf(primary_image_size,
+                                       0, /* primary_image_offset */
+                                       secondary_image_size,
+                                       secondary_image_offset);
+      JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+      JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
+      JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+      JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+      JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
   }
 
   // Write primary image
   JPEGR_CHECK(Write(dest,
       (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
+  // Finish primary image
+
+  // Begin secondary image (recovery map)
+  // Write SOI
+  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
+
+  // Prepare and write XMP
+  {
+    const int length = xmp_secondary_length;
+    const uint8_t lengthH = ((length >> 8) & 0xff);
+    const uint8_t lengthL = (length & 0xff);
+    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
+    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+    JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
+    JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
+  }
 
   // Write secondary image
-  JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
+  JPEGR_CHECK(Write(dest,
+        (uint8_t*)compressed_recovery_map->data + 2, compressed_recovery_map->length - 2, pos));
 
   // Set back length
   dest->length = pos;
@@ -864,8 +928,7 @@
   return NO_ERROR;
 }
 
-status_t JpegR::toneMap(jr_uncompressed_ptr src,
-                              jr_uncompressed_ptr dest) {
+status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
   if (src == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
diff --git a/libs/jpegrecoverymap/jpegrutils.cpp b/libs/jpegrecoverymap/jpegrutils.cpp
index 49526c8..38b78ad 100644
--- a/libs/jpegrecoverymap/jpegrutils.cpp
+++ b/libs/jpegrecoverymap/jpegrutils.cpp
@@ -15,18 +15,19 @@
  */
 
 #include <jpegrecoverymap/jpegrutils.h>
+#include <utils/Log.h>
 #include <image_io/xml/xml_reader.h>
 #include <image_io/xml/xml_writer.h>
 #include <image_io/base/message_handler.h>
 #include <image_io/xml/xml_element_rules.h>
 #include <image_io/xml/xml_handler.h>
 #include <image_io/xml/xml_rule.h>
+#include <cmath>
 
 using namespace photos_editing_formats::image_io;
 using namespace std;
 
 namespace android::jpegrecoverymap {
-
 /*
  * Helper function used for generating XMP metadata.
  *
@@ -34,12 +35,62 @@
  * @param suffix The suffix part of the name.
  * @return A name of the form "prefix:suffix".
  */
-string Name(const string &prefix, const string &suffix) {
+static inline string Name(const string &prefix, const string &suffix) {
   std::stringstream ss;
   ss << prefix << ":" << suffix;
   return ss.str();
 }
 
+DataStruct::DataStruct(int s) {
+    data = malloc(s);
+    length = s;
+    memset(data, 0, s);
+    writePos = 0;
+}
+
+DataStruct::~DataStruct() {
+    if (data != nullptr) {
+        free(data);
+    }
+}
+
+void* DataStruct::getData() {
+    return data;
+}
+
+int DataStruct::getLength() {
+    return length;
+}
+
+int DataStruct::getBytesWritten() {
+    return writePos;
+}
+
+bool DataStruct::write8(uint8_t value) {
+    uint8_t v = value;
+    return write(&v, 1);
+}
+
+bool DataStruct::write16(uint16_t value) {
+    uint16_t v = value;
+    return write(&v, 2);
+}
+bool DataStruct::write32(uint32_t value) {
+    uint32_t v = value;
+    return write(&v, 4);
+}
+
+bool DataStruct::write(const void* src, int size) {
+    if (writePos + size > length) {
+        ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d",
+                writePos, size, length);
+        return false;
+    }
+    memcpy((uint8_t*) data + writePos, src, size);
+    writePos += size;
+    return true;
+}
+
 /*
  * Helper function used for writing data to destination.
  */
@@ -58,7 +109,7 @@
 public:
 
     XMPXmlHandler() : XmlHandler() {
-        gContainerItemState = NotStrarted;
+        state = NotStrarted;
     }
 
     enum ParseState {
@@ -70,11 +121,11 @@
     virtual DataMatchResult StartElement(const XmlTokenContext& context) {
         string val;
         if (context.BuildTokenValue(&val)) {
-            if (!val.compare(gContainerItemName)) {
-                gContainerItemState = Started;
+            if (!val.compare(containerName)) {
+                state = Started;
             } else {
-                if (gContainerItemState != Done) {
-                    gContainerItemState = NotStrarted;
+                if (state != Done) {
+                    state = NotStrarted;
                 }
             }
         }
@@ -82,8 +133,8 @@
     }
 
     virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
-        if (gContainerItemState == Started) {
-            gContainerItemState = Done;
+        if (state == Started) {
+            state = Done;
             lastAttributeName = "";
         }
         return context.GetResult();
@@ -91,7 +142,7 @@
 
     virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
         string val;
-        if (gContainerItemState == Started) {
+        if (state == Started) {
             if (context.BuildTokenValue(&val)) {
                 if (!val.compare(maxContentBoostAttrName)) {
                     lastAttributeName = maxContentBoostAttrName;
@@ -107,7 +158,7 @@
 
     virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
         string val;
-        if (gContainerItemState == Started) {
+        if (state == Started) {
             if (context.BuildTokenValue(&val, true)) {
                 if (!lastAttributeName.compare(maxContentBoostAttrName)) {
                     maxContentBoostStr = val;
@@ -120,11 +171,11 @@
     }
 
     bool getMaxContentBoost(float* max_content_boost) {
-        if (gContainerItemState == Done) {
+        if (state == Done) {
             stringstream ss(maxContentBoostStr);
             float val;
             if (ss >> val) {
-                *max_content_boost = val;
+                *max_content_boost = exp2(val);
                 return true;
             } else {
                 return false;
@@ -135,11 +186,11 @@
     }
 
     bool getMinContentBoost(float* min_content_boost) {
-        if (gContainerItemState == Done) {
+        if (state == Done) {
             stringstream ss(minContentBoostStr);
             float val;
             if (ss >> val) {
-                *min_content_boost = val;
+                *min_content_boost = exp2(val);
                 return true;
             } else {
                 return false;
@@ -150,13 +201,13 @@
     }
 
 private:
-    static const string gContainerItemName;
+    static const string containerName;
     static const string maxContentBoostAttrName;
     string              maxContentBoostStr;
     static const string minContentBoostAttrName;
     string              minContentBoostStr;
     string              lastAttributeName;
-    ParseState          gContainerItemState;
+    ParseState          state;
 };
 
 // GContainer XMP constants - URI and namespace prefix
@@ -168,8 +219,7 @@
 const string kConItem                 = Name(kContainerPrefix, "Item");
 
 // GContainer XMP constants - names for XMP handlers
-const string XMPXmlHandler::gContainerItemName = kConItem;
-
+const string XMPXmlHandler::containerName = "rdf:Description";
 // Item XMP constants - URI and namespace prefix
 const string kItemUri        = "http://ns.google.com/photos/1.0/container/item/";
 const string kItemPrefix     = "Item";
@@ -185,17 +235,23 @@
 const string kMimeImageJpeg       = "image/jpeg";
 
 // RecoveryMap XMP constants - URI and namespace prefix
-const string kRecoveryMapUri      = "http://ns.google.com/photos/1.0/recoverymap/";
-const string kRecoveryMapPrefix   = "RecoveryMap";
+const string kRecoveryMapUri      = "http://ns.adobe.com/hdr-gain-map/1.0/";
+const string kRecoveryMapPrefix   = "hdrgm";
 
 // RecoveryMap XMP constants - element and attribute names
-const string kMapMaxContentBoost  = Name(kRecoveryMapPrefix, "MaxContentBoost");
-const string kMapMinContentBoost  = Name(kRecoveryMapPrefix, "MinContentBoost");
 const string kMapVersion          = Name(kRecoveryMapPrefix, "Version");
+const string kMapGainMapMin       = Name(kRecoveryMapPrefix, "GainMapMin");
+const string kMapGainMapMax       = Name(kRecoveryMapPrefix, "GainMapMax");
+const string kMapGamma            = Name(kRecoveryMapPrefix, "Gamma");
+const string kMapOffsetSdr        = Name(kRecoveryMapPrefix, "OffsetSDR");
+const string kMapOffsetHdr        = Name(kRecoveryMapPrefix, "OffsetHDR");
+const string kMapHDRCapacityMin   = Name(kRecoveryMapPrefix, "HDRCapacityMin");
+const string kMapHDRCapacityMax   = Name(kRecoveryMapPrefix, "HDRCapacityMax");
+const string kMapBaseRendition    = Name(kRecoveryMapPrefix, "BaseRendition");
 
 // RecoveryMap XMP constants - names for XMP handlers
-const string XMPXmlHandler::maxContentBoostAttrName = kMapMaxContentBoost;
-const string XMPXmlHandler::minContentBoostAttrName = kMapMinContentBoost;
+const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin;
+const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax;
 
 bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
     string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
@@ -243,7 +299,7 @@
     return true;
 }
 
-string generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
+string generateXmpForPrimaryImage(int secondary_image_length) {
   const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
   const vector<string> kLiItem({string("rdf:li"), kConItem});
 
@@ -257,7 +313,6 @@
   writer.StartWritingElement("rdf:Description");
   writer.WriteXmlns(kContainerPrefix, kContainerUri);
   writer.WriteXmlns(kItemPrefix, kItemUri);
-  writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
   writer.StartWritingElements(kConDirSeq);
   size_t item_depth = writer.StartWritingElements(kLiItem);
   writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
@@ -267,9 +322,33 @@
   writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap);
   writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
   writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
+  writer.FinishWriting();
+
+  return ss.str();
+}
+
+string generateXmpForSecondaryImage(jpegr_metadata& metadata) {
+  const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
+  const vector<string> kLiItem({string("rdf:li"), kConItem});
+
+  std::stringstream ss;
+  photos_editing_formats::image_io::XmlWriter writer(ss);
+  writer.StartWritingElement("x:xmpmeta");
+  writer.WriteXmlns("x", "adobe:ns:meta/");
+  writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
+  writer.StartWritingElement("rdf:RDF");
+  writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+  writer.StartWritingElement("rdf:Description");
+  writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
   writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
-  writer.WriteAttributeNameAndValue(kMapMaxContentBoost, metadata.maxContentBoost);
-  writer.WriteAttributeNameAndValue(kMapMinContentBoost, metadata.minContentBoost);
+  writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost));
+  writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost));
+  writer.WriteAttributeNameAndValue(kMapGamma, "1");
+  writer.WriteAttributeNameAndValue(kMapOffsetSdr, "0");
+  writer.WriteAttributeNameAndValue(kMapOffsetHdr, "0");
+  writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, "0");
+  writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, "2.3");
+  writer.WriteAttributeNameAndValue(kMapBaseRendition, "SDR");
   writer.FinishWriting();
 
   return ss.str();
diff --git a/libs/jpegrecoverymap/multipictureformat.cpp b/libs/jpegrecoverymap/multipictureformat.cpp
new file mode 100644
index 0000000..a219aef
--- /dev/null
+++ b/libs/jpegrecoverymap/multipictureformat.cpp
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+#include <jpegrecoverymap/multipictureformat.h>
+#include <jpegrecoverymap/jpegrutils.h>
+
+namespace android::jpegrecoverymap {
+size_t calculateMpfSize() {
+    return sizeof(kMpfSig) +                 // Signature
+            kMpEndianSize +                   // Endianness
+            sizeof(uint32_t) +                // Index IFD Offset
+            sizeof(uint16_t) +                // Tag count
+            kTagSerializedCount * kTagSize +  // 3 tags at 12 bytes each
+            sizeof(uint32_t) +                // Attribute IFD offset
+            kNumPictures * kMPEntrySize;      // MP Entries for each image
+}
+
+sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset,
+        int secondary_image_size, int secondary_image_offset) {
+    size_t mpf_size = calculateMpfSize();
+    sp<DataStruct> dataStruct = new DataStruct(mpf_size);
+
+    dataStruct->write(static_cast<const void*>(kMpfSig), sizeof(kMpfSig));
+#if USE_BIG_ENDIAN
+    dataStruct->write(static_cast<const void*>(kMpBigEndian), kMpEndianSize);
+#else
+    dataStruct->write(static_cast<const void*>(kMpLittleEndian), kMpEndianSize);
+#endif
+
+    // Set the Index IFD offset be the position after the endianness value and this offset.
+    constexpr uint32_t indexIfdOffset =
+            static_cast<uint16_t>(kMpEndianSize + sizeof(kMpfSig));
+    dataStruct->write32(Endian_SwapBE32(indexIfdOffset));
+
+    // We will write 3 tags (version, number of images, MP entries).
+    dataStruct->write16(Endian_SwapBE16(kTagSerializedCount));
+
+    // Write the version tag.
+    dataStruct->write16(Endian_SwapBE16(kVersionTag));
+    dataStruct->write16(Endian_SwapBE16(kVersionType));
+    dataStruct->write32(Endian_SwapBE32(kVersionCount));
+    dataStruct->write(kVersionExpected, kVersionSize);
+
+    // Write the number of images.
+    dataStruct->write16(Endian_SwapBE16(kNumberOfImagesTag));
+    dataStruct->write16(Endian_SwapBE16(kNumberOfImagesType));
+    dataStruct->write32(Endian_SwapBE32(kNumberOfImagesCount));
+    dataStruct->write32(Endian_SwapBE32(kNumPictures));
+
+    // Write the MP entries.
+    dataStruct->write16(Endian_SwapBE16(kMPEntryTag));
+    dataStruct->write16(Endian_SwapBE16(kMPEntryType));
+    dataStruct->write32(Endian_SwapBE32(kMPEntrySize * kNumPictures));
+    const uint32_t mpEntryOffset =
+            static_cast<uint32_t>(dataStruct->getBytesWritten() -  // The bytes written so far
+                                  sizeof(kMpfSig) +   // Excluding the MPF signature
+                                  sizeof(uint32_t) +  // The 4 bytes for this offset
+                                  sizeof(uint32_t));  // The 4 bytes for the attribute IFD offset.
+    dataStruct->write32(Endian_SwapBE32(mpEntryOffset));
+
+    // Write the attribute IFD offset (zero because we don't write it).
+    dataStruct->write32(0);
+
+    // Write the MP entries for primary image
+    dataStruct->write32(
+            Endian_SwapBE32(kMPEntryAttributeFormatJpeg | kMPEntryAttributeTypePrimary));
+    dataStruct->write32(Endian_SwapBE32(primary_image_size));
+    dataStruct->write32(Endian_SwapBE32(primary_image_offset));
+    dataStruct->write16(0);
+    dataStruct->write16(0);
+
+    // Write the MP entries for secondary image
+    dataStruct->write32(Endian_SwapBE32(kMPEntryAttributeFormatJpeg));
+    dataStruct->write32(Endian_SwapBE32(secondary_image_size));
+    dataStruct->write32(Endian_SwapBE32(secondary_image_offset));
+    dataStruct->write16(0);
+    dataStruct->write16(0);
+
+    return dataStruct;
+}
+
+} // namespace android::jpegrecoverymap
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
index 5a4edb2..61b3db9 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -40,6 +40,7 @@
         "libjpegencoder",
         "libjpegrecoverymap",
         "libskia",
+        "libutils",
     ],
 }
 
diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp
index 7a3133d..df212e1 100644
--- a/libs/jpegrecoverymap/tests/jpegr_test.cpp
+++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp
@@ -177,11 +177,10 @@
   jpegr_metadata metadata_expected;
   metadata_expected.maxContentBoost = 1.25;
   metadata_expected.minContentBoost = 0.75;
-  int length_expected = 1000;
   const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
   const int nameSpaceLength = nameSpace.size() + 1;  // need to count the null terminator
 
-  std::string xmp = generateXmp(1000, metadata_expected);
+  std::string xmp = generateXmpForSecondaryImage(metadata_expected);
 
   std::vector<uint8_t> xmpData;
   xmpData.reserve(nameSpaceLength + xmp.size());
@@ -220,7 +219,7 @@
   }
   if (SAVE_ENCODING_RESULT) {
     // Output image data to file
-    std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
+    std::string filePath = "/sdcard/Documents/encoded_from_p010_input.jpgr";
     std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
     if (!imageFile.is_open()) {
       ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
@@ -237,7 +236,7 @@
   }
   if (SAVE_DECODING_RESULT) {
     // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
+    std::string filePath = "/sdcard/Documents/decoded_from_p010_input.rgb10";
     std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
     if (!imageFile.is_open()) {
       ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
@@ -281,7 +280,7 @@
   }
   if (SAVE_ENCODING_RESULT) {
     // Output image data to file
-    std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
+    std::string filePath = "/sdcard/Documents/encoded_from_p010_yuv420p_input.jpgr";
     std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
     if (!imageFile.is_open()) {
       ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
@@ -298,7 +297,7 @@
   }
   if (SAVE_DECODING_RESULT) {
     // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
+    std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_input.rgb10";
     std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
     if (!imageFile.is_open()) {
       ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
@@ -346,7 +345,7 @@
   }
   if (SAVE_ENCODING_RESULT) {
     // Output image data to file
-    std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
+    std::string filePath = "/sdcard/Documents/encoded_from_p010_yuv420p_jpeg_input.jpgr";
     std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
     if (!imageFile.is_open()) {
       ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
@@ -363,7 +362,7 @@
   }
   if (SAVE_DECODING_RESULT) {
     // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
+    std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_jpeg_input.rgb10";
     std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
     if (!imageFile.is_open()) {
       ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
@@ -427,7 +426,7 @@
   }
   if (SAVE_ENCODING_RESULT) {
     // Output image data to file
-    std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
+    std::string filePath = "/sdcard/Documents/encoded_from_p010_jpeg_input.jpgr";
     std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
     if (!imageFile.is_open()) {
       ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
@@ -444,7 +443,7 @@
   }
   if (SAVE_DECODING_RESULT) {
     // Output image data to file
-    std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
+    std::string filePath = "/sdcard/Documents/decoded_from_p010_jpeg_input.rgb10";
     std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
     if (!imageFile.is_open()) {
       ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
diff --git a/libs/renderengine/ExternalTexture.cpp b/libs/renderengine/ExternalTexture.cpp
index 84771c0..210dca5 100644
--- a/libs/renderengine/ExternalTexture.cpp
+++ b/libs/renderengine/ExternalTexture.cpp
@@ -39,7 +39,7 @@
 }
 
 ExternalTexture::~ExternalTexture() {
-    mRenderEngine.unmapExternalTextureBuffer(mBuffer);
+    mRenderEngine.unmapExternalTextureBuffer(std::move(mBuffer));
 }
 
 } // namespace android::renderengine::impl
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index 13f766c..0d7df10 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -800,7 +800,7 @@
     return NO_ERROR;
 }
 
-void GLESRenderEngine::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
+void GLESRenderEngine::unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) {
     mImageManager->releaseAsync(buffer->getId(), nullptr);
 }
 
@@ -1262,7 +1262,7 @@
 
             // Do not cache protected EGLImage, protected memory is limited.
             if (gBuf->getUsage() & GRALLOC_USAGE_PROTECTED) {
-                unmapExternalTextureBuffer(gBuf);
+                unmapExternalTextureBuffer(std::move(gBuf));
             }
         }
 
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
index 1b34921..402ff52 100644
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ b/libs/renderengine/gl/GLESRenderEngine.h
@@ -101,7 +101,7 @@
     size_t getMaxViewportDims() const override;
     void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable)
             EXCLUDES(mRenderingMutex);
-    void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) EXCLUDES(mRenderingMutex);
+    void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) EXCLUDES(mRenderingMutex);
     bool canSkipPostRenderCleanup() const override;
     void drawLayersInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
                             const DisplaySettings& display,
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 39621cd..0d910c9 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -231,7 +231,7 @@
     // asynchronously, but the caller can expect that map/unmap calls are performed in a manner
     // that's conflict serializable, i.e. unmap a buffer should never occur before binding the
     // buffer if the caller called mapExternalTextureBuffer before calling unmap.
-    virtual void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) = 0;
+    virtual void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) = 0;
 
     // A thread safe query to determine if any post rendering cleanup is necessary.  Returning true
     // is a signal that calling the postRenderCleanup method would be a no-op and that callers can
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index e3ce85d..d3035e2 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -63,7 +63,7 @@
 protected:
     // mock renderengine still needs to implement these, but callers should never need to call them.
     void mapExternalTextureBuffer(const sp<GraphicBuffer>&, bool) {}
-    void unmapExternalTextureBuffer(const sp<GraphicBuffer>&) {}
+    void unmapExternalTextureBuffer(sp<GraphicBuffer>&&) {}
 };
 
 } // namespace mock
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 413811e..5965d41 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -423,7 +423,7 @@
     }
 }
 
-void SkiaRenderEngine::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
+void SkiaRenderEngine::unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) {
     ATRACE_CALL();
     std::lock_guard<std::mutex> lock(mRenderingMutex);
     if (const auto& iter = mGraphicBufferExternalRefs.find(buffer->getId());
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index 1973c7d..dd6646b 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -130,7 +130,7 @@
 private:
     void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
                                   bool isRenderable) override final;
-    void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override final;
+    void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) override final;
     bool canSkipPostRenderCleanup() const override final;
 
     void initCanvas(SkCanvas* canvas, const DisplaySettings& display);
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index 8d99f3d..936e316 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -444,8 +444,11 @@
     ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent);
 
     VkQueue graphicsQueue;
-    VK_GET_DEV_PROC(device, GetDeviceQueue);
-    vkGetDeviceQueue(device, graphicsQueueIndex, 0, &graphicsQueue);
+    VK_GET_DEV_PROC(device, GetDeviceQueue2);
+    const VkDeviceQueueInfo2 deviceQueueInfo2 = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2, nullptr,
+                                                 deviceQueueCreateFlags,
+                                                 (uint32_t)graphicsQueueIndex, 0};
+    vkGetDeviceQueue2(device, &deviceQueueInfo2, &graphicsQueue);
 
     VK_GET_DEV_PROC(device, DeviceWaitIdle);
     VK_GET_DEV_PROC(device, DestroyDevice);
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index 8aa41b3..6a1561a 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -230,16 +230,17 @@
     mCondition.notify_one();
 }
 
-void RenderEngineThreaded::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
+void RenderEngineThreaded::unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) {
     ATRACE_CALL();
     // This function is designed so it can run asynchronously, so we do not need to wait
     // for the futures.
     {
         std::lock_guard lock(mThreadMutex);
-        mFunctionCalls.push([=](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::unmapExternalTextureBuffer");
-            instance.unmapExternalTextureBuffer(buffer);
-        });
+        mFunctionCalls.push(
+                [=, buffer = std::move(buffer)](renderengine::RenderEngine& instance) mutable {
+                    ATRACE_NAME("REThreaded::unmapExternalTextureBuffer");
+                    instance.unmapExternalTextureBuffer(std::move(buffer));
+                });
     }
     mCondition.notify_one();
 }
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index 168e2d2..6eb108e 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -69,7 +69,7 @@
 
 protected:
     void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable) override;
-    void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override;
+    void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) override;
     bool canSkipPostRenderCleanup() const override;
     void drawLayersInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
                             const DisplaySettings& display,
diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp
index 2278d39..e2aac8c 100644
--- a/libs/sensor/ISensorServer.cpp
+++ b/libs/sensor/ISensorServer.cpp
@@ -67,7 +67,11 @@
         v.setCapacity(n);
         while (n) {
             n--;
-            reply.read(s);
+            if(reply.read(s) != OK) {
+                ALOGE("Failed to read reply from getSensorList");
+                v.clear();
+                break;
+            }
             v.add(s);
         }
         return v;
@@ -85,7 +89,11 @@
         v.setCapacity(n);
         while (n) {
             n--;
-            reply.read(s);
+            if(reply.read(s) != OK) {
+                ALOGE("Failed to read reply from getDynamicSensorList");
+                v.clear();
+                break;
+            }
             v.add(s);
         }
         return v;
diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp
index fb895f5..b6ea77d 100644
--- a/libs/sensor/Sensor.cpp
+++ b/libs/sensor/Sensor.cpp
@@ -628,7 +628,13 @@
         return false;
     }
     outputString8.setTo(static_cast<char const*>(buffer), len);
+
+    if (size < FlattenableUtils::align<4>(len)) {
+        ALOGE("Malformed Sensor String8 field. Should be in a 4-byte aligned buffer but is not.");
+        return false;
+    }
     FlattenableUtils::advance(buffer, size, FlattenableUtils::align<4>(len));
+
     return true;
 }
 
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 2748276..44a208d 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -92,6 +92,16 @@
     return *sensorManager;
 }
 
+void SensorManager::removeInstanceForPackage(const String16& packageName) {
+    Mutex::Autolock _l(sLock);
+    auto iterator = sPackageInstances.find(packageName);
+    if (iterator != sPackageInstances.end()) {
+        SensorManager* sensorManager = iterator->second;
+        delete sensorManager;
+        sPackageInstances.erase(iterator);
+    }
+}
+
 SensorManager::SensorManager(const String16& opPackageName)
     : mSensorList(nullptr), mOpPackageName(opPackageName), mDirectConnectionHandle(1) {
     Mutex::Autolock _l(mLock);
@@ -166,6 +176,11 @@
 
         mSensors = mSensorServer->getSensorList(mOpPackageName);
         size_t count = mSensors.size();
+        if (count == 0) {
+            ALOGE("Failed to get Sensor list");
+            mSensorServer.clear();
+            return UNKNOWN_ERROR;
+        }
         mSensorList =
                 static_cast<Sensor const**>(malloc(count * sizeof(Sensor*)));
         LOG_ALWAYS_FATAL_IF(mSensorList == nullptr, "mSensorList NULL");
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index 0798da2..c31f648 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -54,6 +54,7 @@
 {
 public:
     static SensorManager& getInstanceForPackage(const String16& packageName);
+    static void removeInstanceForPackage(const String16& packageName);
     ~SensorManager();
 
     ssize_t getSensorList(Sensor const* const** list);
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index 7459466..c3af996 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -22,6 +22,8 @@
 #include <aidlcommonsupport/NativeHandle.h>
 #include <android/binder_enums.h>
 #include <android/binder_manager.h>
+#include <cutils/android_filesystem_config.h>
+#include <cutils/multiuser.h>
 #include <gralloctypes/Gralloc4.h>
 #include <hidl/ServiceManagement.h>
 #include <hwbinder/IPCThreadState.h>
@@ -1195,8 +1197,15 @@
     mAllocator = IAllocator::getService();
     if (__builtin_available(android 31, *)) {
         if (hasIAllocatorAidl()) {
-            mAidlAllocator = AidlIAllocator::fromBinder(ndk::SpAIBinder(
-                    AServiceManager_waitForService(kAidlAllocatorServiceName.c_str())));
+            // TODO(b/269517338): Perform the isolated checking for this in service manager instead.
+            uid_t aid = multiuser_get_app_id(getuid());
+            if (aid >= AID_ISOLATED_START && aid <= AID_ISOLATED_END) {
+                mAidlAllocator = AidlIAllocator::fromBinder(ndk::SpAIBinder(
+                        AServiceManager_getService(kAidlAllocatorServiceName.c_str())));
+            } else {
+                mAidlAllocator = AidlIAllocator::fromBinder(ndk::SpAIBinder(
+                        AServiceManager_waitForService(kAidlAllocatorServiceName.c_str())));
+            }
             ALOGE_IF(!mAidlAllocator, "AIDL IAllocator declared but failed to get service");
         }
     }
diff --git a/opengl/libs/EGL/BlobCache.cpp b/opengl/libs/EGL/BlobCache.cpp
index 86c788d..aecfc6b 100644
--- a/opengl/libs/EGL/BlobCache.cpp
+++ b/opengl/libs/EGL/BlobCache.cpp
@@ -231,7 +231,7 @@
 
 int BlobCache::unflatten(void const* buffer, size_t size) {
     // All errors should result in the BlobCache being in an empty state.
-    mCacheEntries.clear();
+    clear();
 
     // Read the cache header
     if (size < sizeof(Header)) {
@@ -258,7 +258,7 @@
     size_t numEntries = header->mNumEntries;
     for (size_t i = 0; i < numEntries; i++) {
         if (byteOffset + sizeof(EntryHeader) > size) {
-            mCacheEntries.clear();
+            clear();
             ALOGE("unflatten: not enough room for cache entry headers");
             return -EINVAL;
         }
@@ -270,7 +270,7 @@
 
         size_t totalSize = align4(entrySize);
         if (byteOffset + totalSize > size) {
-            mCacheEntries.clear();
+            clear();
             ALOGE("unflatten: not enough room for cache entry headers");
             return -EINVAL;
         }
diff --git a/opengl/libs/EGL/BlobCache.h b/opengl/libs/EGL/BlobCache.h
index ff03d30..52078ff 100644
--- a/opengl/libs/EGL/BlobCache.h
+++ b/opengl/libs/EGL/BlobCache.h
@@ -117,7 +117,10 @@
 
     // clear flushes out all contents of the cache then the BlobCache, leaving
     // it in an empty state.
-    void clear() { mCacheEntries.clear(); }
+    void clear() {
+        mCacheEntries.clear();
+        mTotalSize = 0;
+    }
 
 protected:
     // mMaxTotalSize is the maximum size that all cache entries can occupy. This
diff --git a/opengl/libs/EGL/BlobCache_test.cpp b/opengl/libs/EGL/BlobCache_test.cpp
index ceea0fb..450c128 100644
--- a/opengl/libs/EGL/BlobCache_test.cpp
+++ b/opengl/libs/EGL/BlobCache_test.cpp
@@ -466,4 +466,31 @@
     ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
 }
 
+// Test for a divide by zero bug (b/239862516). Before the fix, unflatten() would not reset
+// mTotalSize when it encountered an error, which would trigger division by 0 in clean() in the
+// right conditions.
+TEST_F(BlobCacheFlattenTest, SetAfterFailedUnflatten) {
+    // isCleanable() must be true, so mTotalSize must be > mMaxTotalSize / 2 after unflattening
+    // after one entry is lost. To make this the case, MaxTotalSize is 30 and three 10 sized
+    // entries are used. One of those entries is lost, resulting in mTotalSize=20
+    const size_t kMaxKeySize = 10;
+    const size_t kMaxValueSize = 10;
+    const size_t kMaxTotalSize = 30;
+    mBC.reset(new BlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize));
+    mBC2.reset(new BlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize));
+    mBC->set("aaaaa", 5, "aaaaa", 5);
+    mBC->set("bbbbb", 5, "bbbbb", 5);
+    mBC->set("ccccc", 5, "ccccc", 5);
+
+    size_t size = mBC->getFlattenedSize();
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(OK, mBC->flatten(flat, size));
+
+    ASSERT_EQ(BAD_VALUE, mBC2->unflatten(flat, size - 10));
+    delete[] flat;
+
+    // This line will trigger clean() which caused a crash.
+    mBC2->set("dddddddddd", 10, "dddddddddd", 10);
+}
+
 } // namespace android
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index ab5c5ef..da4e42f 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -34,6 +34,7 @@
     srcs: [
         "AnrTracker.cpp",
         "Connection.cpp",
+        "DebugConfig.cpp",
         "DragState.cpp",
         "Entry.cpp",
         "FocusResolver.cpp",
diff --git a/services/inputflinger/dispatcher/DebugConfig.cpp b/services/inputflinger/dispatcher/DebugConfig.cpp
new file mode 100644
index 0000000..764194d
--- /dev/null
+++ b/services/inputflinger/dispatcher/DebugConfig.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include "DebugConfig.h"
+
+#include <android-base/properties.h>
+
+namespace android::inputdispatcher {
+
+const bool IS_DEBUGGABLE_BUILD =
+#if defined(__ANDROID__)
+        android::base::GetBoolProperty("ro.debuggable", false);
+#else
+        true;
+#endif
+
+bool debugInboundEventDetails() {
+    if (!IS_DEBUGGABLE_BUILD) {
+        static const bool DEBUG_INBOUND_EVENT_DETAILS =
+                __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent",
+                                          ANDROID_LOG_INFO);
+        return DEBUG_INBOUND_EVENT_DETAILS;
+    }
+    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent", ANDROID_LOG_INFO);
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/DebugConfig.h b/services/inputflinger/dispatcher/DebugConfig.h
index d2ad407..0e260a7 100644
--- a/services/inputflinger/dispatcher/DebugConfig.h
+++ b/services/inputflinger/dispatcher/DebugConfig.h
@@ -22,12 +22,20 @@
 #include <log/log_event_list.h>
 
 namespace android::inputdispatcher {
+
+/**
+ * Signals whether this is a debuggable Android build.
+ * This is populated by reading the value of the "ro.debuggable" property.
+ */
+extern const bool IS_DEBUGGABLE_BUILD;
+
 /**
  * Log detailed debug messages about each inbound event notification to the dispatcher.
- * Enable this via "adb shell setprop log.tag.InputDispatcherInboundEvent DEBUG" (requires restart)
+ * Enable this via "adb shell setprop log.tag.InputDispatcherInboundEvent DEBUG".
+ * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
+ * on debuggable builds (e.g. userdebug).
  */
-const bool DEBUG_INBOUND_EVENT_DETAILS =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent", ANDROID_LOG_INFO);
+bool debugInboundEventDetails();
 
 /**
  * Log detailed debug messages about each outbound event processed by the dispatcher.
@@ -90,4 +98,5 @@
  */
 const bool DEBUG_HOVER =
         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Hover", ANDROID_LOG_INFO);
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index ce7c882..b625a1b 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "InputDispatcher"
+
 #include "Entry.h"
 
 #include "Connection.h"
+#include "DebugConfig.h"
 
-#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <cutils/atomic.h>
 #include <inttypes.h>
 
-using android::base::GetBoolProperty;
 using android::base::StringPrintf;
 
 namespace android::inputdispatcher {
@@ -172,7 +173,7 @@
 KeyEntry::~KeyEntry() {}
 
 std::string KeyEntry::getDescription() const {
-    if (!GetBoolProperty("ro.debuggable", false)) {
+    if (!IS_DEBUGGABLE_BUILD) {
         return "KeyEvent";
     }
     return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64 ", source=%s, displayId=%" PRId32
@@ -242,7 +243,7 @@
 MotionEntry::~MotionEntry() {}
 
 std::string MotionEntry::getDescription() const {
-    if (!GetBoolProperty("ro.debuggable", false)) {
+    if (!IS_DEBUGGABLE_BUILD) {
         return "MotionEvent";
     }
     std::string msg;
@@ -292,7 +293,7 @@
                         deviceId, inputEventSourceToString(source).c_str(),
                         ftl::enum_string(sensorType).c_str(), accuracy, hwTimestamp);
 
-    if (!GetBoolProperty("ro.debuggable", false)) {
+    if (IS_DEBUGGABLE_BUILD) {
         for (size_t i = 0; i < values.size(); i++) {
             if (i > 0) {
                 msg += ", ";
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 9d5bbbd..43cac05 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -1207,7 +1207,7 @@
     const char* reason;
     switch (dropReason) {
         case DropReason::POLICY:
-            if (DEBUG_INBOUND_EVENT_DETAILS) {
+            if (debugInboundEventDetails()) {
                 ALOGD("Dropped event because policy consumed it.");
             }
             reason = "inbound event was dropped because the policy consumed it";
@@ -1596,7 +1596,7 @@
         } else if (entry->action == AKEY_EVENT_ACTION_UP && mKeyRepeatState.lastKeyEntry &&
                    mKeyRepeatState.lastKeyEntry->deviceId != entry->deviceId) {
             // The key on device 'deviceId' is still down, do not stop key repeat
-            if (DEBUG_INBOUND_EVENT_DETAILS) {
+            if (debugInboundEventDetails()) {
                 ALOGD("deviceId=%d got KEY_UP as stale", entry->deviceId);
             }
         } else if (!entry->syntheticRepeat) {
@@ -2184,18 +2184,20 @@
     const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction;
     const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
 
+    // If pointers are already down, let's finish the current gesture and ignore the new events
+    // from another device. However, if the new event is a down event, let's cancel the current
+    // touch and let the new one take over.
+    if (switchedDevice && wasDown && !isDown) {
+        LOG(INFO) << "Dropping event because a pointer for device " << oldState->deviceId
+                  << " is already down in display " << displayId << ": " << entry.getDescription();
+        // TODO(b/211379801): test multiple simultaneous input streams.
+        outInjectionResult = InputEventInjectionResult::FAILED;
+        return {}; // wrong device
+    }
+
     if (newGesture) {
-        // If pointers are already down, let's finish the current gesture and ignore the new events
-        // from another device.
-        if (switchedDevice && wasDown) {
-            ALOGI("Dropping event because a pointer for a different device is already down "
-                  "in display %" PRId32,
-                  displayId);
-            // TODO: test multiple simultaneous input streams.
-            outInjectionResult = InputEventInjectionResult::FAILED;
-            return {}; // wrong device
-        }
-        tempTouchState.clearWindowsWithoutPointers();
+        // If a new gesture is starting, clear the touch state completely.
+        tempTouchState.reset();
         tempTouchState.deviceId = entry.deviceId;
         tempTouchState.source = entry.source;
         isSplit = false;
@@ -2203,7 +2205,7 @@
         ALOGI("Dropping move event because a pointer for a different device is already active "
               "in display %" PRId32,
               displayId);
-        // TODO: test multiple simultaneous input streams.
+        // TODO(b/211379801): test multiple simultaneous input streams.
         outInjectionResult = InputEventInjectionResult::FAILED;
         return {}; // wrong device
     }
@@ -2317,6 +2319,10 @@
             const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
                     maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
 
+            // TODO(b/211379801): Currently, even if pointerIds are empty (hover case), we would
+            // still add a window to the touch state. We should avoid doing that, but some of the
+            // later checks ("at least one foreground window") rely on this in order to dispatch
+            // the event properly, so that needs to be updated, possibly by looking at InputTargets.
             tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds,
                                              isDownOrPointerDown
                                                      ? std::make_optional(entry.eventTime)
@@ -2369,10 +2375,9 @@
 
         // If the pointer is not currently down, then ignore the event.
         if (!tempTouchState.isDown()) {
-            ALOGD_IF(DEBUG_FOCUS,
-                     "Dropping event because the pointer is not down or we previously "
-                     "dropped the pointer down event in display %" PRId32 ": %s",
-                     displayId, entry.getDescription().c_str());
+            LOG(INFO) << "Dropping event because the pointer is not down or we previously "
+                         "dropped the pointer down event in display "
+                      << displayId << ": " << entry.getDescription();
             outInjectionResult = InputEventInjectionResult::FAILED;
             return {};
         }
@@ -2530,7 +2535,6 @@
     }
 
     // Success!  Output targets from the touch state.
-    tempTouchState.clearWindowsWithoutPointers();
     for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
         if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) {
             // Windows with hovering pointers are getting persisted inside TouchState.
@@ -2570,14 +2574,13 @@
     } else if (maskedAction == AMOTION_EVENT_ACTION_UP) {
         // Pointer went up.
         tempTouchState.removeTouchedPointer(entry.pointerProperties[0].id);
-        tempTouchState.clearWindowsWithoutPointers();
     } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
         // All pointers up or canceled.
         tempTouchState.reset();
     } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
         // First pointer went down.
-        if (oldState && oldState->isDown()) {
-            ALOGD("Conflicting pointer actions: Down received while already down.");
+        if (oldState && (oldState->isDown() || oldState->hasHoveringPointers())) {
+            ALOGD("Conflicting pointer actions: Down received while already down or hovering.");
             *outConflictingPointerActions = true;
         }
     } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
@@ -2600,6 +2603,7 @@
     // state was only valid for this one action.
     if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
         if (displayId >= 0) {
+            tempTouchState.clearWindowsWithoutPointers();
             mTouchStatesByDisplay[displayId] = tempTouchState;
         } else {
             mTouchStatesByDisplay.erase(displayId);
@@ -2741,7 +2745,8 @@
         if (displayInfoIt != mDisplayInfos.end()) {
             inputTarget.displayTransform = displayInfoIt->second.transform;
         } else {
-            ALOGE("DisplayInfo not found for window on display: %d", windowInfo->displayId);
+            // DisplayInfo not found for this window on display windowInfo->displayId.
+            // TODO(b/198444055): Make this an error message after 'setInputWindows' API is removed.
         }
         inputTargets.push_back(inputTarget);
         it = inputTargets.end() - 1;
@@ -3048,9 +3053,13 @@
 
         const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry);
         if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) {
-            LOG_ALWAYS_FATAL_IF(!inputTarget.firstDownTimeInTarget.has_value(),
-                                "Splitting motion events requires a down time to be set for the "
-                                "target");
+            if (!inputTarget.firstDownTimeInTarget.has_value()) {
+                logDispatchStateLocked();
+                LOG(FATAL) << "Splitting motion events requires a down time to be set for the "
+                              "target on connection "
+                           << connection->getInputChannelName() << " for "
+                           << originalMotionEntry.getDescription();
+            }
             std::unique_ptr<MotionEntry> splitMotionEntry =
                     splitMotionEvent(originalMotionEntry, inputTarget.pointerIds,
                                      inputTarget.firstDownTimeInTarget.value());
@@ -3931,8 +3940,8 @@
         // in this way.
         ALOGW("Dropping split motion event because the pointer count is %d but "
               "we expected there to be %zu pointers.  This probably means we received "
-              "a broken sequence of pointer ids from the input device.",
-              splitPointerCount, pointerIds.count());
+              "a broken sequence of pointer ids from the input device: %s",
+              splitPointerCount, pointerIds.count(), originalMotionEntry.getDescription().c_str());
         return nullptr;
     }
 
@@ -4007,7 +4016,7 @@
 }
 
 void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
-    if (DEBUG_INBOUND_EVENT_DETAILS) {
+    if (debugInboundEventDetails()) {
         ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args->eventTime);
     }
 
@@ -4063,14 +4072,15 @@
 }
 
 void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
-    if (DEBUG_INBOUND_EVENT_DETAILS) {
-        ALOGD("notifyKey - eventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32
-              "policyFlags=0x%x, action=0x%x, "
-              "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%" PRId64,
-              args->eventTime, args->deviceId, args->source, args->displayId, args->policyFlags,
-              args->action, args->flags, args->keyCode, args->scanCode, args->metaState,
-              args->downTime);
-    }
+    ALOGD_IF(debugInboundEventDetails(),
+             "notifyKey - id=%" PRIx32 ", eventTime=%" PRId64
+             ", deviceId=%d, source=%s, displayId=%" PRId32
+             "policyFlags=0x%x, action=%s, flags=0x%x, keyCode=%s, scanCode=0x%x, metaState=0x%x, "
+             "downTime=%" PRId64,
+             args->id, args->eventTime, args->deviceId,
+             inputEventSourceToString(args->source).c_str(), args->displayId, args->policyFlags,
+             KeyEvent::actionToString(args->action), args->flags, KeyEvent::getLabel(args->keyCode),
+             args->scanCode, args->metaState, args->downTime);
     if (!validateKeyEvent(args->action)) {
         return;
     }
@@ -4141,23 +4151,22 @@
 }
 
 void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
-    if (DEBUG_INBOUND_EVENT_DETAILS) {
-        ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
+    if (debugInboundEventDetails()) {
+        ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=%s, "
               "displayId=%" PRId32 ", policyFlags=0x%x, "
               "action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, "
               "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, "
               "yCursorPosition=%f, downTime=%" PRId64,
-              args->id, args->eventTime, args->deviceId, args->source, args->displayId,
-              args->policyFlags, MotionEvent::actionToString(args->action).c_str(),
-              args->actionButton, args->flags, args->metaState, args->buttonState, args->edgeFlags,
-              args->xPrecision, args->yPrecision, args->xCursorPosition, args->yCursorPosition,
-              args->downTime);
+              args->id, args->eventTime, args->deviceId,
+              inputEventSourceToString(args->source).c_str(), args->displayId, args->policyFlags,
+              MotionEvent::actionToString(args->action).c_str(), args->actionButton, args->flags,
+              args->metaState, args->buttonState, args->edgeFlags, args->xPrecision,
+              args->yPrecision, args->xCursorPosition, args->yCursorPosition, args->downTime);
         for (uint32_t i = 0; i < args->pointerCount; i++) {
-            ALOGD("  Pointer %d: id=%d, toolType=%d, "
-                  "x=%f, y=%f, pressure=%f, size=%f, "
-                  "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
-                  "orientation=%f",
-                  i, args->pointerProperties[i].id, args->pointerProperties[i].toolType,
+            ALOGD("  Pointer %d: id=%d, toolType=%s, x=%f, y=%f, pressure=%f, size=%f, "
+                  "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f",
+                  i, args->pointerProperties[i].id,
+                  motionToolTypeToString(args->pointerProperties[i].toolType),
                   args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
                   args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
                   args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
@@ -4252,7 +4261,7 @@
 }
 
 void InputDispatcher::notifySensor(const NotifySensorArgs* args) {
-    if (DEBUG_INBOUND_EVENT_DETAILS) {
+    if (debugInboundEventDetails()) {
         ALOGD("notifySensor - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
               " sensorType=%s",
               args->id, args->eventTime, args->deviceId, args->source,
@@ -4280,7 +4289,7 @@
 }
 
 void InputDispatcher::notifyVibratorState(const NotifyVibratorStateArgs* args) {
-    if (DEBUG_INBOUND_EVENT_DETAILS) {
+    if (debugInboundEventDetails()) {
         ALOGD("notifyVibratorState - eventTime=%" PRId64 ", device=%d,  isOn=%d", args->eventTime,
               args->deviceId, args->isOn);
     }
@@ -4292,7 +4301,7 @@
 }
 
 void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) {
-    if (DEBUG_INBOUND_EVENT_DETAILS) {
+    if (debugInboundEventDetails()) {
         ALOGD("notifySwitch - eventTime=%" PRId64 ", policyFlags=0x%x, switchValues=0x%08x, "
               "switchMask=0x%08x",
               args->eventTime, args->policyFlags, args->switchValues, args->switchMask);
@@ -4304,7 +4313,7 @@
 }
 
 void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
-    if (DEBUG_INBOUND_EVENT_DETAILS) {
+    if (debugInboundEventDetails()) {
         ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args->eventTime,
               args->deviceId);
     }
@@ -4324,7 +4333,7 @@
 }
 
 void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
-    if (DEBUG_INBOUND_EVENT_DETAILS) {
+    if (debugInboundEventDetails()) {
         ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args->eventTime,
               args->request.enable ? "true" : "false");
     }
@@ -4347,7 +4356,7 @@
                                                             InputEventInjectionSync syncMode,
                                                             std::chrono::milliseconds timeout,
                                                             uint32_t policyFlags) {
-    if (DEBUG_INBOUND_EVENT_DETAILS) {
+    if (debugInboundEventDetails()) {
         ALOGD("injectInputEvent - eventType=%d, targetUid=%s, syncMode=%d, timeout=%lld, "
               "policyFlags=0x%08x",
               event->getType(), targetUid ? std::to_string(*targetUid).c_str() : "none", syncMode,
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index ad5a7fd..94f3813 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -28,10 +28,6 @@
 
 InputState::~InputState() {}
 
-bool InputState::isNeutral() const {
-    return mKeyMementos.empty() && mMotionMementos.empty();
-}
-
 bool InputState::isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const {
     for (const MotionMemento& memento : mMotionMementos) {
         if (memento.deviceId == deviceId && memento.source == source &&
@@ -251,10 +247,19 @@
 }
 
 void InputState::MotionMemento::setPointers(const MotionEntry& entry) {
-    pointerCount = entry.pointerCount;
+    pointerCount = 0;
     for (uint32_t i = 0; i < entry.pointerCount; i++) {
-        pointerProperties[i].copyFrom(entry.pointerProperties[i]);
-        pointerCoords[i].copyFrom(entry.pointerCoords[i]);
+        if (MotionEvent::getActionMasked(entry.action) == AMOTION_EVENT_ACTION_POINTER_UP) {
+            // In POINTER_UP events, the pointer is leaving. Since the action is not stored,
+            // this departing pointer should not be recorded.
+            const uint8_t actionIndex = MotionEvent::getActionIndex(entry.action);
+            if (i == actionIndex) {
+                continue;
+            }
+        }
+        pointerProperties[pointerCount].copyFrom(entry.pointerProperties[i]);
+        pointerCoords[pointerCount].copyFrom(entry.pointerCoords[i]);
+        pointerCount++;
     }
 }
 
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index 42d8cc6..d788e47 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -34,9 +34,6 @@
     explicit InputState(const IdGenerator& idGenerator);
     ~InputState();
 
-    // Returns true if there is no state to be canceled.
-    bool isNeutral() const;
-
     // Returns true if the specified source is known to have received a hover enter
     // motion event.
     bool isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const;
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 4258471..9c443f1 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -35,6 +35,7 @@
     for (TouchedWindow& touchedWindow : windows) {
         touchedWindow.removeTouchingPointer(pointerId);
     }
+    clearWindowsWithoutPointers();
 }
 
 void TouchState::removeTouchedPointerFromWindow(
@@ -42,6 +43,7 @@
     for (TouchedWindow& touchedWindow : windows) {
         if (touchedWindow.windowHandle == windowHandle) {
             touchedWindow.removeTouchingPointer(pointerId);
+            clearWindowsWithoutPointers();
             return;
         }
     }
@@ -51,6 +53,7 @@
     for (TouchedWindow& touchedWindow : windows) {
         touchedWindow.clearHoveringPointers();
     }
+    clearWindowsWithoutPointers();
 }
 
 void TouchState::clearWindowsWithoutPointers() {
@@ -135,7 +138,7 @@
             w.pointerIds &= ~pointerIds;
         }
     });
-    std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); });
+    clearWindowsWithoutPointers();
 }
 
 /**
@@ -164,7 +167,7 @@
                 w.pilferedPointerIds ^ allPilferedPointerIds;
         w.pointerIds &= ~pilferedByOtherWindows;
     });
-    std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); });
+    clearWindowsWithoutPointers();
 }
 
 sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
@@ -216,6 +219,11 @@
                        [](const TouchedWindow& window) { return window.pointerIds.any(); });
 }
 
+bool TouchState::hasHoveringPointers() const {
+    return std::any_of(windows.begin(), windows.end(),
+                       [](const TouchedWindow& window) { return window.hasHoveringPointers(); });
+}
+
 std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId,
                                                                          int32_t pointerId) const {
     std::set<sp<WindowInfoHandle>> out;
@@ -231,9 +239,7 @@
     for (TouchedWindow& window : windows) {
         window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId);
     }
-    std::erase_if(windows, [](const TouchedWindow& w) {
-        return w.pointerIds.none() && !w.hasHoveringPointers();
-    });
+    clearWindowsWithoutPointers();
 }
 
 std::string TouchState::dump() const {
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 6e965d8..a20080f 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -71,6 +71,7 @@
             const sp<android::gui::WindowInfoHandle>& windowHandle) const;
     // Whether any of the windows are currently being touched
     bool isDown() const;
+    bool hasHoveringPointers() const;
 
     std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer(
             int32_t deviceId, int32_t pointerId) const;
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index d29692c..132c3a1 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -39,6 +39,7 @@
         "EventHub.cpp",
         "InputDevice.cpp",
         "InputReader.cpp",
+        "Macros.cpp",
         "TouchVideoDevice.cpp",
         "controller/PeripheralController.cpp",
         "mapper/CursorInputMapper.cpp",
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index c598c0a..6fd4ff3 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -412,22 +412,21 @@
     // in the order received.
     std::list<NotifyArgs> out;
     for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
-        if (DEBUG_RAW_EVENTS) {
-            ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%" PRId64,
-                  rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value,
-                  rawEvent->when);
+        if (debugRawEvents()) {
+            const auto [type, code, value] =
+                    InputEventLookup::getLinuxEvdevLabel(rawEvent->type, rawEvent->code,
+                                                         rawEvent->value);
+            ALOGD("Input event: eventHubDevice=%d type=%s code=%s value=%s when=%" PRId64,
+                  rawEvent->deviceId, type.c_str(), code.c_str(), value.c_str(), rawEvent->when);
         }
 
         if (mDropUntilNextSync) {
             if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
                 mDropUntilNextSync = false;
-                if (DEBUG_RAW_EVENTS) {
-                    ALOGD("Recovered from input event buffer overrun.");
-                }
+                ALOGD_IF(debugRawEvents(), "Recovered from input event buffer overrun.");
             } else {
-                if (DEBUG_RAW_EVENTS) {
-                    ALOGD("Dropped input event while waiting for next input sync.");
-                }
+                ALOGD_IF(debugRawEvents(),
+                         "Dropped input event while waiting for next input sync.");
             }
         } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
             ALOGI("Detected input event buffer overrun for device %s.", getName().c_str());
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 57f679c..9080cc1 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -146,7 +146,7 @@
         if (mNextTimeout != LLONG_MAX) {
             nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
             if (now >= mNextTimeout) {
-                if (DEBUG_RAW_EVENTS) {
+                if (debugRawEvents()) {
                     ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
                 }
                 mNextTimeout = LLONG_MAX;
@@ -199,7 +199,7 @@
                 }
                 batchSize += 1;
             }
-            if (DEBUG_RAW_EVENTS) {
+            if (debugRawEvents()) {
                 ALOGD("BatchSize: %zu Count: %zu", batchSize, count);
             }
             out += processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
diff --git a/services/inputflinger/reader/Macros.cpp b/services/inputflinger/reader/Macros.cpp
new file mode 100644
index 0000000..8841d0f
--- /dev/null
+++ b/services/inputflinger/reader/Macros.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include "Macros.h"
+
+#include <android-base/properties.h>
+
+namespace {
+
+const bool IS_DEBUGGABLE_BUILD =
+#if defined(__ANDROID__)
+        android::base::GetBoolProperty("ro.debuggable", false);
+#else
+        true;
+#endif
+
+} // namespace
+
+namespace android {
+
+bool debugRawEvents() {
+    if (!IS_DEBUGGABLE_BUILD) {
+        static const bool DEBUG_RAW_EVENTS =
+                __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "RawEvents", ANDROID_LOG_INFO);
+        return DEBUG_RAW_EVENTS;
+    }
+    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "RawEvents", ANDROID_LOG_INFO);
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/Macros.h b/services/inputflinger/reader/Macros.h
index d2a7ced..2bce215 100644
--- a/services/inputflinger/reader/Macros.h
+++ b/services/inputflinger/reader/Macros.h
@@ -25,12 +25,14 @@
 #include <unordered_map>
 
 namespace android {
+
 /**
  * Log debug messages for each raw event received from the EventHub.
- * Enable this via "adb shell setprop log.tag.InputReaderRawEvents DEBUG" (requires restart)
+ * Enable this via "adb shell setprop log.tag.InputReaderRawEvents DEBUG".
+ * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
+ * on debuggable builds (e.g. userdebug).
  */
-const bool DEBUG_RAW_EVENTS =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "RawEvents", ANDROID_LOG_INFO);
+bool debugRawEvents();
 
 /**
  * Log debug messages about virtual key processing.
@@ -52,6 +54,7 @@
  */
 const bool DEBUG_POINTER_ASSIGNMENT =
         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "PointerAssignment", ANDROID_LOG_INFO);
+
 /**
  * Log debug messages about gesture detection.
  * Enable this via "adb shell setprop log.tag.InputReaderGestures DEBUG" (requires restart)
@@ -79,6 +82,7 @@
  */
 const bool DEBUG_LIGHT_DETAILS =
         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "LightDetails", ANDROID_LOG_INFO);
+
 } // namespace android
 
 #define INDENT "  "
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 31fdac9..3ba6cd0 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -1446,7 +1446,7 @@
         assignPointerIds(last, next);
     }
 
-    ALOGD_IF(DEBUG_RAW_EVENTS,
+    ALOGD_IF(debugRawEvents(),
              "syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, "
              "hovering ids 0x%08x -> 0x%08x, canceled ids 0x%08x",
              last.rawPointerData.pointerCount, next.rawPointerData.pointerCount,
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 9f32311..8ab6748 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -16,6 +16,7 @@
 
 #include "../Macros.h"
 
+#include <limits>
 #include <optional>
 
 #include <android/input.h>
@@ -30,6 +31,76 @@
 
 namespace {
 
+// Describes a segment of the acceleration curve.
+struct CurveSegment {
+    // The maximum pointer speed which this segment should apply. The last segment in a curve should
+    // always set this to infinity.
+    double maxPointerSpeedMmPerS;
+    double slope;
+    double intercept;
+};
+
+const std::vector<CurveSegment> segments = {
+        {10.922, 3.19, 0},
+        {31.750, 4.79, -17.526},
+        {98.044, 7.28, -96.52},
+        {std::numeric_limits<double>::infinity(), 15.04, -857.758},
+};
+
+const std::vector<double> sensitivityFactors = {1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20};
+
+std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity,
+                                                          size_t propertySize) {
+    LOG_ALWAYS_FATAL_IF(propertySize < 4 * segments.size());
+    std::vector<double> output(propertySize, 0);
+
+    // The Gestures library uses functions of the following form to define curve segments, where a,
+    // b, and c can be specified by us:
+    //     output_speed(input_speed_mm) = a * input_speed_mm ^ 2 + b * input_speed_mm + c
+    //
+    // (a, b, and c are also called sqr_, mul_, and int_ in the Gestures library code.)
+    //
+    // We are trying to implement the following function, where slope and intercept are the
+    // parameters specified in the `segments` array above:
+    //     gain(input_speed_mm) =
+    //             0.64 * (sensitivityFactor / 10) * (slope + intercept / input_speed_mm)
+    // Where "gain" is a multiplier applied to the input speed to produce the output speed:
+    //     output_speed(input_speed_mm) = input_speed_mm * gain(input_speed_mm)
+    //
+    // To put our function in the library's form, we substitute it into the function above:
+    //     output_speed(input_speed_mm) =
+    //             input_speed_mm * (0.64 * (sensitivityFactor / 10) *
+    //             (slope + 25.4 * intercept / input_speed_mm))
+    // then expand the brackets so that input_speed_mm cancels out for the intercept term:
+    //     gain(input_speed_mm) =
+    //             0.64 * (sensitivityFactor / 10) * slope * input_speed_mm +
+    //             0.64 * (sensitivityFactor / 10) * intercept
+    //
+    // This gives us the following parameters for the Gestures library function form:
+    //     a = 0
+    //     b = 0.64 * (sensitivityFactor / 10) * slope
+    //     c = 0.64 * (sensitivityFactor / 10) * intercept
+
+    double commonFactor = 0.64 * sensitivityFactors[sensitivity + 7] / 10;
+
+    size_t i = 0;
+    for (CurveSegment seg : segments) {
+        // The library's curve format consists of four doubles per segment:
+        // * maximum pointer speed for the segment (mm/s)
+        // * multiplier for the x² term (a.k.a. "a" or "sqr")
+        // * multiplier for the x term (a.k.a. "b" or "mul")
+        // * the intercept (a.k.a. "c" or "int")
+        // (see struct CurveSegment in the library's AccelFilterInterpreter)
+        output[i + 0] = seg.maxPointerSpeedMmPerS;
+        output[i + 1] = 0;
+        output[i + 2] = commonFactor * seg.slope;
+        output[i + 3] = commonFactor * seg.intercept;
+        i += 4;
+    }
+
+    return output;
+}
+
 short getMaxTouchCount(const InputDeviceContext& context) {
     if (context.hasScanCode(BTN_TOOL_QUINTTAP)) return 5;
     if (context.hasScanCode(BTN_TOOL_QUADTAP)) return 4;
@@ -147,10 +218,12 @@
         mGestureConverter.setOrientation(orientation);
     }
     if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCHPAD_SETTINGS)) {
-        // TODO(b/265798483): load an Android-specific acceleration curve instead of mapping to one
-        // of five ChromeOS curves.
-        const int pointerSensitivity = (config->touchpadPointerSpeed + 7) / 3 + 1;
-        mPropertyProvider.getProperty("Pointer Sensitivity").setIntValues({pointerSensitivity});
+        mPropertyProvider.getProperty("Use Custom Touchpad Pointer Accel Curve")
+                .setBoolValues({true});
+        GesturesProp accelCurveProp = mPropertyProvider.getProperty("Pointer Accel Curve");
+        accelCurveProp.setRealValues(
+                createAccelerationCurveForSensitivity(config->touchpadPointerSpeed,
+                                                      accelCurveProp.getCount()));
         mPropertyProvider.getProperty("Invert Scrolling")
                 .setBoolValues({config->touchpadNaturalScrollingEnabled});
         mPropertyProvider.getProperty("Tap Enable")
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index e71cdce..e4ba241 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -58,8 +58,23 @@
 static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
 static constexpr int32_t SECOND_DISPLAY_ID = 1;
 
+static constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
+static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+static constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP;
+static constexpr int32_t ACTION_HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER;
+static constexpr int32_t ACTION_HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT;
 static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE;
 static constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL;
+/**
+ * The POINTER_DOWN(0) is an unusual, but valid, action. It just means that the new pointer in the
+ * MotionEvent is at the index 0 rather than 1 (or later). That is, the pointer id=0 (which is at
+ * index 0) is the new pointer going down. The same pointer could have been placed at a different
+ * index, and the action would become POINTER_1_DOWN, 2, etc..; these would all be valid. In
+ * general, we try to place pointer id = 0 at the index 0. Of course, this is not possible if
+ * pointer id=0 leaves but the pointer id=1 remains.
+ */
+static constexpr int32_t POINTER_0_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 static constexpr int32_t POINTER_1_DOWN =
         AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 static constexpr int32_t POINTER_2_DOWN =
@@ -90,6 +105,8 @@
 static constexpr int expectedWallpaperFlags =
         AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
+using ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID;
+
 struct PointF {
     float x;
     float y;
@@ -145,6 +162,10 @@
     return arg.getDisplayId() == displayId;
 }
 
+MATCHER_P(WithDeviceId, deviceId, "InputEvent with specified deviceId") {
+    return arg.getDeviceId() == deviceId;
+}
+
 MATCHER_P(WithSource, source, "InputEvent with specified source") {
     *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
                      << inputEventSourceToString(arg.getSource());
@@ -163,6 +184,10 @@
     return arg.getX(/*pointerIndex=*/0) == x && arg.getY(/*pointerIndex=*/0) == y;
 }
 
+MATCHER_P(WithPointerCount, pointerCount, "MotionEvent with specified number of pointers") {
+    return arg.getPointerCount() == pointerCount;
+}
+
 MATCHER_P(WithPointers, pointers, "MotionEvent with specified pointers") {
     // Build a map for the received pointers, by pointer id
     std::map<int32_t /*pointerId*/, PointF> actualPointers;
@@ -1922,6 +1947,48 @@
 }
 
 /**
+ * Two fingers down on the window, and lift off the first finger.
+ * Next, cancel the gesture to the window by removing the window. Make sure that the CANCEL event
+ * contains a single pointer.
+ */
+TEST_F(InputDispatcherTest, CancelAfterPointer0Up) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    NotifyMotionArgs args;
+    // First touch pointer down on right window
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                           .build()));
+    // Second touch pointer down
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(110).y(100))
+                           .build()));
+    // First touch pointer lifts. The second one remains down
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(110).y(100))
+                           .build()));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+    window->consumeMotionEvent(WithMotionAction(POINTER_0_UP));
+
+    // Remove the window. The gesture should be canceled
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}});
+    const std::map<int32_t, PointF> expectedPointers{{1, PointF{110, 100}}};
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithPointers(expectedPointers)));
+}
+
+/**
  * Same test as WhenForegroundWindowDisappears_WallpaperTouchIsCanceled above,
  * with the following differences:
  * After ACTION_DOWN, Wallpaper window hangs up its channel, which forces the dispatcher to
@@ -2029,8 +2096,17 @@
     wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                             {100, 100}))
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .displayId(ADISPLAY_ID_DEFAULT)
+                                        .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                                        .pointer(PointerBuilder(/* id */ 1,
+                                                                AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                         .x(100)
+                                                         .y(100))
+                                        .build(),
+                                INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
     wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
@@ -2365,6 +2441,205 @@
 }
 
 /**
+ * Two windows: a window on the left and a window on the right.
+ * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains
+ * down. Then, on the left window, also place second touch pointer down.
+ * This test tries to reproduce a crash.
+ * In the buggy implementation, second pointer down on the left window would cause a crash.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceSplitTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}});
+
+    const int32_t touchDeviceId = 4;
+    const int32_t mouseDeviceId = 6;
+    NotifyMotionArgs args;
+
+    // Start hovering over the left window
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                           .deviceId(mouseDeviceId)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100))
+                           .build()));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+
+    // Mouse down on left window
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                           .deviceId(mouseDeviceId)
+                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100))
+                           .build()));
+
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                           .deviceId(mouseDeviceId)
+                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100))
+                           .build()));
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+    // First touch pointer down on right window
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
+                           .build()));
+    leftWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+
+    rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Second touch pointer down on left window
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
+                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                           .build()));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    // This MOVE event is not necessary (doesn't carry any new information), but it's there in the
+    // current implementation.
+    const std::map<int32_t, PointF> expectedPointers{{0, PointF{100, 100}}};
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithPointers(expectedPointers)));
+
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
+ * On a single window, use two different devices: mouse and touch.
+ * Touch happens first, with two pointers going down, and then the first pointer leaving.
+ * Mouse is clicked next, which causes the touch stream to be aborted with ACTION_CANCEL.
+ * Finally, a second touch pointer goes down again. Ensure the second touch pointer is ignored,
+ * because the mouse is currently down, and a POINTER_DOWN event from the touchscreen does not
+ * represent a new gesture.
+ */
+TEST_F(InputDispatcherTest, MixedTouchAndMouseWithPointerDown) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 400, 400));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    const int32_t touchDeviceId = 4;
+    const int32_t mouseDeviceId = 6;
+    NotifyMotionArgs args;
+
+    // First touch pointer down
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
+                           .build()));
+    // Second touch pointer down
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
+                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100))
+                           .build()));
+    // First touch pointer lifts. The second one remains down
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
+                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100))
+                           .build()));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+    window->consumeMotionEvent(WithMotionAction(POINTER_0_UP));
+
+    // Mouse down. The touch should be canceled
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                           .deviceId(mouseDeviceId)
+                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(320).y(100))
+                           .build()));
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId),
+                                     WithPointerCount(1u)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                           .deviceId(mouseDeviceId)
+                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(320).y(100))
+                           .build()));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+    // Second touch pointer down.
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
+                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100))
+                           .build()));
+    // The pointer_down event should be ignored
+    window->assertNoEvents();
+}
+
+/**
+ * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event cancels
+ * the injected event.
+ */
+TEST_F(InputDispatcherTest, UnfinishedInjectedEvent) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 400, 400));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    const int32_t touchDeviceId = 4;
+    NotifyMotionArgs args;
+    // Pretend a test injects an ACTION_DOWN mouse event, but forgets to lift up the touch after
+    // completion.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                        .deviceId(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(50)
+                                                         .y(50))
+                                        .build()));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(VIRTUAL_KEYBOARD_ID)));
+
+    // Now a real touch comes. Rather than crashing or dropping the real event, the injected pointer
+    // should be canceled and the new gesture should take over.
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100))
+                           .build()));
+
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(VIRTUAL_KEYBOARD_ID)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
  * This test is similar to the test above, but the sequence of injected events is different.
  *
  * Two windows: a window on the left and a window on the right.
@@ -2541,6 +2816,182 @@
 }
 
 /**
+ * A spy window above a window with no input channel.
+ * Start hovering with a stylus device, and then tap with it.
+ * Ensure spy window receives the entire sequence.
+ */
+TEST_F(InputDispatcherTest, StylusHoverAndDownNoInputChannel) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 200, 200));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setNoInputChannel(true);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+
+    NotifyMotionArgs args;
+
+    // Start hovering with stylus
+    mDispatcher->notifyMotion(
+            &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50))
+                             .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+    // Stop hovering
+    mDispatcher->notifyMotion(
+            &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50))
+                             .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+
+    // Stylus touches down
+    mDispatcher->notifyMotion(
+            &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50))
+                             .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Stylus goes up
+    mDispatcher->notifyMotion(
+            &(args = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS)
+                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50))
+                             .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_UP));
+
+    // Again hover
+    mDispatcher->notifyMotion(
+            &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50))
+                             .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+    // Stop hovering
+    mDispatcher->notifyMotion(
+            &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50))
+                             .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+
+    // No more events
+    spyWindow->assertNoEvents();
+    window->assertNoEvents();
+}
+
+/**
+ * Start hovering with a mouse, and then tap with a touch device. Pilfer the touch stream.
+ * Next, click with the mouse device. Both windows (spy and regular) should receive the new mouse
+ * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active.
+ * While the mouse is down, new move events from the touch device should be ignored.
+ */
+TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 200, 200));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+
+    const int32_t mouseDeviceId = 7;
+    const int32_t touchDeviceId = 4;
+    NotifyMotionArgs args;
+
+    // Hover a bit with mouse first
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                           .deviceId(mouseDeviceId)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100))
+                           .build()));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+
+    // Start touching
+    mDispatcher->notifyMotion(
+            &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                             .deviceId(touchDeviceId)
+                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                             .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    mDispatcher->notifyMotion(
+            &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                             .deviceId(touchDeviceId)
+                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(55).y(55))
+                             .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+    // Pilfer the stream
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken()));
+    window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+
+    mDispatcher->notifyMotion(
+            &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                             .deviceId(touchDeviceId)
+                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60))
+                             .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+    // Mouse down
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                           .deviceId(mouseDeviceId)
+                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100))
+                           .build()));
+
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                           .deviceId(mouseDeviceId)
+                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100))
+                           .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+    // Mouse move!
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                           .deviceId(mouseDeviceId)
+                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(110).y(110))
+                           .build()));
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+    // Touch move!
+    mDispatcher->notifyMotion(
+            &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                             .deviceId(touchDeviceId)
+                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(65).y(65))
+                             .build()));
+
+    // No more events
+    spyWindow->assertNoEvents();
+    window->assertNoEvents();
+}
+
+/**
  * On the display, have a single window, and also an area where there's no window.
  * First pointer touches the "no window" area of the screen. Second pointer touches the window.
  * Make sure that the window receives the second pointer, and first pointer is simply ignored.
@@ -2649,8 +3100,8 @@
     window1->assertNoEvents();
 
     // Now move the pointer on the first window
-    mDispatcher->notifyMotion(
-            &(args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}})));
+    mDispatcher->notifyMotion(&(
+            args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}, {150, 50}})));
     mDispatcher->waitForIdle();
     window1->consumeMotionEvent(WithDownTime(downTimeForWindow1));
 
@@ -2705,7 +3156,8 @@
                                                          .x(300)
                                                          .y(400))
                                         .build()));
-    windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    windowLeft->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    windowLeft->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher,
@@ -2750,7 +3202,6 @@
                                                          .x(900)
                                                          .y(400))
                                         .build()));
-    windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
     windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
 
     // No more events
@@ -2758,6 +3209,70 @@
     windowRight->assertNoEvents();
 }
 
+/**
+ * Put two fingers down (and don't release them) and click the mouse button.
+ * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the
+ * currently active gesture should be canceled, and the new one should proceed.
+ */
+TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 600, 800));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    const int32_t touchDeviceId = 4;
+    const int32_t mouseDeviceId = 6;
+    NotifyMotionArgs args;
+
+    // Two pointers down
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                           .build()));
+
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100))
+                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120))
+                           .build()));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+
+    // Inject a series of mouse events for a mouse click
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                           .deviceId(mouseDeviceId)
+                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(300).y(400))
+                           .build()));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId),
+                                     WithPointerCount(2u)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+                           .deviceId(mouseDeviceId)
+                           .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(300).y(400))
+                           .build()));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+    // Try to send more touch events while the mouse is down. Since it's a continuation of an
+    // already canceled gesture, it should be ignored.
+    mDispatcher->notifyMotion(&(
+            args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                           .deviceId(touchDeviceId)
+                           .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(101).y(101))
+                           .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(121).y(121))
+                           .build()));
+    window->assertNoEvents();
+}
+
 TEST_F(InputDispatcherTest, HoverWithSpyWindows) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
@@ -2940,7 +3455,8 @@
                                                          .x(300)
                                                          .y(400))
                                         .build()));
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher,
@@ -2984,7 +3500,7 @@
                                                          .x(300)
                                                          .y(400))
                                         .build()));
-    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+    window->assertNoEvents();
 }
 
 /**
@@ -3017,6 +3533,42 @@
 }
 
 /**
+ * If mouse is hovering when the touch goes down, the hovering should be stopped via HOVER_EXIT.
+ */
+TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    const int32_t mouseDeviceId = 7;
+    const int32_t touchDeviceId = 4;
+    NotifyMotionArgs args;
+
+    // Start hovering with the mouse
+    mDispatcher->notifyMotion(
+            &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                             .deviceId(mouseDeviceId)
+                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(10).y(10))
+                             .build()));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+
+    // Touch goes down
+    mDispatcher->notifyMotion(
+            &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                             .deviceId(touchDeviceId)
+                             .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                             .build()));
+
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
  * Inject a mouse hover event followed by a tap from touchscreen.
  * The tap causes a HOVER_EXIT event to be generated because the current event
  * stream's source has been switched.
@@ -4903,6 +5455,7 @@
 }
 
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseEventIdFromInputDispatcher) {
+    GTEST_SKIP() << "Flaky test (b/270393106)";
     sendAndConsumeKeyDown(/*deviceId=*/1);
     for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
         InputEvent* repeatEvent = mWindow->consume();
@@ -4913,6 +5466,7 @@
 }
 
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseUniqueEventId) {
+    GTEST_SKIP() << "Flaky test (b/270393106)";
     sendAndConsumeKeyDown(/*deviceId=*/1);
 
     std::unordered_set<int32_t> idSet;
@@ -5042,6 +5596,13 @@
     windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID);
     monitorInSecondary.consumeMotionDown(SECOND_DISPLAY_ID);
 
+    // Lift up the touch from the second display
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    windowInSecondary->consumeMotionUp(SECOND_DISPLAY_ID);
+    monitorInSecondary.consumeMotionUp(SECOND_DISPLAY_ID);
+
     // Test inject a non-pointer motion event.
     // If specific a display, it will dispatch to the focused window of particular display,
     // or it will dispatch to the focused window of focused display.
diff --git a/services/sensorservice/aidl/SensorManager.cpp b/services/sensorservice/aidl/SensorManager.cpp
index b7aecdf..08e00b4 100644
--- a/services/sensorservice/aidl/SensorManager.cpp
+++ b/services/sensorservice/aidl/SensorManager.cpp
@@ -59,6 +59,9 @@
     if (mPollThread.joinable()) {
         mPollThread.join();
     }
+
+    ::android::SensorManager::removeInstanceForPackage(
+            String16(ISensorManager::descriptor));
 }
 
 ndk::ScopedAStatus createDirectChannel(::android::SensorManager& manager, size_t size, int type,
diff --git a/services/sensorservice/hidl/SensorManager.cpp b/services/sensorservice/hidl/SensorManager.cpp
index f04712c..3d148e1 100644
--- a/services/sensorservice/hidl/SensorManager.cpp
+++ b/services/sensorservice/hidl/SensorManager.cpp
@@ -60,6 +60,9 @@
     if (mPollThread.joinable()) {
         mPollThread.join();
     }
+
+    ::android::SensorManager::removeInstanceForPackage(
+            String16(ISensorManager::descriptor));
 }
 
 // Methods from ::android::frameworks::sensorservice::V1_0::ISensorManager follow.
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index d94f5ca..433606a 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -790,7 +790,7 @@
 // transaction
 // ----------------------------------------------------------------------------
 
-uint32_t Layer::doTransaction(uint32_t flags, nsecs_t latchTime) {
+uint32_t Layer::doTransaction(uint32_t flags) {
     ATRACE_CALL();
 
     // TODO: This is unfortunate.
@@ -818,12 +818,12 @@
         mFlinger->mUpdateInputInfo = true;
     }
 
-    commitTransaction(mDrawingState, latchTime);
+    commitTransaction(mDrawingState);
 
     return flags;
 }
 
-void Layer::commitTransaction(State&, nsecs_t currentLatchTime) {
+void Layer::commitTransaction(State&) {
     // Set the present state for all bufferlessSurfaceFramesTX to Presented. The
     // bufferSurfaceFrameTX will be presented in latchBuffer.
     for (auto& [token, surfaceFrame] : mDrawingState.bufferlessSurfaceFramesTX) {
@@ -835,7 +835,6 @@
         }
     }
     mDrawingState.bufferlessSurfaceFramesTX.clear();
-    mLastLatchTime = currentLatchTime;
 }
 
 uint32_t Layer::clearTransactionFlags(uint32_t mask) {
@@ -1381,7 +1380,7 @@
     surfaceFrame->setAcquireFenceTime(acquireFenceTime);
     surfaceFrame->setPresentState(PresentState::Presented, mLastLatchTime);
     mFlinger->mFrameTimeline->addSurfaceFrame(surfaceFrame);
-    mLastLatchTime = currentLatchTime;
+    updateLastLatchTime(currentLatchTime);
 }
 
 std::shared_ptr<frametimeline::SurfaceFrame> Layer::createSurfaceFrameForTransaction(
@@ -4084,7 +4083,7 @@
     }
 }
 
-void Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds,
+bool Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds,
                                        TrustedPresentationListener const& listener) {
     bool hadTrustedPresentationListener = hasTrustedPresentationListener();
     mTrustedPresentationListener = listener;
@@ -4095,6 +4094,20 @@
     } else if (hadTrustedPresentationListener && !haveTrustedPresentationListener) {
         mFlinger->mNumTrustedPresentationListeners--;
     }
+
+    // Reset trusted presentation states to ensure we start the time again.
+    mEnteredTrustedPresentationStateTime = -1;
+    mLastReportedTrustedPresentationState = false;
+    mLastComputedTrustedPresentationState = false;
+
+    // If there's a new trusted presentation listener, the code needs to go through the composite
+    // path to ensure it recomutes the current state and invokes the TrustedPresentationListener if
+    // we're already in the requested state.
+    return haveTrustedPresentationListener;
+}
+
+void Layer::updateLastLatchTime(nsecs_t latchTime) {
+    mLastLatchTime = latchTime;
 }
 
 // ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 54f5fa7..4309aca 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -347,6 +347,7 @@
     void useSurfaceDamage();
     void useEmptyDamage();
     Region getVisibleRegion(const DisplayDevice*) const;
+    void updateLastLatchTime(nsecs_t latchtime);
 
     /*
      * isOpaque - true if this surface is opaque
@@ -627,7 +628,7 @@
      * doTransaction - process the transaction. This is a good place to figure
      * out which attributes of the surface have changed.
      */
-    virtual uint32_t doTransaction(uint32_t transactionFlags, nsecs_t currentLatchTime);
+    virtual uint32_t doTransaction(uint32_t transactionFlags);
 
     /*
      * Remove relative z for the layer if its relative parent is not part of the
@@ -761,7 +762,7 @@
     std::shared_ptr<frametimeline::SurfaceFrame> createSurfaceFrameForBuffer(
             const FrameTimelineInfo& info, nsecs_t queueTime, std::string debugName);
 
-    void setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds,
+    bool setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds,
                                     TrustedPresentationListener const& listener);
 
     // Creates a new handle each time, so we only expect
@@ -865,7 +866,7 @@
     void preparePerFrameCompositionState();
     void preparePerFrameBufferCompositionState();
     void preparePerFrameEffectsCompositionState();
-    virtual void commitTransaction(State& stateToCommit, nsecs_t currentLatchTime = 0);
+    virtual void commitTransaction(State& stateToCommit);
     void gatherBufferInfo();
     void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>&);
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index eb6d7e4..57661f1 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -238,7 +238,7 @@
 
 namespace impl {
 
-EventThread::EventThread(const char* name, scheduler::VsyncSchedule& vsyncSchedule,
+EventThread::EventThread(const char* name, std::shared_ptr<scheduler::VsyncSchedule> vsyncSchedule,
                          android::frametimeline::TokenManager* tokenManager,
                          ThrottleVsyncCallback throttleVsyncCallback,
                          GetVsyncPeriodFunction getVsyncPeriodFunction,
@@ -248,13 +248,8 @@
         mVsyncTracer(base::StringPrintf("VSYNC-%s", name), 0),
         mWorkDuration(base::StringPrintf("VsyncWorkDuration-%s", name), workDuration),
         mReadyDuration(readyDuration),
-        mVsyncSchedule(vsyncSchedule),
-        mVsyncRegistration(
-                vsyncSchedule.getDispatch(),
-                [this](nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) {
-                    onVsync(vsyncTime, wakeupTime, readyTime);
-                },
-                name),
+        mVsyncSchedule(std::move(vsyncSchedule)),
+        mVsyncRegistration(mVsyncSchedule->getDispatch(), createDispatchCallback(), name),
         mTokenManager(tokenManager),
         mThrottleVsyncCallback(std::move(throttleVsyncCallback)),
         mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)) {
@@ -375,7 +370,7 @@
     vsyncEventData.frameInterval = frameInterval;
     const auto [presentTime, deadline] = [&]() -> std::pair<nsecs_t, nsecs_t> {
         std::lock_guard<std::mutex> lock(mMutex);
-        const auto vsyncTime = mVsyncSchedule.getTracker().nextAnticipatedVSyncTimeFrom(
+        const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom(
                 systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
         return {vsyncTime, vsyncTime - mReadyDuration.count()};
     }();
@@ -384,23 +379,13 @@
     return vsyncEventData;
 }
 
-void EventThread::onScreenReleased() {
+void EventThread::enableSyntheticVsync(bool enable) {
     std::lock_guard<std::mutex> lock(mMutex);
-    if (!mVSyncState || mVSyncState->synthetic) {
+    if (!mVSyncState || mVSyncState->synthetic == enable) {
         return;
     }
 
-    mVSyncState->synthetic = true;
-    mCondition.notify_all();
-}
-
-void EventThread::onScreenAcquired() {
-    std::lock_guard<std::mutex> lock(mMutex);
-    if (!mVSyncState || !mVSyncState->synthetic) {
-        return;
-    }
-
-    mVSyncState->synthetic = false;
+    mVSyncState->synthetic = enable;
     mCondition.notify_all();
 }
 
@@ -543,7 +528,7 @@
     const auto throttleVsync = [&] {
         const auto& vsyncData = event.vsync.vsyncData;
         if (connection->frameRate.isValid()) {
-            return !mVsyncSchedule.getTracker()
+            return !mVsyncSchedule->getTracker()
                             .isVSyncInPhase(vsyncData.preferredExpectedPresentationTime(),
                                             connection->frameRate);
         }
@@ -706,6 +691,26 @@
     }
 }
 
+void EventThread::onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule> schedule) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    const bool reschedule = mVsyncRegistration.cancel() == scheduler::CancelResult::Cancelled;
+    mVsyncSchedule = std::move(schedule);
+    mVsyncRegistration =
+            scheduler::VSyncCallbackRegistration(mVsyncSchedule->getDispatch(),
+                                                 createDispatchCallback(), mThreadName);
+    if (reschedule) {
+        mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(),
+                                     .readyDuration = mReadyDuration.count(),
+                                     .earliestVsync = mLastVsyncCallbackTime.ns()});
+    }
+}
+
+scheduler::VSyncDispatch::Callback EventThread::createDispatchCallback() {
+    return [this](nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) {
+        onVsync(vsyncTime, wakeupTime, readyTime);
+    };
+}
+
 } // namespace impl
 } // namespace android
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 347dc4a..87e20a0 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -106,11 +106,8 @@
     virtual sp<EventThreadConnection> createEventConnection(
             ResyncCallback, EventRegistrationFlags eventRegistration = {}) const = 0;
 
-    // called before the screen is turned off from main thread
-    virtual void onScreenReleased() = 0;
-
-    // called after the screen is turned on from main thread
-    virtual void onScreenAcquired() = 0;
+    // Feed clients with fake VSYNC, e.g. while the display is off.
+    virtual void enableSyntheticVsync(bool) = 0;
 
     virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0;
 
@@ -136,6 +133,8 @@
 
     // Retrieves the number of event connections tracked by this EventThread.
     virtual size_t getEventThreadConnectionCount() = 0;
+
+    virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0;
 };
 
 namespace impl {
@@ -145,8 +144,8 @@
     using ThrottleVsyncCallback = std::function<bool(nsecs_t, uid_t)>;
     using GetVsyncPeriodFunction = std::function<nsecs_t(uid_t)>;
 
-    EventThread(const char* name, scheduler::VsyncSchedule&, frametimeline::TokenManager*,
-                ThrottleVsyncCallback, GetVsyncPeriodFunction,
+    EventThread(const char* name, std::shared_ptr<scheduler::VsyncSchedule>,
+                frametimeline::TokenManager*, ThrottleVsyncCallback, GetVsyncPeriodFunction,
                 std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration);
     ~EventThread();
 
@@ -159,11 +158,7 @@
     VsyncEventData getLatestVsyncEventData(
             const sp<EventThreadConnection>& connection) const override;
 
-    // called before the screen is turned off from main thread
-    void onScreenReleased() override;
-
-    // called after the screen is turned on from main thread
-    void onScreenAcquired() override;
+    void enableSyntheticVsync(bool) override;
 
     void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override;
 
@@ -179,6 +174,8 @@
 
     size_t getEventThreadConnectionCount() override;
 
+    void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) override;
+
 private:
     friend EventThreadTest;
 
@@ -202,11 +199,13 @@
                                nsecs_t timestamp, nsecs_t preferredExpectedPresentationTime,
                                nsecs_t preferredDeadlineTimestamp) const;
 
+    scheduler::VSyncDispatch::Callback createDispatchCallback();
+
     const char* const mThreadName;
     TracedOrdinal<int> mVsyncTracer;
     TracedOrdinal<std::chrono::nanoseconds> mWorkDuration GUARDED_BY(mMutex);
     std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex);
-    scheduler::VsyncSchedule& mVsyncSchedule;
+    std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
     TimePoint mLastVsyncCallbackTime GUARDED_BY(mMutex) = TimePoint::now();
     scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex);
     frametimeline::TokenManager* const mTokenManager;
diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
index c4de749..92c2189 100644
--- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h
+++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
@@ -18,12 +18,14 @@
 
 #include <vector>
 
+#include <ui/DisplayId.h>
+
 #include "Display/DisplayModeRequest.h"
 
 namespace android::scheduler {
 
 struct ISchedulerCallback {
-    virtual void setVsyncEnabled(bool) = 0;
+    virtual void setVsyncEnabled(PhysicalDisplayId, bool) = 0;
     virtual void requestDisplayModes(std::vector<display::DisplayModeRequest>) = 0;
     virtual void kernelTimerChanged(bool expired) = 0;
     virtual void triggerOnFrameRateOverridesChanged() = 0;
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index dec8f59..7457b84 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -75,19 +75,36 @@
     mHandler->dispatchFrame(vsyncId, expectedVsyncTime);
 }
 
-void MessageQueue::initVsync(scheduler::VSyncDispatch& dispatch,
+void MessageQueue::initVsync(std::shared_ptr<scheduler::VSyncDispatch> dispatch,
                              frametimeline::TokenManager& tokenManager,
                              std::chrono::nanoseconds workDuration) {
     std::lock_guard lock(mVsync.mutex);
     mVsync.workDuration = workDuration;
     mVsync.tokenManager = &tokenManager;
+    onNewVsyncScheduleLocked(std::move(dispatch));
+}
+
+void MessageQueue::onNewVsyncSchedule(std::shared_ptr<scheduler::VSyncDispatch> dispatch) {
+    std::lock_guard lock(mVsync.mutex);
+    onNewVsyncScheduleLocked(std::move(dispatch));
+}
+
+void MessageQueue::onNewVsyncScheduleLocked(std::shared_ptr<scheduler::VSyncDispatch> dispatch) {
+    const bool reschedule = mVsync.registration &&
+            mVsync.registration->cancel() == scheduler::CancelResult::Cancelled;
     mVsync.registration = std::make_unique<
-            scheduler::VSyncCallbackRegistration>(dispatch,
+            scheduler::VSyncCallbackRegistration>(std::move(dispatch),
                                                   std::bind(&MessageQueue::vsyncCallback, this,
                                                             std::placeholders::_1,
                                                             std::placeholders::_2,
                                                             std::placeholders::_3),
                                                   "sf");
+    if (reschedule) {
+        mVsync.scheduledFrameTime =
+                mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
+                                               .readyDuration = 0,
+                                               .earliestVsync = mVsync.lastCallbackTime.ns()});
+    }
 }
 
 void MessageQueue::destroyVsync() {
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index 0d59337..9c9b2f3 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -65,7 +65,7 @@
 public:
     virtual ~MessageQueue() = default;
 
-    virtual void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&,
+    virtual void initVsync(std::shared_ptr<scheduler::VSyncDispatch>, frametimeline::TokenManager&,
                            std::chrono::nanoseconds workDuration) = 0;
     virtual void destroyVsync() = 0;
     virtual void setDuration(std::chrono::nanoseconds workDuration) = 0;
@@ -106,6 +106,8 @@
 
     void vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime);
 
+    void onNewVsyncSchedule(std::shared_ptr<scheduler::VSyncDispatch>) EXCLUDES(mVsync.mutex);
+
 private:
     virtual void onFrameSignal(ICompositor&, VsyncId, TimePoint expectedVsyncTime) = 0;
 
@@ -127,10 +129,12 @@
 
     Vsync mVsync;
 
+    void onNewVsyncScheduleLocked(std::shared_ptr<scheduler::VSyncDispatch>) REQUIRES(mVsync.mutex);
+
 public:
     explicit MessageQueue(ICompositor&);
 
-    void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&,
+    void initVsync(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/OneShotTimer.h b/services/surfaceflinger/Scheduler/OneShotTimer.h
index f95646c..02e8719 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.h
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.h
@@ -40,7 +40,7 @@
 
     OneShotTimer(std::string name, const Interval& interval, const ResetCallback& resetCallback,
                  const TimeoutCallback& timeoutCallback,
-                 std::unique_ptr<Clock> clock = std::make_unique<SteadyClock>());
+                 std::unique_ptr<android::Clock> clock = std::make_unique<SteadyClock>());
     ~OneShotTimer();
 
     Duration interval() const { return mInterval; }
@@ -82,7 +82,7 @@
     std::thread mThread;
 
     // Clock object for the timer. Mocked in unit tests.
-    std::unique_ptr<Clock> mClock;
+    std::unique_ptr<android::Clock> mClock;
 
     // Semaphore to keep mThread synchronized.
     sem_t mSemaphore;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 4f5842a..5052e6e 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -32,7 +32,6 @@
 #include <scheduler/Seamlessness.h>
 
 #include "DisplayHardware/DisplayMode.h"
-#include "DisplayHardware/HWComposer.h"
 #include "Scheduler/OneShotTimer.h"
 #include "Scheduler/StrongTyping.h"
 #include "ThreadContext.h"
@@ -297,6 +296,8 @@
     RefreshRateSelector(const RefreshRateSelector&) = delete;
     RefreshRateSelector& operator=(const RefreshRateSelector&) = delete;
 
+    const DisplayModes& displayModes() const { return mDisplayModes; }
+
     // Returns whether switching modes (refresh rate or resolution) is possible.
     // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only
     //  differ in resolution. Once Config::FrameRateOverride::Enabled becomes the default,
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index dab01ba..064f853 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -114,10 +114,18 @@
 }
 
 void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
+    registerDisplayInternal(displayId, std::move(selectorPtr),
+                            std::make_shared<VsyncSchedule>(displayId, mFeatures));
+}
+
+void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId,
+                                        RefreshRateSelectorPtr selectorPtr,
+                                        std::shared_ptr<VsyncSchedule> vsyncSchedule) {
     demoteLeaderDisplay();
 
     std::scoped_lock lock(mDisplayLock);
     mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr));
+    mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule));
 
     promoteLeaderDisplay();
 }
@@ -127,6 +135,7 @@
 
     std::scoped_lock lock(mDisplayLock);
     mRefreshRateSelectors.erase(displayId);
+    mVsyncSchedules.erase(displayId);
 
     // Do not allow removing the final display. Code in the scheduler expects
     // there to be at least one display. (This may be relaxed in the future with
@@ -154,10 +163,6 @@
     compositor.sample();
 }
 
-void Scheduler::createVsyncSchedule(FeatureFlags features) {
-    mVsyncSchedule = std::make_unique<VsyncSchedule>(features);
-}
-
 std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const {
     const bool supportsFrameRateOverrideByContent =
             leaderSelectorPtr()->supportsAppFrameRateOverrideByContent();
@@ -172,11 +177,11 @@
     }
 
     ATRACE_FORMAT("%s uid: %d frameRate: %s", __func__, uid, to_string(*frameRate).c_str());
-    return mVsyncSchedule->getTracker().isVSyncInPhase(expectedVsyncTimestamp.ns(), *frameRate);
+    return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTimestamp.ns(), *frameRate);
 }
 
 bool Scheduler::isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const {
-    return mVsyncSchedule->getTracker().isVSyncInPhase(timePoint.ns(), frameRate);
+    return getVsyncSchedule()->getTracker().isVSyncInPhase(timePoint.ns(), frameRate);
 }
 
 impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const {
@@ -188,7 +193,8 @@
 impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const {
     return [this](uid_t uid) {
         const Fps refreshRate = leaderSelectorPtr()->getActiveMode().fps;
-        const nsecs_t currentPeriod = mVsyncSchedule->period().ns() ?: refreshRate.getPeriodNsecs();
+        const nsecs_t currentPeriod =
+                getVsyncSchedule()->period().ns() ?: refreshRate.getPeriodNsecs();
 
         const auto frameRate = getFrameRateOverride(uid);
         if (!frameRate.has_value()) {
@@ -208,7 +214,7 @@
                                               std::chrono::nanoseconds workDuration,
                                               std::chrono::nanoseconds readyDuration) {
     auto eventThread = std::make_unique<impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf",
-                                                           *mVsyncSchedule, tokenManager,
+                                                           getVsyncSchedule(), tokenManager,
                                                            makeThrottleVsyncCallback(),
                                                            makeGetVsyncPeriodFunction(),
                                                            workDuration, readyDuration);
@@ -265,24 +271,16 @@
     thread->onHotplugReceived(displayId, connected);
 }
 
-void Scheduler::onScreenAcquired(ConnectionHandle handle) {
+void Scheduler::enableSyntheticVsync(bool enable) {
+    // TODO(b/241285945): Remove connection handles.
+    const ConnectionHandle handle = mAppConnectionHandle;
     android::EventThread* thread;
     {
         std::lock_guard<std::mutex> lock(mConnectionsLock);
         RETURN_IF_INVALID_HANDLE(handle);
         thread = mConnections[handle].thread.get();
     }
-    thread->onScreenAcquired();
-}
-
-void Scheduler::onScreenReleased(ConnectionHandle handle) {
-    android::EventThread* thread;
-    {
-        std::lock_guard<std::mutex> lock(mConnectionsLock);
-        RETURN_IF_INVALID_HANDLE(handle);
-        thread = mConnections[handle].thread.get();
-    }
-    thread->onScreenReleased();
+    thread->enableSyntheticVsync(enable);
 }
 
 void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) {
@@ -393,32 +391,59 @@
     setDuration(config.sfWorkDuration);
 }
 
-void Scheduler::enableHardwareVsync() {
-    mVsyncSchedule->enableHardwareVsync(mSchedulerCallback);
+void Scheduler::enableHardwareVsync(PhysicalDisplayId id) {
+    auto schedule = getVsyncSchedule(id);
+    schedule->enableHardwareVsync(mSchedulerCallback);
 }
 
-void Scheduler::disableHardwareVsync(bool disallow) {
-    mVsyncSchedule->disableHardwareVsync(mSchedulerCallback, disallow);
+void Scheduler::disableHardwareVsync(PhysicalDisplayId id, bool disallow) {
+    auto schedule = getVsyncSchedule(id);
+    schedule->disableHardwareVsync(mSchedulerCallback, disallow);
 }
 
-void Scheduler::resyncToHardwareVsync(bool allowToEnable, Fps refreshRate) {
-    if (mVsyncSchedule->isHardwareVsyncAllowed(allowToEnable) && refreshRate.isValid()) {
-        mVsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod());
+void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) {
+    std::scoped_lock lock(mDisplayLock);
+    ftl::FakeGuard guard(kMainThreadContext);
+
+    for (const auto& [id, _] : mRefreshRateSelectors) {
+        resyncToHardwareVsyncLocked(id, allowToEnable);
     }
 }
 
-void Scheduler::setRenderRate(Fps renderFrameRate) {
-    const auto mode = leaderSelectorPtr()->getActiveMode();
+void Scheduler::resyncToHardwareVsyncLocked(PhysicalDisplayId id, bool allowToEnable,
+                                            std::optional<Fps> refreshRate) {
+    auto schedule = getVsyncScheduleLocked(id);
+    if (schedule->isHardwareVsyncAllowed(allowToEnable)) {
+        if (!refreshRate) {
+            auto selectorPtr = mRefreshRateSelectors.get(id);
+            LOG_ALWAYS_FATAL_IF(!selectorPtr);
+            refreshRate = selectorPtr->get()->getActiveMode().modePtr->getFps();
+        }
+        if (refreshRate->isValid()) {
+            schedule->startPeriodTransition(mSchedulerCallback, refreshRate->getPeriod(),
+                                            false /* force */);
+        }
+    }
+}
+
+void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) {
+    std::scoped_lock lock(mDisplayLock);
+    ftl::FakeGuard guard(kMainThreadContext);
+
+    auto selectorPtr = mRefreshRateSelectors.get(id);
+    LOG_ALWAYS_FATAL_IF(!selectorPtr);
+    const auto mode = selectorPtr->get()->getActiveMode();
 
     using fps_approx_ops::operator!=;
     LOG_ALWAYS_FATAL_IF(renderFrameRate != mode.fps,
-                        "Mismatch in render frame rates. Selector: %s, Scheduler: %s",
-                        to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str());
+                        "Mismatch in render frame rates. Selector: %s, Scheduler: %s, Display: "
+                        "%" PRIu64,
+                        to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str(), id.value);
 
     ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
           to_string(mode.modePtr->getFps()).c_str());
 
-    mVsyncSchedule->getTracker().setRenderRate(renderFrameRate);
+    getVsyncScheduleLocked(id)->getTracker().setRenderRate(renderFrameRate);
 }
 
 void Scheduler::resync() {
@@ -428,24 +453,26 @@
     const nsecs_t last = mLastResyncTime.exchange(now);
 
     if (now - last > kIgnoreDelay) {
-        const auto refreshRate = leaderSelectorPtr()->getActiveMode().modePtr->getFps();
-        resyncToHardwareVsync(false, refreshRate);
+        resyncAllToHardwareVsync(false /* allowToEnable */);
     }
 }
 
-bool Scheduler::addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriodIn) {
+bool Scheduler::addResyncSample(PhysicalDisplayId id, nsecs_t timestamp,
+                                std::optional<nsecs_t> hwcVsyncPeriodIn) {
     const auto hwcVsyncPeriod = ftl::Optional(hwcVsyncPeriodIn).transform([](nsecs_t nanos) {
         return Period::fromNs(nanos);
     });
-    return mVsyncSchedule->addResyncSample(mSchedulerCallback, TimePoint::fromNs(timestamp),
-                                           hwcVsyncPeriod);
+    return getVsyncSchedule(id)->addResyncSample(mSchedulerCallback, TimePoint::fromNs(timestamp),
+                                                 hwcVsyncPeriod);
 }
 
-void Scheduler::addPresentFence(std::shared_ptr<FenceTime> fence) {
-    if (mVsyncSchedule->getController().addPresentFence(std::move(fence))) {
-        enableHardwareVsync();
+void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr<FenceTime> fence) {
+    auto schedule = getVsyncSchedule(id);
+    const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence));
+    if (needMoreSignals) {
+        schedule->enableHardwareVsync(mSchedulerCallback);
     } else {
-        disableHardwareVsync(false);
+        schedule->disableHardwareVsync(mSchedulerCallback, false /* disallow */);
     }
 }
 
@@ -497,12 +524,22 @@
     }
 }
 
-void Scheduler::setDisplayPowerMode(hal::PowerMode powerMode) {
-    {
+void Scheduler::setDisplayPowerMode(PhysicalDisplayId id, hal::PowerMode powerMode) {
+    const bool isLeader = [this, id]() REQUIRES(kMainThreadContext) {
+        ftl::FakeGuard guard(mDisplayLock);
+        return id == mLeaderDisplayId;
+    }();
+    if (isLeader) {
+        // TODO (b/255657128): This needs to be handled per display.
         std::lock_guard<std::mutex> lock(mPolicyLock);
         mPolicy.displayPowerMode = powerMode;
     }
-    mVsyncSchedule->getController().setDisplayPowerMode(powerMode);
+    {
+        std::scoped_lock lock(mDisplayLock);
+        auto vsyncSchedule = getVsyncScheduleLocked(id);
+        vsyncSchedule->getController().setDisplayPowerMode(powerMode);
+    }
+    if (!isLeader) return;
 
     if (mDisplayPowerTimer) {
         mDisplayPowerTimer->reset();
@@ -513,6 +550,24 @@
     mLayerHistory.clear();
 }
 
+std::shared_ptr<const VsyncSchedule> Scheduler::getVsyncSchedule(
+        std::optional<PhysicalDisplayId> idOpt) const {
+    std::scoped_lock lock(mDisplayLock);
+    return getVsyncScheduleLocked(idOpt);
+}
+
+std::shared_ptr<const VsyncSchedule> Scheduler::getVsyncScheduleLocked(
+        std::optional<PhysicalDisplayId> idOpt) const {
+    ftl::FakeGuard guard(kMainThreadContext);
+    if (!idOpt) {
+        LOG_ALWAYS_FATAL_IF(!mLeaderDisplayId, "Missing a leader!");
+        idOpt = mLeaderDisplayId;
+    }
+    auto scheduleOpt = mVsyncSchedules.get(*idOpt);
+    LOG_ALWAYS_FATAL_IF(!scheduleOpt);
+    return std::const_pointer_cast<const VsyncSchedule>(scheduleOpt->get());
+}
+
 void Scheduler::kernelIdleTimerCallback(TimerState state) {
     ATRACE_INT("ExpiredKernelIdleTimer", static_cast<int>(state));
 
@@ -527,12 +582,17 @@
         // If we're not in performance mode then the kernel timer shouldn't do
         // anything, as the refresh rate during DPU power collapse will be the
         // same.
-        resyncToHardwareVsync(true /* makeAvailable */, refreshRate);
+        resyncAllToHardwareVsync(true /* allowToEnable */);
     } else if (state == TimerState::Expired && refreshRate <= FPS_THRESHOLD_FOR_KERNEL_TIMER) {
         // Disable HW VSYNC if the timer expired, as we don't need it enabled if
         // we're not pushing frames, and if we're in PERFORMANCE mode then we'll
         // need to update the VsyncController model anyway.
-        disableHardwareVsync(false /* makeUnavailable */);
+        std::scoped_lock lock(mDisplayLock);
+        ftl::FakeGuard guard(kMainThreadContext);
+        constexpr bool disallow = false;
+        for (auto& [_, schedule] : mVsyncSchedules) {
+            schedule->disableHardwareVsync(mSchedulerCallback, disallow);
+        }
     }
 
     mSchedulerCallback.kernelTimerChanged(state == TimerState::Expired);
@@ -589,7 +649,20 @@
 }
 
 void Scheduler::dumpVsync(std::string& out) const {
-    mVsyncSchedule->dump(out);
+    std::scoped_lock lock(mDisplayLock);
+    ftl::FakeGuard guard(kMainThreadContext);
+    if (mLeaderDisplayId) {
+        base::StringAppendF(&out, "VsyncSchedule for leader %s:\n",
+                            to_string(*mLeaderDisplayId).c_str());
+        getVsyncScheduleLocked()->dump(out);
+    }
+    for (auto& [id, vsyncSchedule] : mVsyncSchedules) {
+        if (id == mLeaderDisplayId) {
+            continue;
+        }
+        base::StringAppendF(&out, "VsyncSchedule for follower %s:\n", to_string(id).c_str());
+        vsyncSchedule->dump(out);
+    }
 }
 
 bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps displayRefreshRate) {
@@ -609,6 +682,7 @@
     mLeaderDisplayId = leaderIdOpt.value_or(mRefreshRateSelectors.begin()->first);
     ALOGI("Display %s is the leader", to_string(*mLeaderDisplayId).c_str());
 
+    auto vsyncSchedule = getVsyncScheduleLocked(*mLeaderDisplayId);
     if (const auto leaderPtr = leaderSelectorPtrLocked()) {
         leaderPtr->setIdleTimerCallbacks(
                 {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); },
@@ -618,6 +692,18 @@
                                     [this] { kernelIdleTimerCallback(TimerState::Expired); }}});
 
         leaderPtr->startIdleTimer();
+
+        const Fps refreshRate = leaderPtr->getActiveMode().modePtr->getFps();
+        vsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(),
+                                             true /* force */);
+    }
+
+    onNewVsyncSchedule(vsyncSchedule->getDispatch());
+    {
+        std::lock_guard<std::mutex> lock(mConnectionsLock);
+        for (auto& [_, connection] : mConnections) {
+            connection.thread->onNewVsyncSchedule(vsyncSchedule);
+        }
     }
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index a340919..7374054 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -114,8 +114,6 @@
 
     void run();
 
-    void createVsyncSchedule(FeatureFlags);
-
     using Impl::initVsync;
 
     using Impl::getScheduledFrameTime;
@@ -157,8 +155,8 @@
     void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected);
     void onPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&) EXCLUDES(mPolicyLock);
     void onNonPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&);
-    void onScreenAcquired(ConnectionHandle);
-    void onScreenReleased(ConnectionHandle);
+
+    void enableSyntheticVsync(bool = true) REQUIRES(kMainThreadContext);
 
     void onFrameRateOverridesChanged(ConnectionHandle, PhysicalDisplayId)
             EXCLUDES(mConnectionsLock);
@@ -169,9 +167,21 @@
 
     const VsyncModulator& vsyncModulator() const { return *mVsyncModulator; }
 
+    // In some cases, we should only modulate for the leader display. In those
+    // cases, the caller should pass in the relevant display, and the method
+    // will no-op if it's not the leader. Other cases are not specific to a
+    // display.
     template <typename... Args,
               typename Handler = std::optional<VsyncConfig> (VsyncModulator::*)(Args...)>
-    void modulateVsync(Handler handler, Args... args) {
+    void modulateVsync(std::optional<PhysicalDisplayId> id, Handler handler, Args... args) {
+        if (id) {
+            std::scoped_lock lock(mDisplayLock);
+            ftl::FakeGuard guard(kMainThreadContext);
+            if (id != mLeaderDisplayId) {
+                return;
+            }
+        }
+
         if (const auto config = (*mVsyncModulator.*handler)(args...)) {
             setVsyncConfig(*config, getLeaderVsyncPeriod());
         }
@@ -180,24 +190,32 @@
     void setVsyncConfigSet(const VsyncConfigSet&, Period vsyncPeriod);
 
     // Sets the render rate for the scheduler to run at.
-    void setRenderRate(Fps);
+    void setRenderRate(PhysicalDisplayId, Fps);
 
-    void enableHardwareVsync();
-    void disableHardwareVsync(bool disallow);
+    void enableHardwareVsync(PhysicalDisplayId);
+    void disableHardwareVsync(PhysicalDisplayId, bool disallow);
 
     // Resyncs the scheduler to hardware vsync.
     // If allowToEnable is true, then hardware vsync will be turned on.
     // Otherwise, if hardware vsync is not already enabled then this method will
     // no-op.
-    void resyncToHardwareVsync(bool allowToEnable, Fps refreshRate);
+    // If refreshRate is nullopt, use the existing refresh rate of the display.
+    void resyncToHardwareVsync(PhysicalDisplayId id, bool allowToEnable,
+                               std::optional<Fps> refreshRate = std::nullopt)
+            EXCLUDES(mDisplayLock) {
+        std::scoped_lock lock(mDisplayLock);
+        ftl::FakeGuard guard(kMainThreadContext);
+        resyncToHardwareVsyncLocked(id, allowToEnable, refreshRate);
+    }
     void resync() EXCLUDES(mDisplayLock);
     void forceNextResync() { mLastResyncTime = 0; }
 
     // Passes a vsync sample to VsyncController. Returns true if
     // VsyncController detected that the vsync period changed and false
     // otherwise.
-    bool addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod);
-    void addPresentFence(std::shared_ptr<FenceTime>);
+    bool addResyncSample(PhysicalDisplayId, nsecs_t timestamp,
+                         std::optional<nsecs_t> hwcVsyncPeriod);
+    void addPresentFence(PhysicalDisplayId, std::shared_ptr<FenceTime>) EXCLUDES(mDisplayLock);
 
     // Layers are registered on creation, and unregistered when the weak reference expires.
     void registerLayer(Layer*);
@@ -215,20 +233,26 @@
     // Indicates that touch interaction is taking place.
     void onTouchHint();
 
-    void setDisplayPowerMode(hal::PowerMode powerMode);
+    void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode powerMode)
+            REQUIRES(kMainThreadContext);
 
-    VsyncSchedule& getVsyncSchedule() { return *mVsyncSchedule; }
+    std::shared_ptr<const VsyncSchedule> getVsyncSchedule(
+            std::optional<PhysicalDisplayId> idOpt = std::nullopt) const EXCLUDES(mDisplayLock);
+    std::shared_ptr<VsyncSchedule> getVsyncSchedule(
+            std::optional<PhysicalDisplayId> idOpt = std::nullopt) EXCLUDES(mDisplayLock) {
+        return std::const_pointer_cast<VsyncSchedule>(
+                static_cast<const Scheduler*>(this)->getVsyncSchedule(idOpt));
+    }
 
     // Returns true if a given vsync timestamp is considered valid vsync
     // for a given uid
     bool isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const;
 
-    // Checks if a vsync timestamp is in phase for a frame rate
-    bool isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const;
+    bool isVsyncInPhase(TimePoint expectedVsyncTime, Fps frameRate) const;
 
     void dump(utils::Dumper&) const;
     void dump(ConnectionHandle, std::string&) const;
-    void dumpVsync(std::string&) const;
+    void dumpVsync(std::string&) const EXCLUDES(mDisplayLock);
 
     // Returns the preferred refresh rate and frame rate for the leader display.
     FrameRateMode getPreferredDisplayMode();
@@ -288,6 +312,10 @@
     void touchTimerCallback(TimerState);
     void displayPowerTimerCallback(TimerState);
 
+    void resyncToHardwareVsyncLocked(PhysicalDisplayId, bool allowToEnable,
+                                     std::optional<Fps> refreshRate = std::nullopt)
+            REQUIRES(kMainThreadContext, mDisplayLock);
+    void resyncAllToHardwareVsync(bool allowToEnable) EXCLUDES(mDisplayLock);
     void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod);
 
     // Chooses a leader among the registered displays, unless `leaderIdOpt` is specified. The new
@@ -299,6 +327,10 @@
     // caller on the main thread to avoid deadlock, since the timer thread locks it before exit.
     void demoteLeaderDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock);
 
+    void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr,
+                                 std::shared_ptr<VsyncSchedule>) REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock);
+
     struct Policy;
 
     // Sets the S state of the policy to the T value under mPolicyLock, and chooses a display mode
@@ -355,7 +387,6 @@
     std::atomic<nsecs_t> mLastResyncTime = 0;
 
     const FeatureFlags mFeatures;
-    std::unique_ptr<VsyncSchedule> mVsyncSchedule;
 
     // Shifts the VSYNC phase during certain transactions and refresh rate changes.
     const sp<VsyncModulator> mVsyncModulator;
@@ -380,6 +411,10 @@
     display::PhysicalDisplayMap<PhysicalDisplayId, RefreshRateSelectorPtr> mRefreshRateSelectors
             GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext);
 
+    // TODO (b/266715559): Store in the same map as mRefreshRateSelectors.
+    display::PhysicalDisplayMap<PhysicalDisplayId, std::shared_ptr<VsyncSchedule>> mVsyncSchedules
+            GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext);
+
     ftl::Optional<PhysicalDisplayId> mLeaderDisplayId GUARDED_BY(mDisplayLock)
             GUARDED_BY(kMainThreadContext);
 
@@ -399,6 +434,14 @@
                 .value_or(std::cref(noLeader));
     }
 
+    std::shared_ptr<const VsyncSchedule> getVsyncScheduleLocked(
+            std::optional<PhysicalDisplayId> idOpt = std::nullopt) const REQUIRES(mDisplayLock);
+    std::shared_ptr<VsyncSchedule> getVsyncScheduleLocked(
+            std::optional<PhysicalDisplayId> idOpt = std::nullopt) REQUIRES(mDisplayLock) {
+        return std::const_pointer_cast<VsyncSchedule>(
+                static_cast<const Scheduler*>(this)->getVsyncScheduleLocked(idOpt));
+    }
+
     struct Policy {
         // Policy for choosing the display mode.
         LayerHistory::Summary contentRequirements;
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h
index 9520131..77875e3 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatch.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h
@@ -161,7 +161,8 @@
  */
 class VSyncCallbackRegistration {
 public:
-    VSyncCallbackRegistration(VSyncDispatch&, VSyncDispatch::Callback, std::string callbackName);
+    VSyncCallbackRegistration(std::shared_ptr<VSyncDispatch>, VSyncDispatch::Callback,
+                              std::string callbackName);
     ~VSyncCallbackRegistration();
 
     VSyncCallbackRegistration(VSyncCallbackRegistration&&);
@@ -177,7 +178,7 @@
     CancelResult cancel();
 
 private:
-    std::reference_wrapper<VSyncDispatch> mDispatch;
+    std::shared_ptr<VSyncDispatch> mDispatch;
     VSyncDispatch::CallbackToken mToken;
     bool mValidToken;
 };
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 73d52cf..26389eb 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -215,10 +215,10 @@
 }
 
 VSyncDispatchTimerQueue::VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper> tk,
-                                                 VSyncTracker& tracker, nsecs_t timerSlack,
-                                                 nsecs_t minVsyncDistance)
+                                                 VsyncSchedule::TrackerPtr tracker,
+                                                 nsecs_t timerSlack, nsecs_t minVsyncDistance)
       : mTimeKeeper(std::move(tk)),
-        mTracker(tracker),
+        mTracker(std::move(tracker)),
         mTimerSlack(timerSlack),
         mMinVsyncDistance(minVsyncDistance) {}
 
@@ -255,7 +255,7 @@
         }
 
         if (it != skipUpdateIt) {
-            callback->update(mTracker, now);
+            callback->update(*mTracker, now);
         }
         auto const wakeupTime = *callback->wakeupTime();
         if (!min || *min > wakeupTime) {
@@ -365,10 +365,10 @@
     auto const rearmImminent = now > mIntendedWakeupTime;
     if (CC_UNLIKELY(rearmImminent)) {
         callback->addPendingWorkloadUpdate(scheduleTiming);
-        return getExpectedCallbackTime(mTracker, now, scheduleTiming);
+        return getExpectedCallbackTime(*mTracker, now, scheduleTiming);
     }
 
-    const ScheduleResult result = callback->schedule(scheduleTiming, mTracker, now);
+    const ScheduleResult result = callback->schedule(scheduleTiming, *mTracker, now);
     if (!result.has_value()) {
         return {};
     }
@@ -434,15 +434,15 @@
     }
 }
 
-VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch,
+VSyncCallbackRegistration::VSyncCallbackRegistration(std::shared_ptr<VSyncDispatch> dispatch,
                                                      VSyncDispatch::Callback callback,
                                                      std::string callbackName)
-      : mDispatch(dispatch),
-        mToken(dispatch.registerCallback(std::move(callback), std::move(callbackName))),
+      : mDispatch(std::move(dispatch)),
+        mToken(mDispatch->registerCallback(std::move(callback), std::move(callbackName))),
         mValidToken(true) {}
 
 VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncCallbackRegistration&& other)
-      : mDispatch(other.mDispatch),
+      : mDispatch(std::move(other.mDispatch)),
         mToken(std::move(other.mToken)),
         mValidToken(std::move(other.mValidToken)) {
     other.mValidToken = false;
@@ -457,28 +457,28 @@
 }
 
 VSyncCallbackRegistration::~VSyncCallbackRegistration() {
-    if (mValidToken) mDispatch.get().unregisterCallback(mToken);
+    if (mValidToken) mDispatch->unregisterCallback(mToken);
 }
 
 ScheduleResult VSyncCallbackRegistration::schedule(VSyncDispatch::ScheduleTiming scheduleTiming) {
     if (!mValidToken) {
         return std::nullopt;
     }
-    return mDispatch.get().schedule(mToken, scheduleTiming);
+    return mDispatch->schedule(mToken, scheduleTiming);
 }
 
 ScheduleResult VSyncCallbackRegistration::update(VSyncDispatch::ScheduleTiming scheduleTiming) {
     if (!mValidToken) {
         return std::nullopt;
     }
-    return mDispatch.get().update(mToken, scheduleTiming);
+    return mDispatch->update(mToken, scheduleTiming);
 }
 
 CancelResult VSyncCallbackRegistration::cancel() {
     if (!mValidToken) {
         return CancelResult::Error;
     }
-    return mDispatch.get().cancel(mToken);
+    return mDispatch->cancel(mToken);
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index c3af136..6499d69 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -26,11 +26,11 @@
 #include <android-base/thread_annotations.h>
 
 #include "VSyncDispatch.h"
+#include "VsyncSchedule.h"
 
 namespace android::scheduler {
 
 class TimeKeeper;
-class VSyncTracker;
 
 // VSyncDispatchTimerQueueEntry is a helper class representing internal state for each entry in
 // VSyncDispatchTimerQueue hoisted to public for unit testing.
@@ -120,8 +120,8 @@
     //                                  should be grouped into one wakeup.
     // \param[in] minVsyncDistance      The minimum distance between two vsync estimates before the
     //                                  vsyncs are considered the same vsync event.
-    VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper>, VSyncTracker&, nsecs_t timerSlack,
-                            nsecs_t minVsyncDistance);
+    VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper>, VsyncSchedule::TrackerPtr,
+                            nsecs_t timerSlack, nsecs_t minVsyncDistance);
     ~VSyncDispatchTimerQueue();
 
     CallbackToken registerCallback(Callback, std::string callbackName) final;
@@ -148,7 +148,7 @@
 
     static constexpr nsecs_t kInvalidTime = std::numeric_limits<int64_t>::max();
     std::unique_ptr<TimeKeeper> const mTimeKeeper;
-    VSyncTracker& mTracker;
+    VsyncSchedule::TrackerPtr mTracker;
     nsecs_t const mTimerSlack;
     nsecs_t const mMinVsyncDistance;
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index fdeb310..e969fdc 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -31,6 +31,7 @@
 #include <android-base/stringprintf.h>
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
+#include <ftl/concat.h>
 #include <gui/TraceUtils.h>
 #include <utils/Log.h>
 
@@ -45,9 +46,10 @@
 
 VSyncPredictor::~VSyncPredictor() = default;
 
-VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize,
+VSyncPredictor::VSyncPredictor(PhysicalDisplayId id, nsecs_t idealPeriod, size_t historySize,
                                size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
-      : mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
+      : mId(id),
+        mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
         kHistorySize(historySize),
         kMinimumSamplesForPrediction(minimumSamplesForPrediction),
         kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
@@ -57,12 +59,12 @@
 
 inline void VSyncPredictor::traceInt64If(const char* name, int64_t value) const {
     if (CC_UNLIKELY(mTraceOn)) {
-        ATRACE_INT64(name, value);
+        traceInt64(name, value);
     }
 }
 
 inline void VSyncPredictor::traceInt64(const char* name, int64_t value) const {
-    ATRACE_INT64(name, value);
+    ATRACE_INT64(ftl::Concat(ftl::truncated<14>(name), " ", mId.value).c_str(), value);
 }
 
 inline size_t VSyncPredictor::next(size_t i) const {
@@ -214,8 +216,8 @@
 
     it->second = {anticipatedPeriod, intercept};
 
-    ALOGV("model update ts: %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, timestamp,
-          anticipatedPeriod, intercept);
+    ALOGV("model update ts %" PRIu64 ": %" PRId64 " slope: %" PRId64 " intercept: %" PRId64,
+          mId.value, timestamp, anticipatedPeriod, intercept);
     return true;
 }
 
@@ -331,7 +333,7 @@
 }
 
 void VSyncPredictor::setRenderRate(Fps fps) {
-    ALOGV("%s: %s", __func__, to_string(fps).c_str());
+    ALOGV("%s %s: %s", __func__, to_string(mId).c_str(), to_string(fps).c_str());
     std::lock_guard lock(mMutex);
     mRenderRate = fps;
 }
@@ -347,7 +349,7 @@
 }
 
 void VSyncPredictor::setPeriod(nsecs_t period) {
-    ATRACE_CALL();
+    ATRACE_FORMAT("%s %s", __func__, to_string(mId).c_str());
     traceInt64("VSP-setPeriod", period);
 
     std::lock_guard lock(mMutex);
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index cd5d9ef..c01c44d 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -21,6 +21,7 @@
 #include <vector>
 
 #include <android-base/thread_annotations.h>
+#include <ui/DisplayId.h>
 
 #include "VSyncTracker.h"
 
@@ -29,14 +30,15 @@
 class VSyncPredictor : public VSyncTracker {
 public:
     /*
+     * \param [in] PhysicalDisplayid The display this corresponds to.
      * \param [in] idealPeriod  The initial ideal period to use.
      * \param [in] historySize  The internal amount of entries to store in the model.
      * \param [in] minimumSamplesForPrediction The minimum number of samples to collect before
      * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter
      * samples that fall outlierTolerancePercent from an anticipated vsync event.
      */
-    VSyncPredictor(nsecs_t idealPeriod, size_t historySize, size_t minimumSamplesForPrediction,
-                   uint32_t outlierTolerancePercent);
+    VSyncPredictor(PhysicalDisplayId, nsecs_t idealPeriod, size_t historySize,
+                   size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent);
     ~VSyncPredictor();
 
     bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
@@ -76,6 +78,8 @@
     VSyncPredictor& operator=(VSyncPredictor const&) = delete;
     void clearTimestamps() REQUIRES(mMutex);
 
+    const PhysicalDisplayId mId;
+
     inline void traceInt64If(const char* name, int64_t value) const;
     inline void traceInt64(const char* name, int64_t value) const;
 
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index b5f212e..2938aa3 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -21,6 +21,8 @@
 
 #include <assert.h>
 #include <cutils/properties.h>
+#include <ftl/concat.h>
+#include <gui/TraceUtils.h>
 #include <log/log.h>
 #include <utils/Trace.h>
 
@@ -39,12 +41,13 @@
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
 
-VSyncReactor::VSyncReactor(std::unique_ptr<Clock> clock, VSyncTracker& tracker,
-                           size_t pendingFenceLimit, bool supportKernelIdleTimer)
-      : mClock(std::move(clock)),
+VSyncReactor::VSyncReactor(PhysicalDisplayId id, std::unique_ptr<Clock> clock,
+                           VSyncTracker& tracker, size_t pendingFenceLimit,
+                           bool supportKernelIdleTimer)
+      : mId(id),
+        mClock(std::move(clock)),
         mTracker(tracker),
         mPendingLimit(pendingFenceLimit),
-        // TODO(adyabr): change mSupportKernelIdleTimer when the active display changes
         mSupportKernelIdleTimer(supportKernelIdleTimer) {}
 
 VSyncReactor::~VSyncReactor() = default;
@@ -114,7 +117,7 @@
 }
 
 void VSyncReactor::startPeriodTransitionInternal(nsecs_t newPeriod) {
-    ATRACE_CALL();
+    ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
     mPeriodConfirmationInProgress = true;
     mPeriodTransitioningTo = newPeriod;
     mMoreSamplesNeeded = true;
@@ -122,18 +125,18 @@
 }
 
 void VSyncReactor::endPeriodTransition() {
-    ATRACE_CALL();
+    ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
     mPeriodTransitioningTo.reset();
     mPeriodConfirmationInProgress = false;
     mLastHwVsync.reset();
 }
 
-void VSyncReactor::startPeriodTransition(nsecs_t period) {
-    ATRACE_INT64("VSR-startPeriodTransition", period);
+void VSyncReactor::startPeriodTransition(nsecs_t period, bool force) {
+    ATRACE_INT64(ftl::Concat("VSR-", __func__, " ", mId.value).c_str(), period);
     std::lock_guard lock(mMutex);
     mLastHwVsync.reset();
 
-    if (!mSupportKernelIdleTimer && period == mTracker.currentPeriod()) {
+    if (!mSupportKernelIdleTimer && period == mTracker.currentPeriod() && !force) {
         endPeriodTransition();
         setIgnorePresentFencesInternal(false);
         mMoreSamplesNeeded = false;
@@ -181,7 +184,7 @@
 
     std::lock_guard lock(mMutex);
     if (periodConfirmed(timestamp, hwcVsyncPeriod)) {
-        ATRACE_NAME("VSR: period confirmed");
+        ATRACE_FORMAT("VSR %" PRIu64 ": period confirmed", mId.value);
         if (mPeriodTransitioningTo) {
             mTracker.setPeriod(*mPeriodTransitioningTo);
             *periodFlushed = true;
@@ -195,12 +198,12 @@
         endPeriodTransition();
         mMoreSamplesNeeded = mTracker.needsMoreSamples();
     } else if (mPeriodConfirmationInProgress) {
-        ATRACE_NAME("VSR: still confirming period");
+        ATRACE_FORMAT("VSR %" PRIu64 ": still confirming period", mId.value);
         mLastHwVsync = timestamp;
         mMoreSamplesNeeded = true;
         *periodFlushed = false;
     } else {
-        ATRACE_NAME("VSR: adding sample");
+        ATRACE_FORMAT("VSR %" PRIu64 ": adding sample", mId.value);
         *periodFlushed = false;
         mTracker.addVsyncTimestamp(timestamp);
         mMoreSamplesNeeded = mTracker.needsMoreSamples();
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.h b/services/surfaceflinger/Scheduler/VSyncReactor.h
index 4501487..f230242 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.h
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.h
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include <android-base/thread_annotations.h>
+#include <ui/DisplayId.h>
 #include <ui/FenceTime.h>
 
 #include <scheduler/TimeKeeper.h>
@@ -37,14 +38,14 @@
 // TODO (b/145217110): consider renaming.
 class VSyncReactor : public VsyncController {
 public:
-    VSyncReactor(std::unique_ptr<Clock> clock, VSyncTracker& tracker, size_t pendingFenceLimit,
-                 bool supportKernelIdleTimer);
+    VSyncReactor(PhysicalDisplayId, std::unique_ptr<Clock> clock, VSyncTracker& tracker,
+                 size_t pendingFenceLimit, bool supportKernelIdleTimer);
     ~VSyncReactor();
 
     bool addPresentFence(std::shared_ptr<FenceTime>) final;
     void setIgnorePresentFences(bool ignore) final;
 
-    void startPeriodTransition(nsecs_t period) final;
+    void startPeriodTransition(nsecs_t period, bool force) final;
 
     bool addHwVsyncTimestamp(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod,
                              bool* periodFlushed) final;
@@ -61,6 +62,7 @@
     bool periodConfirmed(nsecs_t vsync_timestamp, std::optional<nsecs_t> hwcVsyncPeriod)
             REQUIRES(mMutex);
 
+    const PhysicalDisplayId mId;
     std::unique_ptr<Clock> const mClock;
     VSyncTracker& mTracker;
     size_t const mPendingLimit;
diff --git a/services/surfaceflinger/Scheduler/VsyncController.h b/services/surfaceflinger/Scheduler/VsyncController.h
index 726a420..9177899 100644
--- a/services/surfaceflinger/Scheduler/VsyncController.h
+++ b/services/surfaceflinger/Scheduler/VsyncController.h
@@ -63,8 +63,9 @@
      * itself. The controller will end the period transition internally.
      *
      * \param [in] period   The period that the system is changing into.
+     * \param [in] force    True to recalibrate even if period matches the existing period.
      */
-    virtual void startPeriodTransition(nsecs_t period) = 0;
+    virtual void startPeriodTransition(nsecs_t period, bool force) = 0;
 
     /*
      * Tells the tracker to stop using present fences to get a vsync signal.
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index 5245556..62e37db 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -42,8 +42,8 @@
     }
 
 public:
-    explicit PredictedVsyncTracer(VsyncDispatch& dispatch)
-          : mRegistration(dispatch, makeVsyncCallback(), __func__) {
+    explicit PredictedVsyncTracer(std::shared_ptr<VsyncDispatch> dispatch)
+          : mRegistration(std::move(dispatch), makeVsyncCallback(), __func__) {
         schedule();
     }
 
@@ -54,16 +54,19 @@
     VSyncCallbackRegistration mRegistration;
 };
 
-VsyncSchedule::VsyncSchedule(FeatureFlags features)
-      : mTracker(createTracker()),
-        mDispatch(createDispatch(*mTracker)),
-        mController(createController(*mTracker, features)),
+VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, FeatureFlags features)
+      : mId(id),
+        mTracker(createTracker(id)),
+        mDispatch(createDispatch(mTracker)),
+        mController(createController(id, *mTracker, features)),
         mTracer(features.test(Feature::kTracePredictedVsync)
-                        ? std::make_unique<PredictedVsyncTracer>(*mDispatch)
+                        ? std::make_unique<PredictedVsyncTracer>(mDispatch)
                         : nullptr) {}
 
-VsyncSchedule::VsyncSchedule(TrackerPtr tracker, DispatchPtr dispatch, ControllerPtr controller)
-      : mTracker(std::move(tracker)),
+VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, TrackerPtr tracker, DispatchPtr dispatch,
+                             ControllerPtr controller)
+      : mId(id),
+        mTracker(std::move(tracker)),
         mDispatch(std::move(dispatch)),
         mController(std::move(controller)) {}
 
@@ -95,45 +98,46 @@
     mDispatch->dump(out);
 }
 
-VsyncSchedule::TrackerPtr VsyncSchedule::createTracker() {
+VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(PhysicalDisplayId id) {
     // TODO(b/144707443): Tune constants.
     constexpr nsecs_t kInitialPeriod = (60_Hz).getPeriodNsecs();
     constexpr size_t kHistorySize = 20;
     constexpr size_t kMinSamplesForPrediction = 6;
     constexpr uint32_t kDiscardOutlierPercent = 20;
 
-    return std::make_unique<VSyncPredictor>(kInitialPeriod, kHistorySize, kMinSamplesForPrediction,
-                                            kDiscardOutlierPercent);
+    return std::make_unique<VSyncPredictor>(id, kInitialPeriod, kHistorySize,
+                                            kMinSamplesForPrediction, kDiscardOutlierPercent);
 }
 
-VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(VsyncTracker& tracker) {
+VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) {
     using namespace std::chrono_literals;
 
     // TODO(b/144707443): Tune constants.
     constexpr std::chrono::nanoseconds kGroupDispatchWithin = 500us;
     constexpr std::chrono::nanoseconds kSnapToSameVsyncWithin = 3ms;
 
-    return std::make_unique<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
+    return std::make_unique<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), std::move(tracker),
                                                      kGroupDispatchWithin.count(),
                                                      kSnapToSameVsyncWithin.count());
 }
 
-VsyncSchedule::ControllerPtr VsyncSchedule::createController(VsyncTracker& tracker,
+VsyncSchedule::ControllerPtr VsyncSchedule::createController(PhysicalDisplayId id,
+                                                             VsyncTracker& tracker,
                                                              FeatureFlags features) {
     // TODO(b/144707443): Tune constants.
     constexpr size_t kMaxPendingFences = 20;
     const bool hasKernelIdleTimer = features.test(Feature::kKernelIdleTimer);
 
-    auto reactor = std::make_unique<VSyncReactor>(std::make_unique<SystemClock>(), tracker,
+    auto reactor = std::make_unique<VSyncReactor>(id, std::make_unique<SystemClock>(), tracker,
                                                   kMaxPendingFences, hasKernelIdleTimer);
 
     reactor->setIgnorePresentFences(!features.test(Feature::kPresentFences));
     return reactor;
 }
 
-void VsyncSchedule::startPeriodTransition(ISchedulerCallback& callback, Period period) {
+void VsyncSchedule::startPeriodTransition(ISchedulerCallback& callback, Period period, bool force) {
     std::lock_guard<std::mutex> lock(mHwVsyncLock);
-    mController->startPeriodTransition(period.ns());
+    mController->startPeriodTransition(period.ns(), force);
     enableHardwareVsyncLocked(callback);
 }
 
@@ -165,7 +169,7 @@
 void VsyncSchedule::enableHardwareVsyncLocked(ISchedulerCallback& callback) {
     if (mHwVsyncState == HwVsyncState::Disabled) {
         getTracker().resetModel();
-        callback.setVsyncEnabled(true);
+        callback.setVsyncEnabled(mId, true);
         mHwVsyncState = HwVsyncState::Enabled;
     }
 }
@@ -173,7 +177,7 @@
 void VsyncSchedule::disableHardwareVsync(ISchedulerCallback& callback, bool disallow) {
     std::lock_guard<std::mutex> lock(mHwVsyncLock);
     if (mHwVsyncState == HwVsyncState::Enabled) {
-        callback.setVsyncEnabled(false);
+        callback.setVsyncEnabled(mId, false);
     }
     mHwVsyncState = disallow ? HwVsyncState::Disallowed : HwVsyncState::Disabled;
 }
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index d88f1d1..763d058 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -20,13 +20,16 @@
 #include <string>
 
 #include <ThreadContext.h>
+#include <android-base/thread_annotations.h>
 #include <ftl/enum.h>
 #include <ftl/optional.h>
 #include <scheduler/Features.h>
 #include <scheduler/Time.h>
+#include <ui/DisplayId.h>
 
 namespace android {
 class EventThreadTest;
+class VsyncScheduleTest;
 }
 
 namespace android::fuzz {
@@ -48,7 +51,7 @@
 // Schedule that synchronizes to hardware VSYNC of a physical display.
 class VsyncSchedule {
 public:
-    explicit VsyncSchedule(FeatureFlags);
+    VsyncSchedule(PhysicalDisplayId, FeatureFlags);
     ~VsyncSchedule();
 
     Period period() const;
@@ -59,7 +62,9 @@
     // enable hardware VSYNCs in order to calibrate.
     //
     // \param [in] period   The period that the system is changing into.
-    void startPeriodTransition(ISchedulerCallback&, Period period);
+    // \param [in] force    True to force a transition even if it is not a
+    //                      change.
+    void startPeriodTransition(ISchedulerCallback&, Period period, bool force);
 
     // Pass a VSYNC sample to VsyncController. Return true if
     // VsyncController detected that the VSYNC period changed. Enable or disable
@@ -72,8 +77,13 @@
     VsyncTracker& getTracker() { return *mTracker; }
     VsyncController& getController() { return *mController; }
 
+    // TODO(b/185535769): Once these are hidden behind the API, they may no
+    // longer need to be shared_ptrs.
+    using DispatchPtr = std::shared_ptr<VsyncDispatch>;
+    using TrackerPtr = std::shared_ptr<VsyncTracker>;
+
     // TODO(b/185535769): Remove once VsyncSchedule owns all registrations.
-    VsyncDispatch& getDispatch() { return *mDispatch; }
+    DispatchPtr getDispatch() { return mDispatch; }
 
     void dump(std::string&) const;
 
@@ -82,7 +92,8 @@
     void enableHardwareVsync(ISchedulerCallback&) EXCLUDES(mHwVsyncLock);
 
     // Disable hardware VSYNCs. If `disallow` is true, future calls to
-    // enableHardwareVsync are ineffective until allowHardwareVsync is called.
+    // enableHardwareVsync are ineffective until isHardwareVsyncAllowed is
+    // called with `makeAllowed` set to true.
     void disableHardwareVsync(ISchedulerCallback&, bool disallow) EXCLUDES(mHwVsyncLock);
 
     // If true, enableHardwareVsync can enable hardware VSYNC (if not already
@@ -93,21 +104,21 @@
 
     bool getPendingHardwareVsyncState() const REQUIRES(kMainThreadContext);
 
-private:
-    friend class TestableScheduler;
-    friend class android::EventThreadTest;
-    friend class android::fuzz::SchedulerFuzzer;
-
-    using TrackerPtr = std::unique_ptr<VsyncTracker>;
-    using DispatchPtr = std::unique_ptr<VsyncDispatch>;
+protected:
     using ControllerPtr = std::unique_ptr<VsyncController>;
 
     // For tests.
-    VsyncSchedule(TrackerPtr, DispatchPtr, ControllerPtr);
+    VsyncSchedule(PhysicalDisplayId, TrackerPtr, DispatchPtr, ControllerPtr);
 
-    static TrackerPtr createTracker();
-    static DispatchPtr createDispatch(VsyncTracker&);
-    static ControllerPtr createController(VsyncTracker&, FeatureFlags);
+private:
+    friend class TestableScheduler;
+    friend class android::EventThreadTest;
+    friend class android::VsyncScheduleTest;
+    friend class android::fuzz::SchedulerFuzzer;
+
+    static TrackerPtr createTracker(PhysicalDisplayId);
+    static DispatchPtr createDispatch(TrackerPtr);
+    static ControllerPtr createController(PhysicalDisplayId, VsyncTracker&, FeatureFlags);
 
     void enableHardwareVsyncLocked(ISchedulerCallback&) REQUIRES(mHwVsyncLock);
 
@@ -135,6 +146,7 @@
     class PredictedVsyncTracer;
     using TracerPtr = std::unique_ptr<PredictedVsyncTracer>;
 
+    const PhysicalDisplayId mId;
     const TrackerPtr mTracker;
     const DispatchPtr mDispatch;
     const ControllerPtr mController;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index b5b7b1e..eecfeb6 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1130,21 +1130,33 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::getDisplayStats(const sp<IBinder>&, DisplayStatInfo* outStats) {
+status_t SurfaceFlinger::getDisplayStats(const sp<IBinder>& displayToken,
+                                         DisplayStatInfo* outStats) {
     if (!outStats) {
         return BAD_VALUE;
     }
 
-    const auto& schedule = mScheduler->getVsyncSchedule();
-    outStats->vsyncTime = schedule.vsyncDeadlineAfter(TimePoint::now()).ns();
-    outStats->vsyncPeriod = schedule.period().ns();
+    std::optional<PhysicalDisplayId> displayIdOpt;
+    {
+        Mutex::Autolock lock(mStateLock);
+        displayIdOpt = getPhysicalDisplayIdLocked(displayToken);
+    }
+
+    if (!displayIdOpt) {
+        ALOGE("%s: Invalid physical display token %p", __func__, displayToken.get());
+        return NAME_NOT_FOUND;
+    }
+    const auto schedule = mScheduler->getVsyncSchedule(displayIdOpt);
+    outStats->vsyncTime = schedule->vsyncDeadlineAfter(TimePoint::now()).ns();
+    outStats->vsyncPeriod = schedule->period().ns();
     return NO_ERROR;
 }
 
 void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request, bool force) {
     ATRACE_CALL();
 
-    auto display = getDisplayDeviceLocked(request.mode.modePtr->getPhysicalDisplayId());
+    const auto displayId = request.mode.modePtr->getPhysicalDisplayId();
+    const auto display = getDisplayDeviceLocked(displayId);
     if (!display) {
         ALOGW("%s: display is no longer valid", __func__);
         return;
@@ -1157,23 +1169,25 @@
                                           force)) {
         case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch:
             // Set the render rate as setDesiredActiveMode updated it.
-            mScheduler->setRenderRate(display->refreshRateSelector().getActiveMode().fps);
+            mScheduler->setRenderRate(displayId,
+                                      display->refreshRateSelector().getActiveMode().fps);
 
             // Schedule a new frame to initiate the display mode switch.
             scheduleComposite(FrameHint::kNone);
 
             // Start receiving vsync samples now, so that we can detect a period
             // switch.
-            mScheduler->resyncToHardwareVsync(true, mode.modePtr->getFps());
+            mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */,
+                                              mode.modePtr->getFps());
+
             // As we called to set period, we will call to onRefreshRateChangeCompleted once
             // VsyncController model is locked.
-            mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated);
-
+            mScheduler->modulateVsync(displayId, &VsyncModulator::onRefreshRateChangeInitiated);
             updatePhaseConfiguration(mode.fps);
             mScheduler->setModeChangePending(true);
             break;
         case DisplayDevice::DesiredActiveModeAction::InitiateRenderRateSwitch:
-            mScheduler->setRenderRate(mode.fps);
+            mScheduler->setRenderRate(displayId, mode.fps);
             updatePhaseConfiguration(mode.fps);
             mRefreshRateStats->setRefreshRate(mode.fps);
             if (display->getPhysicalId() == mActiveDisplayId && emitEvent) {
@@ -1289,11 +1303,14 @@
 }
 
 void SurfaceFlinger::desiredActiveModeChangeDone(const sp<DisplayDevice>& display) {
-    const auto displayFps = display->getDesiredActiveMode()->modeOpt->modePtr->getFps();
-    const auto renderFps = display->getDesiredActiveMode()->modeOpt->fps;
+    const auto desiredActiveMode = display->getDesiredActiveMode();
+    const auto& modeOpt = desiredActiveMode->modeOpt;
+    const auto displayId = modeOpt->modePtr->getPhysicalDisplayId();
+    const auto displayFps = modeOpt->modePtr->getFps();
+    const auto renderFps = modeOpt->fps;
     clearDesiredActiveModeState(display);
-    mScheduler->resyncToHardwareVsync(true, displayFps);
-    mScheduler->setRenderRate(renderFps);
+    mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, displayFps);
+    mScheduler->setRenderRate(displayId, renderFps);
     updatePhaseConfiguration(renderFps);
 }
 
@@ -2031,27 +2048,16 @@
 
 void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp,
                                         std::optional<hal::VsyncPeriodNanos> vsyncPeriod) {
-    const std::string tracePeriod = [vsyncPeriod]() {
-        if (ATRACE_ENABLED() && vsyncPeriod) {
-            std::stringstream ss;
-            ss << "(" << *vsyncPeriod << ")";
-            return ss.str();
-        }
-        return std::string();
-    }();
-    ATRACE_FORMAT("onComposerHalVsync%s", tracePeriod.c_str());
+    ATRACE_NAME(vsyncPeriod
+                        ? ftl::Concat(__func__, ' ', hwcDisplayId, ' ', *vsyncPeriod, "ns").c_str()
+                        : ftl::Concat(__func__, ' ', hwcDisplayId).c_str());
 
     Mutex::Autolock lock(mStateLock);
-
-    if (const auto displayIdOpt = getHwComposer().onVsync(hwcDisplayId, timestamp);
-        displayIdOpt != mActiveDisplayId) {
-        // Ignore VSYNC for invalid/inactive displays.
-        return;
-    }
-
-    const bool periodFlushed = mScheduler->addResyncSample(timestamp, vsyncPeriod);
-    if (periodFlushed) {
-        mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeCompleted);
+    if (const auto displayIdOpt = getHwComposer().onVsync(hwcDisplayId, timestamp)) {
+        if (mScheduler->addResyncSample(*displayIdOpt, timestamp, vsyncPeriod)) {
+            // period flushed
+            mScheduler->modulateVsync(displayIdOpt, &VsyncModulator::onRefreshRateChangeCompleted);
+        }
     }
 }
 
@@ -2096,19 +2102,20 @@
     // TODO(b/202734676) update refresh rate value on the RefreshRateOverlay
 }
 
-void SurfaceFlinger::setVsyncEnabled(bool enabled) {
-    ATRACE_CALL();
+void SurfaceFlinger::setVsyncEnabled(PhysicalDisplayId id, bool enabled) {
+    const char* const whence = __func__;
+    ATRACE_FORMAT("%s (%d) for %" PRIu64, whence, enabled, id.value);
 
     // On main thread to avoid race conditions with display power state.
     static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
         {
             ftl::FakeGuard guard(kMainThreadContext);
-            mScheduler->getVsyncSchedule().setPendingHardwareVsyncState(enabled);
+            mScheduler->getVsyncSchedule(id)->setPendingHardwareVsyncState(enabled);
         }
 
-        if (const auto display = getDefaultDisplayDeviceLocked();
-            display && display->isPoweredOn()) {
-            setHWCVsyncEnabled(display->getPhysicalId(), enabled);
+        ATRACE_FORMAT("%s (%d) for %" PRIu64 " (main thread)", whence, enabled, id.value);
+        if (const auto display = getDisplayDeviceLocked(id); display && display->isPoweredOn()) {
+            setHWCVsyncEnabled(id, enabled);
         }
     }));
 }
@@ -2135,13 +2142,13 @@
 TimePoint SurfaceFlinger::calculateExpectedPresentTime(TimePoint frameTime) const {
     const auto& schedule = mScheduler->getVsyncSchedule();
 
-    const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(frameTime);
+    const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(frameTime);
     if (mScheduler->vsyncModulator().getVsyncConfig().sfOffset > 0) {
         return vsyncDeadline;
     }
 
     // Inflate the expected present time if we're targeting the next vsync.
-    return vsyncDeadline + schedule.period();
+    return vsyncDeadline + schedule->period();
 }
 
 void SurfaceFlinger::configure() FTL_FAKE_GUARD(kMainThreadContext) {
@@ -2297,7 +2304,7 @@
                   ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()),
                   mExpectedPresentTime == expectedVsyncTime ? "" : " (adjusted)");
 
-    const Period vsyncPeriod = mScheduler->getVsyncSchedule().period();
+    const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period();
     const FenceTimePtr& previousPresentFence = getPreviousPresentFence(frameTime, vsyncPeriod);
 
     // When backpressure propagation is enabled, we want to give a small grace period of 1ms
@@ -2547,7 +2554,7 @@
         refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay);
     }
 
-    const auto prevVsyncTime = mExpectedPresentTime - mScheduler->getVsyncSchedule().period();
+    const auto prevVsyncTime = mExpectedPresentTime - mScheduler->getVsyncSchedule()->period();
     const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration;
 
     refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration;
@@ -2630,7 +2637,7 @@
     // TODO(b/160583065): Enable skip validation when SF caches all client composition layers.
     const bool hasGpuUseOrReuse =
             mCompositionCoverage.any(CompositionCoverage::Gpu | CompositionCoverage::GpuReuse);
-    mScheduler->modulateVsync(&VsyncModulator::onDisplayRefresh, hasGpuUseOrReuse);
+    mScheduler->modulateVsync({}, &VsyncModulator::onDisplayRefresh, hasGpuUseOrReuse);
 
     mLayersWithQueuedFrames.clear();
     if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
@@ -2774,9 +2781,9 @@
             ? mPresentLatencyTracker.trackPendingFrame(compositeTime, presentFenceTime)
             : Duration::zero();
 
-    const auto& schedule = mScheduler->getVsyncSchedule();
-    const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(presentTime);
-    const Period vsyncPeriod = schedule.period();
+    const auto schedule = mScheduler->getVsyncSchedule();
+    const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(presentTime);
+    const Period vsyncPeriod = schedule->period();
     const nsecs_t vsyncPhase = mVsyncConfiguration->getCurrentConfigs().late.sfOffset;
 
     const CompositorTiming compositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), vsyncPhase,
@@ -2851,15 +2858,19 @@
     mTimeStats->incrementTotalFrames();
     mTimeStats->setPresentFenceGlobal(presentFenceTime);
 
-    const bool isInternalDisplay = defaultDisplay &&
-            FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays)
-                    .get(defaultDisplay->getPhysicalId())
-                    .transform(&PhysicalDisplay::isInternal)
-                    .value_or(false);
-
-    if (isInternalDisplay && defaultDisplay && defaultDisplay->getPowerMode() == hal::PowerMode::ON &&
-        presentFenceTime->isValid()) {
-        mScheduler->addPresentFence(std::move(presentFenceTime));
+    {
+        ftl::FakeGuard guard(mStateLock);
+        for (const auto& [id, physicalDisplay] : mPhysicalDisplays) {
+            if (auto displayDevice = getDisplayDeviceLocked(id);
+                displayDevice && displayDevice->isPoweredOn() && physicalDisplay.isInternal()) {
+                auto presentFenceTimeI = defaultDisplay && defaultDisplay->getPhysicalId() == id
+                        ? std::move(presentFenceTime)
+                        : std::make_shared<FenceTime>(getHwComposer().getPresentFence(id));
+                if (presentFenceTimeI->isValid()) {
+                    mScheduler->addPresentFence(id, std::move(presentFenceTimeI));
+                }
+            }
+        }
     }
 
     const bool isDisplayConnected =
@@ -2867,7 +2878,7 @@
 
     if (!hasSyncFramework) {
         if (isDisplayConnected && defaultDisplay->isPoweredOn()) {
-            mScheduler->enableHardwareVsync();
+            mScheduler->enableHardwareVsync(defaultDisplay->getPhysicalId());
         }
     }
 
@@ -2983,7 +2994,7 @@
     // so we can call commitTransactionsLocked unconditionally.
     // We clear the flags with mStateLock held to guarantee that
     // mCurrentState won't change until the transaction is committed.
-    mScheduler->modulateVsync(&VsyncModulator::onTransactionCommit);
+    mScheduler->modulateVsync({}, &VsyncModulator::onTransactionCommit);
     commitTransactionsLocked(clearTransactionFlags(eTransactionMask));
 
     mDebugInTransaction = 0;
@@ -3606,6 +3617,10 @@
         });
     }
 
+    if (transactionFlags & eInputInfoUpdateNeeded) {
+        mUpdateInputInfo = true;
+    }
+
     doCommitTransactions();
 }
 
@@ -3809,10 +3824,9 @@
     mScheduler = std::make_unique<Scheduler>(static_cast<ICompositor&>(*this),
                                              static_cast<ISchedulerCallback&>(*this), features,
                                              std::move(modulatorPtr));
-    mScheduler->createVsyncSchedule(features);
     mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
 
-    setVsyncEnabled(false);
+    setVsyncEnabled(display->getPhysicalId(), false);
     mScheduler->startTimers();
 
     const auto configs = mVsyncConfiguration->getCurrentConfigs();
@@ -3828,7 +3842,7 @@
                                           /* workDuration */ activeRefreshRate.getPeriod(),
                                           /* readyDuration */ configs.late.sfWorkDuration);
 
-    mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(),
+    mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(),
                           *mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration);
 
     mRegionSamplingThread =
@@ -3893,7 +3907,7 @@
     for (Layer* offscreenLayer : mOffscreenLayers) {
         offscreenLayer->traverse(LayerVector::StateSet::Drawing, [](Layer* layer) {
             if (layer->clearTransactionFlags(eTransactionNeeded)) {
-                layer->doTransaction(0, 0);
+                layer->doTransaction(0);
                 layer->commitChildList();
             }
         });
@@ -3929,7 +3943,7 @@
     // second frame. But layer 0's second frame could be waiting on display.
     mDrawingState.traverse([&](Layer* layer) {
         if (layer->clearTransactionFlags(eTransactionNeeded) || mForceTransactionDisplayChange) {
-            const uint32_t flags = layer->doTransaction(0, latchTime);
+            const uint32_t flags = layer->doTransaction(0);
             if (flags & Layer::eVisibleRegion) {
                 mVisibleRegionsDirty = true;
             }
@@ -3940,6 +3954,8 @@
             mLayersWithQueuedFrames.emplace(sp<Layer>::fromExisting(layer));
         } else {
             layer->useEmptyDamage();
+            // If the layer has frames we will update the latch time when latching the buffer.
+            layer->updateLastLatchTime(latchTime);
         }
     });
     mForceTransactionDisplayChange = false;
@@ -4042,7 +4058,7 @@
 
 void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule schedule,
                                          const sp<IBinder>& applyToken, FrameHint frameHint) {
-    mScheduler->modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, applyToken);
+    mScheduler->modulateVsync({}, &VsyncModulator::setTransactionSchedule, schedule, applyToken);
     uint32_t transactionFlags = mTransactionFlags.fetch_or(mask);
     ATRACE_INT("mTransactionFlags", transactionFlags);
 
@@ -4229,7 +4245,7 @@
         return false;
     }
 
-    const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule().period() / 2;
+    const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule()->period() / 2;
 
     return predictedPresentTime >= expectedPresentTime &&
             predictedPresentTime - expectedPresentTime >= earlyLatchVsyncThreshold;
@@ -4884,8 +4900,10 @@
     }
 
     if (what & layer_state_t::eTrustedPresentationInfoChanged) {
-        layer->setTrustedPresentationInfo(s.trustedPresentationThresholds,
-                                          s.trustedPresentationListener);
+        if (layer->setTrustedPresentationInfo(s.trustedPresentationThresholds,
+                                              s.trustedPresentationListener)) {
+            flags |= eTraversalNeeded;
+        }
     }
 
     if (what & layer_state_t::eFlushJankData) {
@@ -4990,8 +5008,10 @@
     }
 
     if (what & layer_state_t::eTrustedPresentationInfoChanged) {
-        layer->setTrustedPresentationInfo(s.trustedPresentationThresholds,
-                                          s.trustedPresentationListener);
+        if (layer->setTrustedPresentationInfo(s.trustedPresentationThresholds,
+                                              s.trustedPresentationListener)) {
+            flags |= eTraversalNeeded;
+        }
     }
 
     const auto& requestedLayerState = mLayerLifecycleManager.getLayerFromId(layer->getSequence());
@@ -5267,7 +5287,6 @@
         return;
     }
 
-    const bool isActiveDisplay = displayId == mActiveDisplayId;
     const bool isInternalDisplay = mPhysicalDisplays.get(displayId)
                                            .transform(&PhysicalDisplay::isInternal)
                                            .value_or(false);
@@ -5304,11 +5323,12 @@
             ALOGW("Couldn't set SCHED_FIFO on display on: %s\n", strerror(errno));
         }
         getHwComposer().setPowerMode(displayId, mode);
-        if (isActiveDisplay && mode != hal::PowerMode::DOZE_SUSPEND) {
+        if (displayId == mActiveDisplayId && mode != hal::PowerMode::DOZE_SUSPEND) {
             setHWCVsyncEnabled(displayId,
-                               mScheduler->getVsyncSchedule().getPendingHardwareVsyncState());
-            mScheduler->onScreenAcquired(mAppConnectionHandle);
-            mScheduler->resyncToHardwareVsync(true, refreshRate);
+                               mScheduler->getVsyncSchedule(displayId)
+                                       ->getPendingHardwareVsyncState());
+            mScheduler->enableSyntheticVsync(false);
+            mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, refreshRate);
         }
 
         mVisibleRegionsDirty = true;
@@ -5321,9 +5341,9 @@
         if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) {
             ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno));
         }
-        if (isActiveDisplay && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) {
-            mScheduler->disableHardwareVsync(true);
-            mScheduler->onScreenReleased(mAppConnectionHandle);
+        if (displayId == mActiveDisplayId && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) {
+            mScheduler->disableHardwareVsync(displayId, true);
+            mScheduler->enableSyntheticVsync();
         }
 
         // Make sure HWVsync is disabled before turning off the display
@@ -5335,18 +5355,18 @@
     } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) {
         // Update display while dozing
         getHwComposer().setPowerMode(displayId, mode);
-        if (isActiveDisplay && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) {
+        if (displayId == mActiveDisplayId && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) {
             ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON.");
             mVisibleRegionsDirty = true;
             scheduleRepaint();
-            mScheduler->onScreenAcquired(mAppConnectionHandle);
-            mScheduler->resyncToHardwareVsync(true, refreshRate);
+            mScheduler->enableSyntheticVsync(false);
+            mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, refreshRate);
         }
     } else if (mode == hal::PowerMode::DOZE_SUSPEND) {
         // Leave display going to doze
-        if (isActiveDisplay) {
-            mScheduler->disableHardwareVsync(true);
-            mScheduler->onScreenReleased(mAppConnectionHandle);
+        if (displayId == mActiveDisplayId) {
+            mScheduler->disableHardwareVsync(displayId, true);
+            mScheduler->enableSyntheticVsync();
         }
         getHwComposer().setPowerMode(displayId, mode);
     } else {
@@ -5354,10 +5374,10 @@
         getHwComposer().setPowerMode(displayId, mode);
     }
 
-    if (isActiveDisplay) {
+    if (displayId == mActiveDisplayId) {
         mTimeStats->setPowerMode(mode);
         mRefreshRateStats->setPowerMode(mode);
-        mScheduler->setDisplayPowerMode(mode);
+        mScheduler->setDisplayPowerMode(displayId, mode);
     }
 
     ALOGD("Finished setting power mode %d on display %s", mode, to_string(displayId).c_str());
@@ -7648,8 +7668,9 @@
 }
 
 status_t SurfaceFlinger::addWindowInfosListener(
-        const sp<IWindowInfosListener>& windowInfosListener) const {
+        const sp<IWindowInfosListener>& windowInfosListener) {
     mWindowInfosListenerInvoker->addWindowInfosListener(windowInfosListener);
+    setTransactionFlags(eInputInfoUpdateNeeded);
     return NO_ERROR;
 }
 
@@ -8660,8 +8681,12 @@
 binder::Status SurfaceComposerAIDL::addWindowInfosListener(
         const sp<gui::IWindowInfosListener>& windowInfosListener) {
     status_t status;
+    const int pid = IPCThreadState::self()->getCallingPid();
     const int uid = IPCThreadState::self()->getCallingUid();
-    if (uid == AID_SYSTEM || uid == AID_GRAPHICS) {
+    // TODO(b/270566761) update permissions check so that only system_server and shell can add
+    // WindowInfosListeners
+    if (uid == AID_SYSTEM || uid == AID_GRAPHICS ||
+        checkPermission(sAccessSurfaceFlinger, pid, uid)) {
         status = mFlinger->addWindowInfosListener(windowInfosListener);
     } else {
         status = PERMISSION_DENIED;
@@ -8672,8 +8697,10 @@
 binder::Status SurfaceComposerAIDL::removeWindowInfosListener(
         const sp<gui::IWindowInfosListener>& windowInfosListener) {
     status_t status;
+    const int pid = IPCThreadState::self()->getCallingPid();
     const int uid = IPCThreadState::self()->getCallingUid();
-    if (uid == AID_SYSTEM || uid == AID_GRAPHICS) {
+    if (uid == AID_SYSTEM || uid == AID_GRAPHICS ||
+        checkPermission(sAccessSurfaceFlinger, pid, uid)) {
         status = mFlinger->removeWindowInfosListener(windowInfosListener);
     } else {
         status = PERMISSION_DENIED;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index ddb6232..b353a48 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -162,7 +162,8 @@
     eDisplayTransactionNeeded = 0x04,
     eTransformHintUpdateNeeded = 0x08,
     eTransactionFlushNeeded = 0x10,
-    eTransactionMask = 0x1f,
+    eInputInfoUpdateNeeded = 0x20,
+    eTransactionMask = 0x3f,
 };
 
 // Latch Unsignaled buffer behaviours
@@ -618,7 +619,7 @@
 
     status_t getMaxAcquiredBufferCount(int* buffers) const;
 
-    status_t addWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener) const;
+    status_t addWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener);
     status_t removeWindowInfosListener(
             const sp<gui::IWindowInfosListener>& windowInfosListener) const;
 
@@ -646,7 +647,7 @@
 
     // Toggles hardware VSYNC by calling into HWC.
     // TODO(b/241286146): Rename for self-explanatory API.
-    void setVsyncEnabled(bool) override;
+    void setVsyncEnabled(PhysicalDisplayId, bool) override;
     void requestDisplayModes(std::vector<display::DisplayModeRequest>) override;
     void kernelTimerChanged(bool expired) override;
     void triggerOnFrameRateOverridesChanged() override;
@@ -1133,7 +1134,15 @@
     pid_t mPid;
     std::future<void> mRenderEnginePrimeCacheFuture;
 
-    // access must be protected by mStateLock
+    // mStateLock has conventions related to the current thread, because only
+    // the main thread should modify variables protected by mStateLock.
+    // - read access from a non-main thread must lock mStateLock, since the main
+    // thread may modify these variables.
+    // - write access from a non-main thread is not permitted.
+    // - read access from the main thread can use an ftl::FakeGuard, since other
+    // threads must not modify these variables.
+    // - write access from the main thread must lock mStateLock, since another
+    // thread may be reading these variables.
     mutable Mutex mStateLock;
     State mCurrentState{LayerVector::StateSet::Current};
     std::atomic<int32_t> mTransactionFlags = 0;
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index e0ef8a5..ba08cee 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <gui/SurfaceComposerClient.h>
+#include <renderengine/mock/FakeExternalTexture.h>
 #include <ui/Fence.h>
 #include <ui/Rect.h>
 
@@ -313,6 +314,14 @@
         ResolvedComposerState s;
         s.state.what = 0;
         fromProto(proto.layer_changes(i), s.state);
+        if (s.state.bufferData) {
+            s.externalTexture = std::make_shared<
+                    renderengine::mock::FakeExternalTexture>(s.state.bufferData->getWidth(),
+                                                             s.state.bufferData->getHeight(),
+                                                             s.state.bufferData->getId(),
+                                                             s.state.bufferData->getPixelFormat(),
+                                                             s.state.bufferData->getUsage());
+        }
         t.states.emplace_back(s);
     }
 
diff --git a/services/surfaceflinger/Tracing/tools/run.sh b/services/surfaceflinger/Tracing/tools/run.sh
index baa93f1..307a4d8 100644
--- a/services/surfaceflinger/Tracing/tools/run.sh
+++ b/services/surfaceflinger/Tracing/tools/run.sh
@@ -5,7 +5,15 @@
 # Build, push and run layertracegenerator
 $ANDROID_BUILD_TOP/build/soong/soong_ui.bash --make-mode layertracegenerator
 adb wait-for-device && adb push $OUT/system/bin/layertracegenerator /data/layertracegenerator
-echo "Writing transaction trace to file"
-adb shell service call SurfaceFlinger 1041 i32 0
-adb shell /data/layertracegenerator
+
+if [ -z "$1" ]
+  then
+    echo "Writing transaction trace to file"
+    adb shell service call SurfaceFlinger 1041 i32 0
+    adb shell /data/layertracegenerator
+  else
+    echo "Pushing transaction trace to device"
+    adb push $1 /data/transaction_trace.winscope
+    adb shell /data/layertracegenerator /data/transaction_trace.winscope
+fi
 adb pull /data/misc/wmtrace/layers_trace.winscope
\ No newline at end of file
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index a1313e3..292083b 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -17,7 +17,6 @@
 #include <ftl/small_vector.h>
 #include <gui/ISurfaceComposer.h>
 
-#include "SurfaceFlinger.h"
 #include "WindowInfosListenerInvoker.h"
 
 namespace android {
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h
index a1d66a1..d60a9c4 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.h
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <unordered_set>
+
 #include <android/gui/BnWindowInfosReportedListener.h>
 #include <android/gui/IWindowInfosListener.h>
 #include <android/gui/IWindowInfosReportedListener.h>
@@ -49,8 +51,6 @@
     static constexpr size_t kStaticCapacity = 3;
     ftl::SmallMap<wp<IBinder>, const sp<gui::IWindowInfosListener>, kStaticCapacity>
             mWindowInfosListeners GUARDED_BY(mListenersMutex);
-
-    sp<gui::IWindowInfosReportedListener> mWindowInfosReportedListener;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 609fd33..cfb2032 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -226,19 +226,19 @@
     TestableScheduler(const std::shared_ptr<scheduler::RefreshRateSelector>& selectorPtr,
                       sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
           : TestableScheduler(std::make_unique<android::mock::VsyncController>(),
-                              std::make_unique<android::mock::VSyncTracker>(), selectorPtr,
+                              std::make_shared<android::mock::VSyncTracker>(), selectorPtr,
                               std::move(modulatorPtr), callback) {}
 
     TestableScheduler(std::unique_ptr<VsyncController> controller,
-                      std::unique_ptr<VSyncTracker> tracker,
+                      VsyncSchedule::TrackerPtr tracker,
                       std::shared_ptr<RefreshRateSelector> selectorPtr,
                       sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
           : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) {
-        mVsyncSchedule = std::unique_ptr<VsyncSchedule>(
-                new VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
-
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
-        registerDisplay(displayId, std::move(selectorPtr));
+        registerDisplayInternal(displayId, std::move(selectorPtr),
+                                std::shared_ptr<VsyncSchedule>(
+                                        new VsyncSchedule(displayId, std::move(tracker), nullptr,
+                                                          std::move(controller))));
     }
 
     ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
@@ -647,10 +647,10 @@
 
     // The ISchedulerCallback argument can be nullptr for a no-op implementation.
     void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
-                        std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
+                        std::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
                         std::unique_ptr<EventThread> appEventThread,
                         std::unique_ptr<EventThread> sfEventThread,
-                        scheduler::ISchedulerCallback *callback = nullptr,
+                        scheduler::ISchedulerCallback* callback = nullptr,
                         bool hasMultipleModes = false) {
         constexpr DisplayModeId kModeId60{0};
         DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz));
@@ -789,7 +789,7 @@
     }
 
 private:
-    void setVsyncEnabled(bool) override {}
+    void setVsyncEnabled(PhysicalDisplayId, bool) override {}
     void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index 61fb29a..f6b2c8e 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -48,6 +48,7 @@
 
 constexpr uint16_t kRandomStringLength = 256;
 constexpr std::chrono::duration kSyncPeriod(16ms);
+constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
 
 template <typename T>
 void dump(T* component, FuzzedDataProvider* fdp) {
@@ -76,7 +77,7 @@
 
     FuzzedDataProvider mFdp;
 
-    std::unique_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
+    std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
 };
 
 PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() {
@@ -90,12 +91,13 @@
 }
 
 void SchedulerFuzzer::fuzzEventThread() {
-    mVsyncSchedule = std::unique_ptr<scheduler::VsyncSchedule>(
-            new scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(),
-                                         std::make_unique<mock::VSyncDispatch>(), nullptr));
+    mVsyncSchedule = std::shared_ptr<scheduler::VsyncSchedule>(
+            new scheduler::VsyncSchedule(getPhysicalDisplayId(),
+                                         std::make_shared<mock::VSyncTracker>(),
+                                         std::make_shared<mock::VSyncDispatch>(), nullptr));
     const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); };
     std::unique_ptr<android::impl::EventThread> thread = std::make_unique<
-            android::impl::EventThread>("fuzzer", *mVsyncSchedule, nullptr, nullptr, getVsyncPeriod,
+            android::impl::EventThread>("fuzzer", mVsyncSchedule, nullptr, nullptr, getVsyncPeriod,
                                         (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
                                         (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
 
@@ -109,8 +111,7 @@
     thread->setDuration((std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
                         (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
     thread->registerDisplayEventConnection(connection);
-    thread->onScreenAcquired();
-    thread->onScreenReleased();
+    thread->enableSyntheticVsync(mFdp.ConsumeBool());
     dump<android::impl::EventThread>(thread.get(), &mFdp);
 }
 
@@ -132,7 +133,7 @@
 }
 
 void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() {
-    FuzzImplVSyncTracker stubTracker{mFdp.ConsumeIntegral<nsecs_t>()};
+    auto stubTracker = std::make_shared<FuzzImplVSyncTracker>(mFdp.ConsumeIntegral<nsecs_t>());
     scheduler::VSyncDispatchTimerQueue
             mDispatch{std::make_unique<scheduler::ControllableClock>(), stubTracker,
                       mFdp.ConsumeIntegral<nsecs_t>() /*dispatchGroupThreshold*/,
@@ -145,17 +146,17 @@
     scheduler::VSyncDispatchTimerQueueEntry entry(
             "fuzz", [](auto, auto, auto) {},
             mFdp.ConsumeIntegral<nsecs_t>() /*vSyncMoveThreshold*/);
-    entry.update(stubTracker, 0);
+    entry.update(*stubTracker, 0);
     entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                     .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                     .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()},
-                   stubTracker, 0);
+                   *stubTracker, 0);
     entry.disarm();
     entry.ensureNotRunning();
     entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                     .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
                     .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()},
-                   stubTracker, 0);
+                   *stubTracker, 0);
     auto const wakeup = entry.wakeupTime();
     auto const ready = entry.readyTime();
     entry.callback(entry.executing(), *wakeup, *ready);
@@ -169,7 +170,8 @@
     uint16_t now = mFdp.ConsumeIntegral<uint16_t>();
     uint16_t historySize = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
     uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
-    scheduler::VSyncPredictor tracker{mFdp.ConsumeIntegral<uint16_t>() /*period*/, historySize,
+    scheduler::VSyncPredictor tracker{DEFAULT_DISPLAY_ID,
+                                      mFdp.ConsumeIntegral<uint16_t>() /*period*/, historySize,
                                       minimumSamplesForPrediction,
                                       mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/};
     uint16_t period = mFdp.ConsumeIntegral<uint16_t>();
@@ -242,13 +244,15 @@
 
 void SchedulerFuzzer::fuzzVSyncReactor() {
     std::shared_ptr<FuzzImplVSyncTracker> vSyncTracker = std::make_shared<FuzzImplVSyncTracker>();
-    scheduler::VSyncReactor reactor(std::make_unique<ClockWrapper>(
+    scheduler::VSyncReactor reactor(DEFAULT_DISPLAY_ID,
+                                    std::make_unique<ClockWrapper>(
                                             std::make_shared<FuzzImplClock>()),
                                     *vSyncTracker, mFdp.ConsumeIntegral<uint8_t>() /*pendingLimit*/,
                                     false);
 
-    reactor.startPeriodTransition(mFdp.ConsumeIntegral<nsecs_t>());
-    bool periodFlushed = mFdp.ConsumeBool();
+    reactor.startPeriodTransition(mFdp.ConsumeIntegral<nsecs_t>(), mFdp.ConsumeBool());
+    bool periodFlushed = false; // Value does not matter, since this is an out
+                                // param from addHwVsyncTimestamp.
     reactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed);
     reactor.addHwVsyncTimestamp(mFdp.ConsumeIntegral<nsecs_t>() /*newPeriod*/, std::nullopt,
                                 &periodFlushed);
diff --git a/services/surfaceflinger/tests/WindowInfosListener_test.cpp b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
index 53c3c39..d71486f 100644
--- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp
+++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
@@ -18,61 +18,61 @@
 #include <gui/SurfaceComposerClient.h>
 #include <private/android_filesystem_config.h>
 #include <future>
-#include "utils/TransactionUtils.h"
 
 namespace android {
 using Transaction = SurfaceComposerClient::Transaction;
 using gui::DisplayInfo;
 using gui::WindowInfo;
 
+using WindowInfosPredicate = std::function<bool(const std::vector<WindowInfo>&)>;
+
 class WindowInfosListenerTest : public ::testing::Test {
 protected:
     void SetUp() override {
         seteuid(AID_SYSTEM);
         mClient = sp<SurfaceComposerClient>::make();
-        mWindowInfosListener = sp<SyncWindowInfosListener>::make();
-        mClient->addWindowInfosListener(mWindowInfosListener);
     }
 
-    void TearDown() override {
-        mClient->removeWindowInfosListener(mWindowInfosListener);
-        seteuid(AID_ROOT);
-    }
+    void TearDown() override { seteuid(AID_ROOT); }
 
-    struct SyncWindowInfosListener : public gui::WindowInfosListener {
+    struct WindowInfosListener : public gui::WindowInfosListener {
     public:
+        WindowInfosListener(WindowInfosPredicate predicate, std::promise<void>& promise)
+              : mPredicate(std::move(predicate)), mPromise(promise) {}
+
         void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
                                   const std::vector<DisplayInfo>&) override {
-            windowInfosPromise.set_value(windowInfos);
-        }
-
-        std::vector<WindowInfo> waitForWindowInfos() {
-            std::future<std::vector<WindowInfo>> windowInfosFuture =
-                    windowInfosPromise.get_future();
-            std::vector<WindowInfo> windowInfos = windowInfosFuture.get();
-            windowInfosPromise = std::promise<std::vector<WindowInfo>>();
-            return windowInfos;
+            if (mPredicate(windowInfos)) {
+                mPromise.set_value();
+            }
         }
 
     private:
-        std::promise<std::vector<WindowInfo>> windowInfosPromise;
+        WindowInfosPredicate mPredicate;
+        std::promise<void>& mPromise;
     };
 
     sp<SurfaceComposerClient> mClient;
-    sp<SyncWindowInfosListener> mWindowInfosListener;
+
+    bool waitForWindowInfosPredicate(WindowInfosPredicate predicate) {
+        std::promise<void> promise;
+        auto listener = sp<WindowInfosListener>::make(std::move(predicate), promise);
+        mClient->addWindowInfosListener(listener);
+        auto future = promise.get_future();
+        bool satisfied = future.wait_for(std::chrono::seconds{1}) == std::future_status::ready;
+        mClient->removeWindowInfosListener(listener);
+        return satisfied;
+    }
 };
 
 std::optional<WindowInfo> findMatchingWindowInfo(WindowInfo targetWindowInfo,
                                                  std::vector<WindowInfo> windowInfos) {
-    std::optional<WindowInfo> foundWindowInfo = std::nullopt;
     for (WindowInfo windowInfo : windowInfos) {
         if (windowInfo.token == targetWindowInfo.token) {
-            foundWindowInfo = std::make_optional<>(windowInfo);
-            break;
+            return windowInfo;
         }
     }
-
-    return foundWindowInfo;
+    return std::nullopt;
 }
 
 TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) {
@@ -92,15 +92,17 @@
             .setInputWindowInfo(surfaceControl, windowInfo)
             .apply();
 
-    std::vector<WindowInfo> windowInfos = mWindowInfosListener->waitForWindowInfos();
-    std::optional<WindowInfo> foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos);
-    ASSERT_NE(std::nullopt, foundWindowInfo);
+    auto windowPresent = [&](const std::vector<WindowInfo>& windowInfos) {
+        return findMatchingWindowInfo(windowInfo, windowInfos).has_value();
+    };
+    ASSERT_TRUE(waitForWindowInfosPredicate(windowPresent));
 
     Transaction().reparent(surfaceControl, nullptr).apply();
 
-    windowInfos = mWindowInfosListener->waitForWindowInfos();
-    foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos);
-    ASSERT_EQ(std::nullopt, foundWindowInfo);
+    auto windowNotPresent = [&](const std::vector<WindowInfo>& windowInfos) {
+        return !findMatchingWindowInfo(windowInfo, windowInfos).has_value();
+    };
+    ASSERT_TRUE(waitForWindowInfosPredicate(windowNotPresent));
 }
 
 TEST_F(WindowInfosListenerTest, WindowInfoChanged) {
@@ -121,19 +123,28 @@
             .setInputWindowInfo(surfaceControl, windowInfo)
             .apply();
 
-    std::vector<WindowInfo> windowInfos = mWindowInfosListener->waitForWindowInfos();
-    std::optional<WindowInfo> foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos);
-    ASSERT_NE(std::nullopt, foundWindowInfo);
-    ASSERT_TRUE(foundWindowInfo->touchableRegion.isEmpty());
+    auto windowIsPresentAndTouchableRegionEmpty = [&](const std::vector<WindowInfo>& windowInfos) {
+        auto foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos);
+        if (!foundWindowInfo) {
+            return false;
+        }
+        return foundWindowInfo->touchableRegion.isEmpty();
+    };
+    ASSERT_TRUE(waitForWindowInfosPredicate(windowIsPresentAndTouchableRegionEmpty));
 
     Rect touchableRegions(0, 0, 50, 50);
     windowInfo.addTouchableRegion(Rect(0, 0, 50, 50));
     Transaction().setInputWindowInfo(surfaceControl, windowInfo).apply();
 
-    windowInfos = mWindowInfosListener->waitForWindowInfos();
-    foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos);
-    ASSERT_NE(std::nullopt, foundWindowInfo);
-    ASSERT_TRUE(foundWindowInfo->touchableRegion.hasSameRects(windowInfo.touchableRegion));
+    auto windowIsPresentAndTouchableRegionMatches =
+            [&](const std::vector<WindowInfo>& windowInfos) {
+                auto foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos);
+                if (!foundWindowInfo) {
+                    return false;
+                }
+                return foundWindowInfo->touchableRegion.hasSameRects(windowInfo.touchableRegion);
+            };
+    ASSERT_TRUE(waitForWindowInfosPredicate(windowIsPresentAndTouchableRegionMatches));
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
index 0e214af..5f9214c 100644
--- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
+++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
@@ -91,6 +91,8 @@
     uint64_t curr_frame;
     float x;
     float y;
+    uint32_t bufferWidth;
+    uint32_t bufferHeight;
 };
 
 bool operator==(const LayerInfo& lh, const LayerInfo& rh) {
@@ -105,7 +107,8 @@
 inline void PrintTo(const LayerInfo& info, ::std::ostream* os) {
     *os << "Layer [" << info.id << "] name=" << info.name << " parent=" << info.parent
         << " z=" << info.z << " curr_frame=" << info.curr_frame << " x=" << info.x
-        << " y=" << info.y;
+        << " y=" << info.y << " bufferWidth=" << info.bufferWidth
+        << " bufferHeight=" << info.bufferHeight;
 }
 
 struct find_id : std::unary_function<LayerInfo, bool> {
@@ -114,6 +117,18 @@
     bool operator()(LayerInfo const& m) const { return m.id == id; }
 };
 
+static LayerInfo getLayerInfoFromProto(::android::surfaceflinger::LayerProto& proto) {
+    return {proto.id(),
+            proto.name(),
+            proto.parent(),
+            proto.z(),
+            proto.curr_frame(),
+            proto.has_position() ? proto.position().x() : -1,
+            proto.has_position() ? proto.position().y() : -1,
+            proto.has_active_buffer() ? proto.active_buffer().width() : 0,
+            proto.has_active_buffer() ? proto.active_buffer().height() : 0};
+}
+
 TEST_P(TransactionTraceTestSuite, validateEndState) {
     ASSERT_GT(mActualLayersTraceProto.entry_size(), 0);
     ASSERT_GT(mExpectedLayersTraceProto.entry_size(), 0);
@@ -128,10 +143,7 @@
     expectedLayers.reserve(static_cast<size_t>(expectedLastEntry.layers().layers_size()));
     for (int i = 0; i < expectedLastEntry.layers().layers_size(); i++) {
         auto layer = expectedLastEntry.layers().layers(i);
-        expectedLayers.push_back({layer.id(), layer.name(), layer.parent(), layer.z(),
-                                  layer.curr_frame(),
-                                  layer.has_position() ? layer.position().x() : -1,
-                                  layer.has_position() ? layer.position().y() : -1});
+        expectedLayers.push_back(getLayerInfoFromProto(layer));
     }
     std::sort(expectedLayers.begin(), expectedLayers.end(), compareById);
 
@@ -139,10 +151,7 @@
     actualLayers.reserve(static_cast<size_t>(actualLastEntry.layers().layers_size()));
     for (int i = 0; i < actualLastEntry.layers().layers_size(); i++) {
         auto layer = actualLastEntry.layers().layers(i);
-        actualLayers.push_back({layer.id(), layer.name(), layer.parent(), layer.z(),
-                                layer.curr_frame(),
-                                layer.has_position() ? layer.position().x() : -1,
-                                layer.has_position() ? layer.position().y() : -1});
+        actualLayers.push_back(getLayerInfoFromProto(layer));
     }
     std::sort(actualLayers.begin(), actualLayers.end(), compareById);
 
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 87c3c65..eac9edc 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -105,6 +105,7 @@
         "SurfaceFlinger_DisplayTransactionCommitTest.cpp",
         "SurfaceFlinger_GetDisplayNativePrimariesTest.cpp",
         "SurfaceFlinger_HotplugTest.cpp",
+        "SurfaceFlinger_MultiDisplayLeaderTest.cpp",
         "SurfaceFlinger_NotifyPowerBoostTest.cpp",
         "SurfaceFlinger_OnInitializeDisplaysTest.cpp",
         "SurfaceFlinger_PowerHintTest.cpp",
@@ -133,6 +134,7 @@
         "VSyncPredictorTest.cpp",
         "VSyncReactorTest.cpp",
         "VsyncConfigurationTest.cpp",
+        "VsyncScheduleTest.cpp",
     ],
 }
 
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 17ef6ff..19a93e1 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -284,13 +284,16 @@
         constexpr auto kDisplayConnectionType = ui::DisplayConnectionType::Internal;
         constexpr bool kIsPrimary = true;
 
-        test->mDisplay = FakeDisplayDeviceInjector(test->mFlinger, compositionDisplay,
-                                                   kDisplayConnectionType, HWC_DISPLAY, kIsPrimary)
-                                 .setDisplaySurface(test->mDisplaySurface)
-                                 .setNativeWindow(test->mNativeWindow)
-                                 .setSecure(Derived::IS_SECURE)
-                                 .setPowerMode(Derived::INIT_POWER_MODE)
-                                 .inject();
+        test->mDisplay =
+                FakeDisplayDeviceInjector(test->mFlinger, compositionDisplay,
+                                          kDisplayConnectionType, HWC_DISPLAY, kIsPrimary)
+                        .setDisplaySurface(test->mDisplaySurface)
+                        .setNativeWindow(test->mNativeWindow)
+                        .setSecure(Derived::IS_SECURE)
+                        .setPowerMode(Derived::INIT_POWER_MODE)
+                        .setRefreshRateSelector(test->mFlinger.scheduler()->refreshRateSelector())
+                        .skipRegisterDisplay()
+                        .inject();
         Mock::VerifyAndClear(test->mNativeWindow.get());
 
         constexpr bool kIsInternal = kDisplayConnectionType == ui::DisplayConnectionType::Internal;
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index 52dc695..e32cf88 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -77,8 +77,8 @@
                                                              mock::EventThread::kCallingUid,
                                                              ResyncCallback())));
 
-    mFlinger.setupScheduler(std::unique_ptr<scheduler::VsyncController>(mVsyncController),
-                            std::unique_ptr<scheduler::VSyncTracker>(mVSyncTracker),
+    mFlinger.setupScheduler(std::make_unique<mock::VsyncController>(),
+                            std::make_shared<mock::VSyncTracker>(),
                             std::unique_ptr<EventThread>(mEventThread),
                             std::unique_ptr<EventThread>(mSFEventThread),
                             TestableSurfaceFlinger::DefaultDisplayMode{displayId},
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 23a8bd1..e64cb38 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -128,8 +128,6 @@
     renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
     Hwc2::mock::Composer* mComposer = nullptr;
 
-    mock::VsyncController* mVsyncController = new mock::VsyncController;
-    mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker;
     mock::EventThread* mEventThread = new mock::EventThread;
     mock::EventThread* mSFEventThread = new mock::EventThread;
 
@@ -503,14 +501,16 @@
     static constexpr auto GET_IDENTIFICATION_DATA = getInternalEdid;
 };
 
-template <bool hasIdentificationData>
-struct ExternalDisplay {
-    static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::External;
+template <ui::DisplayConnectionType connectionType, bool hasIdentificationData>
+struct SecondaryDisplay {
+    static constexpr auto CONNECTION_TYPE = connectionType;
     static constexpr Primary PRIMARY = Primary::FALSE;
     static constexpr uint8_t PORT = 254;
     static constexpr HWDisplayId HWC_DISPLAY_ID = 1002;
     static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData;
-    static constexpr auto GET_IDENTIFICATION_DATA = getExternalEdid;
+    static constexpr auto GET_IDENTIFICATION_DATA =
+            connectionType == ui::DisplayConnectionType::Internal ? getInternalEdid
+                                                                  : getExternalEdid;
 };
 
 struct TertiaryDisplay {
@@ -521,7 +521,16 @@
 };
 
 using PrimaryDisplayVariant = PhysicalDisplayVariant<PrimaryDisplay<false>, 3840, 2160>;
-using ExternalDisplayVariant = PhysicalDisplayVariant<ExternalDisplay<false>, 1920, 1280>;
+
+using InnerDisplayVariant = PhysicalDisplayVariant<PrimaryDisplay<true>, 1840, 2208>;
+using OuterDisplayVariant =
+        PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::Internal, true>, 1080,
+                               2092>;
+
+using ExternalDisplayVariant =
+        PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::External, false>, 1920,
+                               1280>;
+
 using TertiaryDisplayVariant = PhysicalDisplayVariant<TertiaryDisplay, 1600, 1200>;
 
 // A virtual display not supported by the HWC.
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index f6bcadc..f1cdca3 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -125,7 +125,7 @@
     ConnectionEventRecorder mConnectionEventCallRecorder{0};
     ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0};
 
-    std::unique_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
+    std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
     std::unique_ptr<impl::EventThread> mThread;
     sp<MockEventThreadConnection> mConnection;
     sp<MockEventThreadConnection> mThrottledConnection;
@@ -140,12 +140,12 @@
             ::testing::UnitTest::GetInstance()->current_test_info();
     ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
 
-    mVsyncSchedule = std::unique_ptr<scheduler::VsyncSchedule>(
-            new scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(),
-                                         std::make_unique<mock::VSyncDispatch>(), nullptr));
-
-    mock::VSyncDispatch& mockDispatch =
-            *static_cast<mock::VSyncDispatch*>(&mVsyncSchedule->getDispatch());
+    auto mockDispatchPtr = std::make_shared<mock::VSyncDispatch>();
+    mVsyncSchedule = std::shared_ptr<scheduler::VsyncSchedule>(
+            new scheduler::VsyncSchedule(INTERNAL_DISPLAY_ID,
+                                         std::make_shared<mock::VSyncTracker>(), mockDispatchPtr,
+                                         nullptr));
+    mock::VSyncDispatch& mockDispatch = *mockDispatchPtr;
     EXPECT_CALL(mockDispatch, registerCallback(_, _))
             .WillRepeatedly(Invoke(mVSyncCallbackRegisterRecorder.getInvocable()));
     EXPECT_CALL(mockDispatch, schedule(_, _))
@@ -189,10 +189,9 @@
     };
 
     mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
-    mThread =
-            std::make_unique<impl::EventThread>(/*std::move(source), */ "EventThreadTest",
-                                                *mVsyncSchedule, mTokenManager.get(), throttleVsync,
-                                                getVsyncPeriod, kWorkDuration, kReadyDuration);
+    mThread = std::make_unique<impl::EventThread>("EventThreadTest", mVsyncSchedule,
+                                                  mTokenManager.get(), throttleVsync,
+                                                  getVsyncPeriod, kWorkDuration, kReadyDuration);
 
     // EventThread should register itself as VSyncSource callback.
     EXPECT_TRUE(mVSyncCallbackRegisterRecorder.waitForCall().has_value());
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index 7aa5201..8f1b450 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -67,12 +67,12 @@
 
 struct MessageQueueTest : testing::Test {
     void SetUp() override {
-        EXPECT_CALL(mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken));
+        EXPECT_CALL(*mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken));
         EXPECT_NO_FATAL_FAILURE(mEventQueue.initVsync(mVSyncDispatch, mTokenManager, kDuration));
-        EXPECT_CALL(mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
+        EXPECT_CALL(*mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
     }
 
-    mock::VSyncDispatch mVSyncDispatch;
+    std::shared_ptr<mock::VSyncDispatch> mVSyncDispatch = std::make_shared<mock::VSyncDispatch>();
     MockTokenManager mTokenManager;
     TestableMessageQueue mEventQueue;
 
@@ -90,7 +90,7 @@
                                                                  .earliestVsync = 0};
     EXPECT_FALSE(mEventQueue.getScheduledFrameTime());
 
-    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
+    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 
     ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
@@ -103,13 +103,13 @@
                                                                  .readyDuration = 0,
                                                                  .earliestVsync = 0};
 
-    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
+    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 
     ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
     EXPECT_EQ(1234, mEventQueue.getScheduledFrameTime()->time_since_epoch().count());
 
-    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567));
+    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 
     ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
@@ -122,7 +122,7 @@
                                                                  .readyDuration = 0,
                                                                  .earliestVsync = 0};
 
-    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
+    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 
     ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
@@ -149,7 +149,7 @@
                                                      .readyDuration = 0,
                                                      .earliestVsync = kPresentTime.ns()};
 
-    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0));
+    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 }
 
@@ -161,7 +161,7 @@
                                                      .readyDuration = 0,
                                                      .earliestVsync = 0};
 
-    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0));
+    EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 }
 
diff --git a/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp b/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp
index aafc323..d08e12c 100644
--- a/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp
@@ -224,7 +224,8 @@
     EXPECT_FALSE(mResetTimerCallback.waitForUnexpectedCall().has_value());
 }
 
-TEST_F(OneShotTimerTest, noCallbacksAfterStopAndResetTest) {
+// TODO(b/186417847) This test is flaky. Reenable once fixed.
+TEST_F(OneShotTimerTest, DISABLED_noCallbacksAfterStopAndResetTest) {
     fake::FakeClock* clock = new fake::FakeClock();
     mIdleTimer = std::make_unique<scheduler::OneShotTimer>("TestTimer", 1ms,
                                                            mResetTimerCallback.getInvocable(),
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 4b15385..26281d2 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <ftl/fake_guard.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <log/log.h>
@@ -122,12 +123,6 @@
     EXPECT_CALL(*mEventThread, onHotplugReceived(_, _)).Times(0);
     mScheduler->onHotplugReceived(handle, kDisplayId1, false);
 
-    EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(0);
-    mScheduler->onScreenAcquired(handle);
-
-    EXPECT_CALL(*mEventThread, onScreenReleased()).Times(0);
-    mScheduler->onScreenReleased(handle);
-
     std::string output;
     EXPECT_CALL(*mEventThread, dump(_)).Times(0);
     mScheduler->dump(handle, output);
@@ -147,12 +142,6 @@
     EXPECT_CALL(*mEventThread, onHotplugReceived(kDisplayId1, false)).Times(1);
     mScheduler->onHotplugReceived(mConnectionHandle, kDisplayId1, false);
 
-    EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(1);
-    mScheduler->onScreenAcquired(mConnectionHandle);
-
-    EXPECT_CALL(*mEventThread, onScreenReleased()).Times(1);
-    mScheduler->onScreenReleased(mConnectionHandle);
-
     std::string output("dump");
     EXPECT_CALL(*mEventThread, dump(output)).Times(1);
     mScheduler->dump(mConnectionHandle, output);
@@ -176,7 +165,7 @@
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
 
     constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON;
-    mScheduler->setDisplayPowerMode(kPowerModeOn);
+    FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn));
 
     constexpr uint32_t kDisplayArea = 999'999;
     mScheduler->onActiveDisplayAreaChanged(kDisplayArea);
@@ -248,7 +237,7 @@
     mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
 
     constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON;
-    mScheduler->setDisplayPowerMode(kPowerModeOn);
+    FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn));
 
     constexpr uint32_t kDisplayArea = 999'999;
     mScheduler->onActiveDisplayAreaChanged(kDisplayArea);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index f436a58..fd1fd47 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -50,7 +50,7 @@
         mFlinger.configureAndCommit();
 
         mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
-                           .setDisplayModes(kModes, kModeId60, std::move(selectorPtr))
+                           .setRefreshRateSelector(std::move(selectorPtr))
                            .inject();
 
         // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy
@@ -100,7 +100,7 @@
                                                              ResyncCallback())));
 
     auto vsyncController = std::make_unique<mock::VsyncController>();
-    auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
+    auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
 
     EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
     EXPECT_CALL(*vsyncTracker, currentPeriod())
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp
new file mode 100644
index 0000000..9c58943
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "DisplayTransactionTestHelpers.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace {
+
+struct MultiDisplayLeaderTest : DisplayTransactionTest {
+    static constexpr bool kWithMockScheduler = false;
+    MultiDisplayLeaderTest() : DisplayTransactionTest(kWithMockScheduler) {}
+};
+
+TEST_F(MultiDisplayLeaderTest, foldable) {
+    injectMockScheduler(InnerDisplayVariant::DISPLAY_ID::get());
+
+    // Inject inner and outer displays with uninitialized power modes.
+    sp<DisplayDevice> innerDisplay, outerDisplay;
+    constexpr bool kInitPowerMode = false;
+    {
+        InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
+        auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
+        injector.setPowerMode(std::nullopt);
+        injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
+        innerDisplay = injector.inject();
+    }
+    {
+        OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
+        auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
+        injector.setPowerMode(std::nullopt);
+        outerDisplay = injector.inject();
+    }
+
+    // When the device boots, the inner display should be the leader.
+    ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId());
+
+    // ...and should still be after powering on.
+    mFlinger.setPowerModeInternal(innerDisplay, PowerMode::ON);
+    ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId());
+
+    // The outer display should become the leader after folding.
+    mFlinger.setPowerModeInternal(innerDisplay, PowerMode::OFF);
+    mFlinger.setPowerModeInternal(outerDisplay, PowerMode::ON);
+    ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), outerDisplay->getPhysicalId());
+
+    // The inner display should become the leader after unfolding.
+    mFlinger.setPowerModeInternal(outerDisplay, PowerMode::OFF);
+    mFlinger.setPowerModeInternal(innerDisplay, PowerMode::ON);
+    ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId());
+
+    // The inner display should stay the leader if both are powered on.
+    // TODO(b/256196556): The leader should depend on the displays' VSYNC phases.
+    mFlinger.setPowerModeInternal(outerDisplay, PowerMode::ON);
+    ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId());
+
+    // The outer display should become the leader if designated.
+    mFlinger.scheduler()->setLeaderDisplay(outerDisplay->getPhysicalId());
+    ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), outerDisplay->getPhysicalId());
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp
index f553a23..98644aa 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp
@@ -44,7 +44,10 @@
     // We expect a scheduled commit for the display transaction.
     EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
-    EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    EXPECT_CALL(static_cast<mock::VSyncTracker&>(
+                        mFlinger.scheduler()->getVsyncSchedule()->getTracker()),
+                nextAnticipatedVSyncTimeFrom(_))
+            .WillRepeatedly(Return(0));
 
     // --------------------------------------------------------------------
     // Invocation
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
index 9699fc2..7839ef0 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -92,6 +92,8 @@
                     .setDisplaySurface(mDisplaySurface)
                     .setNativeWindow(mNativeWindow)
                     .setPowerMode(hal::PowerMode::ON)
+                    .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector())
+                    .skipRegisterDisplay()
                     .inject();
 }
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index a0aaa68..7754c21 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -59,54 +59,45 @@
 };
 
 struct EventThreadBaseSupportedVariant {
-    static void setupVsyncAndEventThreadNoCallExpectations(DisplayTransactionTest* test) {
-        // The callback should not be notified to toggle VSYNC.
-        EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_)).Times(0);
-
-        // The event thread should not be notified.
-        EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0);
-        EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(0);
+    static void setupVsyncNoCallExpectations(DisplayTransactionTest* test) {
+        // Expect no change to hardware nor synthetic VSYNC.
+        EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, _)).Times(0);
+        EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(_)).Times(0);
     }
 };
 
 struct EventThreadNotSupportedVariant : public EventThreadBaseSupportedVariant {
-    static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) {
-        // These calls are only expected for the primary display.
-
-        // Instead expect no calls.
-        setupVsyncAndEventThreadNoCallExpectations(test);
+    static void setupEnableVsyncCallExpectations(DisplayTransactionTest* test) {
+        setupVsyncNoCallExpectations(test);
     }
 
-    static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) {
-        // These calls are only expected for the primary display.
-
-        // Instead expect no calls.
-        setupVsyncAndEventThreadNoCallExpectations(test);
+    static void setupDisableVsyncCallExpectations(DisplayTransactionTest* test) {
+        setupVsyncNoCallExpectations(test);
     }
 };
 
 struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant {
-    static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) {
-        // The callback should be notified to enable VSYNC.
-        EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(true)).Times(1);
-
-        // The event thread should be notified that the screen was acquired.
-        EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(1);
+    static void setupEnableVsyncCallExpectations(DisplayTransactionTest* test) {
+        // Expect to enable hardware VSYNC and disable synthetic VSYNC.
+        EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, true)).Times(1);
+        EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(false)).Times(1);
     }
 
-    static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) {
-        // The callback should be notified to disable VSYNC.
-        EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(false)).Times(1);
-
-        // The event thread should not be notified that the screen was released.
-        EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(1);
+    static void setupDisableVsyncCallExpectations(DisplayTransactionTest* test) {
+        // Expect to disable hardware VSYNC and enable synthetic VSYNC.
+        EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, false)).Times(1);
+        EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(true)).Times(1);
     }
 };
 
 struct DispSyncIsSupportedVariant {
     static void setupResetModelCallExpectations(DisplayTransactionTest* test) {
-        EXPECT_CALL(*test->mVsyncController, startPeriodTransition(DEFAULT_VSYNC_PERIOD)).Times(1);
-        EXPECT_CALL(*test->mVSyncTracker, resetModel()).Times(1);
+        auto vsyncSchedule = test->mFlinger.scheduler()->getVsyncSchedule();
+        EXPECT_CALL(static_cast<mock::VsyncController&>(vsyncSchedule->getController()),
+                    startPeriodTransition(DEFAULT_VSYNC_PERIOD, false))
+                .Times(1);
+        EXPECT_CALL(static_cast<mock::VSyncTracker&>(vsyncSchedule->getTracker()), resetModel())
+                .Times(1);
     }
 };
 
@@ -133,7 +124,7 @@
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
         Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON);
-        Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test);
+        Case::EventThread::setupEnableVsyncCallExpectations(test);
         Case::DispSync::setupResetModelCallExpectations(test);
         Case::setupRepaintEverythingCallExpectations(test);
     }
@@ -148,7 +139,7 @@
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
         Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE_SUSPEND);
-        Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
+        Case::EventThread::setupVsyncNoCallExpectations(test);
         Case::setupRepaintEverythingCallExpectations(test);
     }
 
@@ -160,7 +151,7 @@
 struct TransitionOnToOffVariant : public TransitionVariantCommon<PowerMode::ON, PowerMode::OFF> {
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
-        Case::EventThread::setupReleaseAndDisableVsyncCallExpectations(test);
+        Case::EventThread::setupDisableVsyncCallExpectations(test);
         Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::OFF);
     }
 
@@ -173,7 +164,7 @@
       : public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::OFF> {
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
-        Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
+        Case::EventThread::setupVsyncNoCallExpectations(test);
         Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::OFF);
     }
 
@@ -185,7 +176,7 @@
 struct TransitionOnToDozeVariant : public TransitionVariantCommon<PowerMode::ON, PowerMode::DOZE> {
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
-        Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
+        Case::EventThread::setupVsyncNoCallExpectations(test);
         Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE);
     }
 };
@@ -194,7 +185,7 @@
       : public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::DOZE> {
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
-        Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test);
+        Case::EventThread::setupEnableVsyncCallExpectations(test);
         Case::DispSync::setupResetModelCallExpectations(test);
         Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE);
     }
@@ -203,7 +194,7 @@
 struct TransitionDozeToOnVariant : public TransitionVariantCommon<PowerMode::DOZE, PowerMode::ON> {
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
-        Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
+        Case::EventThread::setupVsyncNoCallExpectations(test);
         Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON);
     }
 };
@@ -212,7 +203,7 @@
       : public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::ON> {
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
-        Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test);
+        Case::EventThread::setupEnableVsyncCallExpectations(test);
         Case::DispSync::setupResetModelCallExpectations(test);
         Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON);
     }
@@ -222,7 +213,7 @@
       : public TransitionVariantCommon<PowerMode::ON, PowerMode::DOZE_SUSPEND> {
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
-        Case::EventThread::setupReleaseAndDisableVsyncCallExpectations(test);
+        Case::EventThread::setupDisableVsyncCallExpectations(test);
         Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE_SUSPEND);
     }
 };
@@ -231,7 +222,7 @@
       : public TransitionVariantCommon<PowerMode::ON, static_cast<PowerMode>(POWER_MODE_LEET)> {
     template <typename Case>
     static void setupCallExpectations(DisplayTransactionTest* test) {
-        Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
+        Case::EventThread::setupVsyncNoCallExpectations(test);
         Case::setupNoComposerPowerModeCallExpectations(test);
     }
 };
@@ -262,8 +253,9 @@
         return display;
     }
 
-    static void setInitialHwVsyncEnabled(DisplayTransactionTest* test, bool enabled) {
-        test->mFlinger.scheduler()->setInitialHwVsyncEnabled(enabled);
+    static void setInitialHwVsyncEnabled(DisplayTransactionTest* test, PhysicalDisplayId id,
+                                         bool enabled) {
+        test->mFlinger.scheduler()->setInitialHwVsyncEnabled(id, enabled);
     }
 
     static void setupRepaintEverythingCallExpectations(DisplayTransactionTest* test) {
@@ -329,9 +321,12 @@
     Case::Doze::setupComposerCallExpectations(this);
     auto display =
             Case::injectDisplayWithInitialPowerMode(this, Case::Transition::INITIAL_POWER_MODE);
-    Case::setInitialHwVsyncEnabled(this,
-                                   PowerModeInitialVSyncEnabled<
-                                           Case::Transition::INITIAL_POWER_MODE>::value);
+    auto displayId = display->getId();
+    if (auto physicalDisplayId = PhysicalDisplayId::tryCast(displayId)) {
+        Case::setInitialHwVsyncEnabled(this, *physicalDisplayId,
+                                       PowerModeInitialVSyncEnabled<
+                                               Case::Transition::INITIAL_POWER_MODE>::value);
+    }
 
     // --------------------------------------------------------------------
     // Call Expectations
@@ -484,38 +479,5 @@
     transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOnToUnknownVariant>>();
 }
 
-// TODO(b/262417075)
-TEST_F(SetPowerModeInternalTest, DISABLED_designatesLeaderDisplay) {
-    using Case = SimplePrimaryDisplayCase;
-
-    // --------------------------------------------------------------------
-    // Preconditions
-
-    // Inject a primary display with uninitialized power mode.
-    constexpr bool kInitPowerMode = false;
-    Case::Display::injectHwcDisplay<kInitPowerMode>(this);
-    auto injector = Case::Display::makeFakeExistingDisplayInjector(this);
-    injector.setPowerMode(std::nullopt);
-    const auto display = injector.inject();
-
-    // --------------------------------------------------------------------
-    // Invocation
-
-    // FakeDisplayDeviceInjector registers the display with Scheduler, so it has already been
-    // designated as the leader. Set an arbitrary leader to verify that `setPowerModeInternal`
-    // designates a leader regardless of any preceding `Scheduler::registerDisplay` call(s).
-    constexpr PhysicalDisplayId kPlaceholderId = PhysicalDisplayId::fromPort(42);
-    ASSERT_NE(display->getPhysicalId(), kPlaceholderId);
-    mFlinger.scheduler()->setLeaderDisplay(kPlaceholderId);
-
-    mFlinger.setPowerModeInternal(display, PowerMode::ON);
-
-    // --------------------------------------------------------------------
-    // Postconditions
-
-    // The primary display should be designated as the leader.
-    EXPECT_EQ(mFlinger.scheduler()->leaderDisplayId(), display->getPhysicalId());
-}
-
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index bd3f3ca..ac04720 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -37,19 +37,16 @@
 public:
     TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback)
           : TestableScheduler(std::make_unique<mock::VsyncController>(),
-                              std::make_unique<mock::VSyncTracker>(), std::move(selectorPtr),
+                              std::make_shared<mock::VSyncTracker>(), std::move(selectorPtr),
                               /* modulatorPtr */ nullptr, callback) {}
 
     TestableScheduler(std::unique_ptr<VsyncController> controller,
-                      std::unique_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
+                      std::shared_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
                       sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback)
           : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) {
-        mVsyncSchedule = std::unique_ptr<VsyncSchedule>(
-                new VsyncSchedule(std::move(tracker), std::make_unique<mock::VSyncDispatch>(),
-                                  std::move(controller)));
-
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
-        registerDisplay(displayId, std::move(selectorPtr));
+        registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
+                        std::move(tracker));
 
         ON_CALL(*this, postMessage).WillByDefault([](sp<MessageHandler>&& handler) {
             // Execute task to prevent broken promise exception on destruction.
@@ -73,8 +70,21 @@
     }
 
     void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
+        registerDisplay(displayId, std::move(selectorPtr),
+                        std::make_unique<mock::VsyncController>(),
+                        std::make_shared<mock::VSyncTracker>());
+    }
+
+    void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+                         std::unique_ptr<VsyncController> controller,
+                         std::shared_ptr<VSyncTracker> tracker) {
         ftl::FakeGuard guard(kMainThreadContext);
-        Scheduler::registerDisplay(displayId, std::move(selectorPtr));
+        Scheduler::registerDisplayInternal(displayId, std::move(selectorPtr),
+                                           std::shared_ptr<VsyncSchedule>(
+                                                   new VsyncSchedule(displayId, std::move(tracker),
+                                                                     std::make_shared<
+                                                                             mock::VSyncDispatch>(),
+                                                                     std::move(controller))));
     }
 
     void unregisterDisplay(PhysicalDisplayId displayId) {
@@ -91,6 +101,7 @@
         Scheduler::setLeaderDisplay(displayId);
     }
 
+    auto& mutableAppConnectionHandle() { return mAppConnectionHandle; }
     auto& mutableVsyncModulator() { return *mVsyncModulator; }
     auto& mutableLayerHistory() { return mLayerHistory; }
 
@@ -150,10 +161,11 @@
         Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
     }
 
-    void setInitialHwVsyncEnabled(bool enabled) {
-        std::lock_guard<std::mutex> lock(mVsyncSchedule->mHwVsyncLock);
-        mVsyncSchedule->mHwVsyncState = enabled ? VsyncSchedule::HwVsyncState::Enabled
-                                                : VsyncSchedule::HwVsyncState::Disabled;
+    void setInitialHwVsyncEnabled(PhysicalDisplayId id, bool enabled) {
+        auto schedule = getVsyncSchedule(id);
+        std::lock_guard<std::mutex> lock(schedule->mHwVsyncLock);
+        schedule->mHwVsyncState = enabled ? VsyncSchedule::HwVsyncState::Enabled
+                                          : VsyncSchedule::HwVsyncState::Disabled;
     }
 
 private:
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index dbde3b1..6334ec8 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -203,7 +203,7 @@
     using DisplayModesVariant = std::variant<DefaultDisplayMode, RefreshRateSelectorPtr>;
 
     void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
-                        std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
+                        std::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
                         std::unique_ptr<EventThread> appEventThread,
                         std::unique_ptr<EventThread> sfEventThread,
                         DisplayModesVariant modesVariant,
@@ -250,9 +250,12 @@
                                                           std::move(modulatorPtr), callback);
         }
 
-        mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), *mTokenManager, 0ms);
+        mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), *mTokenManager, 0ms);
 
-        mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
+        mScheduler->mutableAppConnectionHandle() =
+                mScheduler->createConnection(std::move(appEventThread));
+
+        mFlinger->mAppConnectionHandle = mScheduler->mutableAppConnectionHandle();
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
         resetScheduler(mScheduler);
     }
@@ -277,7 +280,7 @@
                                                                  ResyncCallback())));
 
         auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock);
-        auto vsyncTracker = makeMock<mock::VSyncTracker>(options.useNiceMock);
+        auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock);
 
         EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
         EXPECT_CALL(*vsyncTracker, currentPeriod())
@@ -801,16 +804,16 @@
             return mFlinger.mutableDisplays().get(mDisplayToken)->get();
         }
 
-        // If `selectorPtr` is nullptr, the injector creates RefreshRateSelector from the `modes`.
-        // Otherwise, it uses `selectorPtr`, which the caller must create using the same `modes`.
-        //
-        // TODO(b/182939859): Once `modes` can be retrieved from RefreshRateSelector, remove
-        // the `selectorPtr` parameter in favor of an alternative setRefreshRateSelector API.
-        auto& setDisplayModes(
-                DisplayModes modes, DisplayModeId activeModeId,
-                std::shared_ptr<scheduler::RefreshRateSelector> selectorPtr = nullptr) {
+        auto& setDisplayModes(DisplayModes modes, DisplayModeId activeModeId) {
             mDisplayModes = std::move(modes);
             mCreationArgs.activeModeId = activeModeId;
+            mCreationArgs.refreshRateSelector = nullptr;
+            return *this;
+        }
+
+        auto& setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) {
+            mDisplayModes = selectorPtr->displayModes();
+            mCreationArgs.activeModeId = selectorPtr->getActiveMode().modePtr->getId();
             mCreationArgs.refreshRateSelector = std::move(selectorPtr);
             return *this;
         }
@@ -852,6 +855,11 @@
             return *this;
         }
 
+        auto& skipRegisterDisplay() {
+            mRegisterDisplay = false;
+            return *this;
+        }
+
         sp<DisplayDevice> inject() NO_THREAD_SAFETY_ANALYSIS {
             const auto displayId = mCreationArgs.compositionDisplay->getDisplayId();
 
@@ -915,7 +923,7 @@
                                                                       ui::ColorModes(),
                                                                       std::nullopt);
 
-                if (mFlinger.scheduler()) {
+                if (mFlinger.scheduler() && mRegisterDisplay) {
                     mFlinger.scheduler()->registerDisplay(physicalId,
                                                           display->holdRefreshRateSelector());
                 }
@@ -934,6 +942,7 @@
         sp<BBinder> mDisplayToken = sp<BBinder>::make();
         DisplayDeviceCreationArgs mCreationArgs;
         DisplayModes mDisplayModes;
+        bool mRegisterDisplay = true;
         const std::optional<ui::DisplayConnectionType> mConnectionType;
         const std::optional<hal::HWDisplayId> mHwcDisplayId;
     };
@@ -944,6 +953,11 @@
         return useNiceMock ? std::make_unique<testing::NiceMock<T>>() : std::make_unique<T>();
     }
 
+    template <typename T>
+    static std::shared_ptr<T> makeSharedMock(bool useNiceMock) {
+        return useNiceMock ? std::make_shared<testing::NiceMock<T>>() : std::make_shared<T>();
+    }
+
     static constexpr VsyncId kVsyncId{123};
 
     surfaceflinger::test::Factory mFactory;
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index 2908834..41866a1 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -109,7 +109,8 @@
 
 class RepeatingCallbackReceiver {
 public:
-    RepeatingCallbackReceiver(VSyncDispatch& dispatch, nsecs_t workload, nsecs_t readyDuration)
+    RepeatingCallbackReceiver(std::shared_ptr<VSyncDispatch> dispatch, nsecs_t workload,
+                              nsecs_t readyDuration)
           : mWorkload(workload),
             mReadyDuration(readyDuration),
             mCallback(
@@ -166,9 +167,10 @@
 };
 
 TEST_F(VSyncDispatchRealtimeTest, triple_alarm) {
-    FixedRateIdealStubTracker tracker;
-    VSyncDispatchTimerQueue dispatch(std::make_unique<Timer>(), tracker, mDispatchGroupThreshold,
-                                     mVsyncMoveThreshold);
+    auto tracker = std::make_shared<FixedRateIdealStubTracker>();
+    auto dispatch =
+            std::make_shared<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
+                                                      mDispatchGroupThreshold, mVsyncMoveThreshold);
 
     static size_t constexpr num_clients = 3;
     std::array<RepeatingCallbackReceiver, num_clients>
@@ -195,14 +197,15 @@
 // starts at 333hz, slides down to 43hz
 TEST_F(VSyncDispatchRealtimeTest, vascillating_vrr) {
     auto next_vsync_interval = toNs(3ms);
-    VRRStubTracker tracker(next_vsync_interval);
-    VSyncDispatchTimerQueue dispatch(std::make_unique<Timer>(), tracker, mDispatchGroupThreshold,
-                                     mVsyncMoveThreshold);
+    auto tracker = std::make_shared<VRRStubTracker>(next_vsync_interval);
+    auto dispatch =
+            std::make_shared<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
+                                                      mDispatchGroupThreshold, mVsyncMoveThreshold);
 
     RepeatingCallbackReceiver cb_receiver(dispatch, toNs(1ms), toNs(5ms));
 
     auto const on_each_frame = [&](nsecs_t last_known) {
-        tracker.set_interval(next_vsync_interval += toNs(1ms), last_known);
+        tracker->set_interval(next_vsync_interval += toNs(1ms), last_known);
     };
 
     std::thread eventThread([&] { cb_receiver.repeatedly_schedule(mIterations, on_each_frame); });
@@ -213,9 +216,10 @@
 
 // starts at 333hz, jumps to 200hz at frame 10
 TEST_F(VSyncDispatchRealtimeTest, fixed_jump) {
-    VRRStubTracker tracker(toNs(3ms));
-    VSyncDispatchTimerQueue dispatch(std::make_unique<Timer>(), tracker, mDispatchGroupThreshold,
-                                     mVsyncMoveThreshold);
+    auto tracker = std::make_shared<VRRStubTracker>(toNs(3ms));
+    auto dispatch =
+            std::make_shared<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
+                                                      mDispatchGroupThreshold, mVsyncMoveThreshold);
 
     RepeatingCallbackReceiver cb_receiver(dispatch, toNs(1ms), toNs(5ms));
 
@@ -223,7 +227,7 @@
     auto constexpr jump_frame_at = 10u;
     auto const on_each_frame = [&](nsecs_t last_known) {
         if (jump_frame_counter++ == jump_frame_at) {
-            tracker.set_interval(toNs(5ms), last_known);
+            tracker->set_interval(toNs(5ms), last_known);
         }
     };
     std::thread eventThread([&] { cb_receiver.repeatedly_schedule(mIterations, on_each_frame); });
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index f143b49..7af1da6 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -116,13 +116,14 @@
 
 class CountingCallback {
 public:
-    CountingCallback(VSyncDispatch& dispatch)
-          : mDispatch(dispatch),
-            mToken(dispatch.registerCallback(std::bind(&CountingCallback::counter, this,
-                                                       std::placeholders::_1, std::placeholders::_2,
-                                                       std::placeholders::_3),
-                                             "test")) {}
-    ~CountingCallback() { mDispatch.unregisterCallback(mToken); }
+    CountingCallback(std::shared_ptr<VSyncDispatch> dispatch)
+          : mDispatch(std::move(dispatch)),
+            mToken(mDispatch->registerCallback(std::bind(&CountingCallback::counter, this,
+                                                         std::placeholders::_1,
+                                                         std::placeholders::_2,
+                                                         std::placeholders::_3),
+                                               "test")) {}
+    ~CountingCallback() { mDispatch->unregisterCallback(mToken); }
 
     operator VSyncDispatch::CallbackToken() const { return mToken; }
 
@@ -132,7 +133,7 @@
         mReadyTime.push_back(readyTime);
     }
 
-    VSyncDispatch& mDispatch;
+    std::shared_ptr<VSyncDispatch> mDispatch;
     VSyncDispatch::CallbackToken mToken;
     std::vector<nsecs_t> mCalls;
     std::vector<nsecs_t> mWakeupTime;
@@ -141,12 +142,12 @@
 
 class PausingCallback {
 public:
-    PausingCallback(VSyncDispatch& dispatch, std::chrono::milliseconds pauseAmount)
-          : mDispatch(dispatch),
-            mToken(dispatch.registerCallback(std::bind(&PausingCallback::pause, this,
-                                                       std::placeholders::_1,
-                                                       std::placeholders::_2),
-                                             "test")),
+    PausingCallback(std::shared_ptr<VSyncDispatch> dispatch, std::chrono::milliseconds pauseAmount)
+          : mDispatch(std::move(dispatch)),
+            mToken(mDispatch->registerCallback(std::bind(&PausingCallback::pause, this,
+                                                         std::placeholders::_1,
+                                                         std::placeholders::_2),
+                                               "test")),
             mRegistered(true),
             mPauseAmount(pauseAmount) {}
     ~PausingCallback() { unregister(); }
@@ -181,12 +182,12 @@
 
     void unregister() {
         if (mRegistered) {
-            mDispatch.unregisterCallback(mToken);
+            mDispatch->unregisterCallback(mToken);
             mRegistered = false;
         }
     }
 
-    VSyncDispatch& mDispatch;
+    std::shared_ptr<VSyncDispatch> mDispatch;
     VSyncDispatch::CallbackToken mToken;
     bool mRegistered = true;
 
@@ -231,22 +232,26 @@
     static nsecs_t constexpr mDispatchGroupThreshold = 5;
     nsecs_t const mPeriod = 1000;
     nsecs_t const mVsyncMoveThreshold = 300;
-    NiceMock<MockVSyncTracker> mStubTracker{mPeriod};
-    VSyncDispatchTimerQueue mDispatch{createTimeKeeper(), mStubTracker, mDispatchGroupThreshold,
-                                      mVsyncMoveThreshold};
+    std::shared_ptr<NiceMock<MockVSyncTracker>> mStubTracker =
+            std::make_shared<NiceMock<MockVSyncTracker>>(mPeriod);
+    std::shared_ptr<VSyncDispatch> mDispatch =
+            std::make_shared<VSyncDispatchTimerQueue>(createTimeKeeper(), mStubTracker,
+                                                      mDispatchGroupThreshold, mVsyncMoveThreshold);
 };
 
 TEST_F(VSyncDispatchTimerQueueTest, unregistersSetAlarmOnDestruction) {
     EXPECT_CALL(mMockClock, alarmAt(_, 900));
     EXPECT_CALL(mMockClock, alarmCancel());
     {
-        VSyncDispatchTimerQueue mDispatch{createTimeKeeper(), mStubTracker, mDispatchGroupThreshold,
-                                          mVsyncMoveThreshold};
+        std::shared_ptr<VSyncDispatch> mDispatch =
+                std::make_shared<VSyncDispatchTimerQueue>(createTimeKeeper(), mStubTracker,
+                                                          mDispatchGroupThreshold,
+                                                          mVsyncMoveThreshold);
         CountingCallback cb(mDispatch);
-        const auto result = mDispatch.schedule(cb,
-                                               {.workDuration = 100,
-                                                .readyDuration = 0,
-                                                .earliestVsync = 1000});
+        const auto result = mDispatch->schedule(cb,
+                                                {.workDuration = 100,
+                                                 .readyDuration = 0,
+                                                 .earliestVsync = 1000});
         EXPECT_TRUE(result.has_value());
         EXPECT_EQ(900, *result);
     }
@@ -257,10 +262,10 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900));
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch.schedule(cb,
-                                           {.workDuration = 100,
-                                            .readyDuration = 0,
-                                            .earliestVsync = intended});
+    const auto result = mDispatch->schedule(cb,
+                                            {.workDuration = 100,
+                                             .readyDuration = 0,
+                                             .earliestVsync = intended});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
 
@@ -277,14 +282,15 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 700)).InSequence(seq);
 
     CountingCallback cb(mDispatch);
-    auto result = mDispatch.schedule(cb,
-                                     {.workDuration = 100,
-                                      .readyDuration = 0,
-                                      .earliestVsync = intended});
+    auto result = mDispatch->schedule(cb,
+                                      {.workDuration = 100,
+                                       .readyDuration = 0,
+                                       .earliestVsync = intended});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
 
-    result = mDispatch.update(cb,
+    result =
+            mDispatch->update(cb,
                               {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(700, *result);
@@ -302,17 +308,17 @@
 
     CountingCallback cb(mDispatch);
     const auto result =
-            mDispatch.update(cb,
-                             {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
+            mDispatch->update(cb,
+                              {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
     EXPECT_FALSE(result.has_value());
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithAdjustmentToTrueVsync) {
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150));
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150));
     EXPECT_CALL(mMockClock, alarmAt(_, 1050));
 
     CountingCallback cb(mDispatch);
-    mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
@@ -323,15 +329,15 @@
     auto const now = 234;
     mMockClock.advanceBy(234);
     auto const workDuration = 10 * mPeriod;
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(now + workDuration))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + workDuration))
             .WillOnce(Return(mPeriod * 11));
     EXPECT_CALL(mMockClock, alarmAt(_, mPeriod));
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch.schedule(cb,
-                                           {.workDuration = workDuration,
-                                            .readyDuration = 0,
-                                            .earliestVsync = mPeriod});
+    const auto result = mDispatch->schedule(cb,
+                                            {.workDuration = workDuration,
+                                             .readyDuration = 0,
+                                             .earliestVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod, *result);
 }
@@ -341,12 +347,13 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     CountingCallback cb(mDispatch);
-    const auto result =
-            mDispatch.schedule(cb,
-                               {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    const auto result = mDispatch->schedule(cb,
+                                            {.workDuration = 100,
+                                             .readyDuration = 0,
+                                             .earliestVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod - 100, *result);
-    EXPECT_EQ(mDispatch.cancel(cb), CancelResult::Cancelled);
+    EXPECT_EQ(mDispatch->cancel(cb), CancelResult::Cancelled);
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLate) {
@@ -354,13 +361,14 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     CountingCallback cb(mDispatch);
-    const auto result =
-            mDispatch.schedule(cb,
-                               {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    const auto result = mDispatch->schedule(cb,
+                                            {.workDuration = 100,
+                                             .readyDuration = 0,
+                                             .earliestVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod - 100, *result);
     mMockClock.advanceBy(950);
-    EXPECT_EQ(mDispatch.cancel(cb), CancelResult::TooLate);
+    EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate);
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLateWhenRunning) {
@@ -368,15 +376,16 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     PausingCallback cb(mDispatch, std::chrono::duration_cast<std::chrono::milliseconds>(1s));
-    const auto result =
-            mDispatch.schedule(cb,
-                               {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    const auto result = mDispatch->schedule(cb,
+                                            {.workDuration = 100,
+                                             .readyDuration = 0,
+                                             .earliestVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod - 100, *result);
 
     std::thread pausingThread([&] { mMockClock.advanceToNextCallback(); });
     EXPECT_TRUE(cb.waitForPause());
-    EXPECT_EQ(mDispatch.cancel(cb), CancelResult::TooLate);
+    EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate);
     cb.unpause();
     pausingThread.join();
 }
@@ -389,9 +398,10 @@
 
     PausingCallback cb(mDispatch, 50ms);
     cb.stashResource(resource);
-    const auto result =
-            mDispatch.schedule(cb,
-                               {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    const auto result = mDispatch->schedule(cb,
+                                            {.workDuration = 100,
+                                             .readyDuration = 0,
+                                             .earliestVsync = mPeriod});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(mPeriod - 100, *result);
 
@@ -408,7 +418,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) {
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
             .Times(4)
             .WillOnce(Return(1055))
             .WillOnce(Return(1063))
@@ -423,8 +433,8 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
-    mDispatch.schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
 
     advanceToNextCallback();
     advanceToNextCallback();
@@ -436,7 +446,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) {
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
             .Times(4)
             .WillOnce(Return(1000))
             .WillOnce(Return(2000))
@@ -450,21 +460,21 @@
 
     CountingCallback cb(mDispatch);
 
-    mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0});
 
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
     EXPECT_THAT(cb.mCalls[0], Eq(1000));
 
-    mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
 
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(2));
     EXPECT_THAT(cb.mCalls[1], Eq(2000));
 
-    mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
 
     advanceToNextCallback();
 
@@ -473,7 +483,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) {
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
             .Times(4)
             .WillOnce(Return(10000))
             .WillOnce(Return(1000))
@@ -488,10 +498,10 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch.schedule(cb0,
-                       {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10});
-    mDispatch.schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
-    mDispatch.cancel(cb1);
+    mDispatch->schedule(cb0,
+                        {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10});
+    mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
+    mDispatch->cancel(cb1);
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, noUnnecessaryRearmsWhenRescheduling) {
@@ -502,9 +512,9 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch.schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000});
     advanceToNextCallback();
 }
 
@@ -517,9 +527,9 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch.schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
     advanceToNextCallback();
 }
 
@@ -537,10 +547,10 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch.schedule(cb1,
-                       {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb1,
+                        {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000});
 
     advanceToNextCallback();
     ASSERT_THAT(cb0.mCalls.size(), Eq(1));
@@ -548,9 +558,11 @@
     ASSERT_THAT(cb1.mCalls.size(), Eq(1));
     EXPECT_THAT(cb1.mCalls[0], Eq(mPeriod));
 
-    mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000});
-    mDispatch.schedule(cb1,
-                       {.workDuration = notCloseOffset, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb1,
+                        {.workDuration = notCloseOffset,
+                         .readyDuration = 0,
+                         .earliestVsync = 2000});
     advanceToNextCallback();
     ASSERT_THAT(cb1.mCalls.size(), Eq(2));
     EXPECT_THAT(cb1.mCalls[1], Eq(2000));
@@ -570,32 +582,32 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
     advanceToNextCallback();
-    EXPECT_EQ(mDispatch.cancel(cb0), CancelResult::Cancelled);
+    EXPECT_EQ(mDispatch->cancel(cb0), CancelResult::Cancelled);
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, setAlarmCallsAtCorrectTimeWithChangingVsync) {
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
             .Times(3)
             .WillOnce(Return(950))
             .WillOnce(Return(1975))
             .WillOnce(Return(2950));
 
     CountingCallback cb(mDispatch);
-    mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920});
 
     mMockClock.advanceBy(850);
     EXPECT_THAT(cb.mCalls.size(), Eq(1));
 
-    mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900});
     mMockClock.advanceBy(900);
     EXPECT_THAT(cb.mCalls.size(), Eq(1));
     mMockClock.advanceBy(125);
     EXPECT_THAT(cb.mCalls.size(), Eq(2));
 
-    mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900});
     mMockClock.advanceBy(975);
     EXPECT_THAT(cb.mCalls.size(), Eq(3));
 }
@@ -606,48 +618,48 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
 
     VSyncDispatch::CallbackToken tmp;
-    tmp = mDispatch.registerCallback(
+    tmp = mDispatch->registerCallback(
             [&](auto, auto, auto) {
-                mDispatch.schedule(tmp,
-                                   {.workDuration = 100,
-                                    .readyDuration = 0,
-                                    .earliestVsync = 2000});
+                mDispatch->schedule(tmp,
+                                    {.workDuration = 100,
+                                     .readyDuration = 0,
+                                     .earliestVsync = 2000});
             },
             "o.o");
 
-    mDispatch.schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
     advanceToNextCallback();
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, callbackReentrantWithPastWakeup) {
     VSyncDispatch::CallbackToken tmp;
     std::optional<nsecs_t> lastTarget;
-    tmp = mDispatch.registerCallback(
+    tmp = mDispatch->registerCallback(
             [&](auto timestamp, auto, auto) {
                 auto result =
-                        mDispatch.schedule(tmp,
-                                           {.workDuration = 400,
-                                            .readyDuration = 0,
-                                            .earliestVsync = timestamp - mVsyncMoveThreshold});
-                EXPECT_TRUE(result.has_value());
-                EXPECT_EQ(mPeriod + timestamp - 400, *result);
-                result = mDispatch.schedule(tmp,
+                        mDispatch->schedule(tmp,
                                             {.workDuration = 400,
                                              .readyDuration = 0,
-                                             .earliestVsync = timestamp});
+                                             .earliestVsync = timestamp - mVsyncMoveThreshold});
                 EXPECT_TRUE(result.has_value());
                 EXPECT_EQ(mPeriod + timestamp - 400, *result);
-                result = mDispatch.schedule(tmp,
-                                            {.workDuration = 400,
-                                             .readyDuration = 0,
-                                             .earliestVsync = timestamp + mVsyncMoveThreshold});
+                result = mDispatch->schedule(tmp,
+                                             {.workDuration = 400,
+                                              .readyDuration = 0,
+                                              .earliestVsync = timestamp});
+                EXPECT_TRUE(result.has_value());
+                EXPECT_EQ(mPeriod + timestamp - 400, *result);
+                result = mDispatch->schedule(tmp,
+                                             {.workDuration = 400,
+                                              .readyDuration = 0,
+                                              .earliestVsync = timestamp + mVsyncMoveThreshold});
                 EXPECT_TRUE(result.has_value());
                 EXPECT_EQ(mPeriod + timestamp - 400, *result);
                 lastTarget = timestamp;
             },
             "oo");
 
-    mDispatch.schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000});
     advanceToNextCallback();
     EXPECT_THAT(lastTarget, Eq(1000));
 
@@ -663,16 +675,16 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
 
     CountingCallback cb(mDispatch);
-    mDispatch.schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000});
 
     mMockClock.advanceBy(750);
-    mDispatch.schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000});
 
     advanceToNextCallback();
-    mDispatch.schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000});
 
     mMockClock.advanceBy(800);
-    mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, lateModifications) {
@@ -685,12 +697,12 @@
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
 
-    mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch.schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
 
     advanceToNextCallback();
-    mDispatch.schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000});
-    mDispatch.schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000});
+    mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000});
 
     advanceToNextCallback();
     advanceToNextCallback();
@@ -702,8 +714,8 @@
 
     CountingCallback cb0(mDispatch);
     CountingCallback cb1(mDispatch);
-    mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch.schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000});
+    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000});
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) {
@@ -713,29 +725,30 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
 
     CountingCallback cb0(mDispatch);
-    mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch.cancel(cb0);
-    mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->cancel(cb0);
+    mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, makingUpIdsError) {
     VSyncDispatch::CallbackToken token(100);
-    EXPECT_FALSE(mDispatch
-                         .schedule(token,
-                                   {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000})
-                         .has_value());
-    EXPECT_THAT(mDispatch.cancel(token), Eq(CancelResult::Error));
+    EXPECT_FALSE(
+            mDispatch
+                    ->schedule(token,
+                               {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000})
+                    .has_value());
+    EXPECT_THAT(mDispatch->cancel(token), Eq(CancelResult::Error));
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, canMoveCallbackBackwardsInTime) {
     CountingCallback cb0(mDispatch);
     auto result =
-            mDispatch.schedule(cb0,
-                               {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb0,
+                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
-    result = mDispatch.schedule(cb0,
-                                {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb0,
+                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
 }
@@ -745,14 +758,14 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 500));
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch.schedule(cb,
-                               {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb,
+                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     mMockClock.advanceBy(400);
 
-    result = mDispatch.schedule(cb,
-                                {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb,
+                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1200, *result);
     advanceToNextCallback();
@@ -760,19 +773,19 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedule) {
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
             .Times(2)
             .WillOnce(Return(1000))
             .WillOnce(Return(1002));
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch.schedule(cb,
-                               {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb,
+                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     mMockClock.advanceBy(400);
-    result = mDispatch.schedule(cb,
-                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb,
+                                 {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(602, *result);
 }
@@ -780,13 +793,13 @@
 TEST_F(VSyncDispatchTimerQueueTest, canScheduleNegativeOffsetAgainstDifferentPeriods) {
     CountingCallback cb0(mDispatch);
     auto result =
-            mDispatch.schedule(cb0,
-                               {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb0,
+                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     advanceToNextCallback();
-    result = mDispatch.schedule(cb0,
-                                {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb0,
+                                 {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
 }
@@ -797,13 +810,13 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 1100)).InSequence(seq);
     CountingCallback cb0(mDispatch);
     auto result =
-            mDispatch.schedule(cb0,
-                               {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb0,
+                                {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(500, *result);
     advanceToNextCallback();
-    result = mDispatch.schedule(cb0,
-                                {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb0,
+                                 {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1100, *result);
 }
@@ -813,13 +826,13 @@
 
     CountingCallback cb(mDispatch);
     auto result =
-            mDispatch.schedule(cb,
-                               {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb,
+                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
-    result = mDispatch.schedule(cb,
-                                {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb,
+                                 {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
@@ -865,16 +878,16 @@
     CountingCallback cb2(mDispatch);
 
     auto result =
-            mDispatch.schedule(cb1,
-                               {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1,
+                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
 
-    result = mDispatch.schedule(cb2,
-                                {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb2,
+                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1900, *result);
     mMockClock.advanceBy(80);
@@ -893,16 +906,16 @@
     CountingCallback cb(mDispatch);
 
     auto result =
-            mDispatch.schedule(cb,
-                               {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb,
+                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
 
-    result = mDispatch.schedule(cb,
-                                {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb,
+                                 {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1630, *result);
     mMockClock.advanceBy(80);
@@ -919,19 +932,19 @@
     CountingCallback cb2(mDispatch);
 
     auto result =
-            mDispatch.schedule(cb1,
-                               {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1,
+                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
-    result = mDispatch.schedule(cb2,
-                                {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb2,
+                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1900, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
 
-    EXPECT_EQ(mDispatch.cancel(cb2), CancelResult::Cancelled);
+    EXPECT_EQ(mDispatch->cancel(cb2), CancelResult::Cancelled);
 
     mMockClock.advanceBy(80);
 
@@ -948,19 +961,19 @@
     CountingCallback cb2(mDispatch);
 
     auto result =
-            mDispatch.schedule(cb1,
-                               {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1,
+                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
-    result = mDispatch.schedule(cb2,
-                                {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    result = mDispatch->schedule(cb2,
+                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(1900, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
 
-    EXPECT_EQ(mDispatch.cancel(cb1), CancelResult::Cancelled);
+    EXPECT_EQ(mDispatch->cancel(cb1), CancelResult::Cancelled);
 
     EXPECT_THAT(cb1.mCalls.size(), Eq(0));
     EXPECT_THAT(cb2.mCalls.size(), Eq(0));
@@ -975,21 +988,21 @@
     CountingCallback cb2(mDispatch);
 
     Sequence seq;
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
             .InSequence(seq)
             .WillOnce(Return(1000));
     EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
             .InSequence(seq)
             .WillOnce(Return(1000));
 
     auto result =
-            mDispatch.schedule(cb1,
-                               {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+            mDispatch->schedule(cb1,
+                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(600, *result);
-    result = mDispatch.schedule(cb2,
-                                {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000});
+    result = mDispatch->schedule(cb2,
+                                 {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(610, *result);
 
@@ -1011,10 +1024,10 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900));
 
     CountingCallback cb(mDispatch);
-    const auto result = mDispatch.schedule(cb,
-                                           {.workDuration = 70,
-                                            .readyDuration = 30,
-                                            .earliestVsync = intended});
+    const auto result = mDispatch->schedule(cb,
+                                            {.workDuration = 70,
+                                             .readyDuration = 30,
+                                             .earliestVsync = intended});
     EXPECT_TRUE(result.has_value());
     EXPECT_EQ(900, *result);
     advanceToNextCallback();
@@ -1033,8 +1046,8 @@
 
     CountingCallback cb(mDispatch);
 
-    mDispatch.schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
-    mDispatch.schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
 
     advanceToNextCallback();
 
@@ -1052,7 +1065,8 @@
 protected:
     nsecs_t const mPeriod = 1000;
     nsecs_t const mVsyncMoveThreshold = 200;
-    NiceMock<MockVSyncTracker> mStubTracker{mPeriod};
+    std::shared_ptr<NiceMock<MockVSyncTracker>> mStubTracker =
+            std::make_shared<NiceMock<MockVSyncTracker>>(mPeriod);
 };
 
 TEST_F(VSyncDispatchTimerQueueEntryTest, stateAfterInitialization) {
@@ -1070,7 +1084,7 @@
 
     EXPECT_FALSE(entry.wakeupTime());
     EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
@@ -1084,7 +1098,7 @@
     auto const duration = 500;
     auto const now = 8750;
 
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(now + duration))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + duration))
             .Times(1)
             .WillOnce(Return(10000));
     VSyncDispatchTimerQueueEntry entry(
@@ -1092,7 +1106,7 @@
 
     EXPECT_FALSE(entry.wakeupTime());
     EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 994},
-                               mStubTracker, now)
+                               *mStubTracker.get(), now)
                         .has_value());
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
@@ -1115,7 +1129,7 @@
             mVsyncMoveThreshold);
 
     EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
@@ -1137,7 +1151,7 @@
 }
 
 TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) {
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
             .Times(2)
             .WillOnce(Return(1000))
             .WillOnce(Return(1020));
@@ -1146,17 +1160,17 @@
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     EXPECT_FALSE(entry.wakeupTime());
-    entry.update(mStubTracker, 0);
+    entry.update(*mStubTracker.get(), 0);
     EXPECT_FALSE(entry.wakeupTime());
 
     EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
     auto wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(wakeup, Eq(900));
 
-    entry.update(mStubTracker, 0);
+    entry.update(*mStubTracker.get(), 0);
     wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(*wakeup, Eq(920));
@@ -1166,9 +1180,9 @@
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
     EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
-    entry.update(mStubTracker, 0);
+    entry.update(*mStubTracker.get(), 0);
 
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
@@ -1179,24 +1193,24 @@
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
     EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
     entry.executing(); // 1000 is executing
     // had 1000 not been executing, this could have been scheduled for time 800.
     EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
 
     EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1950));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
 
     EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 1001},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
@@ -1208,24 +1222,24 @@
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     Sequence seq;
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(500))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500))
             .InSequence(seq)
             .WillOnce(Return(1000));
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(500))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500))
             .InSequence(seq)
             .WillOnce(Return(1000));
-    EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold))
+    EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold))
             .InSequence(seq)
             .WillOnce(Return(2000));
 
     EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
 
     entry.executing(); // 1000 is executing
 
     EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
 }
 
@@ -1233,16 +1247,16 @@
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
     EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
     EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
     EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
     EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
 }
 
@@ -1255,7 +1269,7 @@
     entry.addPendingWorkloadUpdate(
             {.workDuration = effectualOffset, .readyDuration = 0, .earliestVsync = 400});
     EXPECT_TRUE(entry.hasPendingWorkloadUpdate());
-    entry.update(mStubTracker, 0);
+    entry.update(*mStubTracker.get(), 0);
     EXPECT_FALSE(entry.hasPendingWorkloadUpdate());
     EXPECT_THAT(*entry.wakeupTime(), Eq(mPeriod - effectualOffset));
 }
@@ -1276,7 +1290,7 @@
             mVsyncMoveThreshold);
 
     EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .earliestVsync = 500},
-                               mStubTracker, 0)
+                               *mStubTracker.get(), 0)
                         .has_value());
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index db531bf..43d683d 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -47,6 +47,8 @@
     return vsyncs;
 }
 
+constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+
 struct VSyncPredictorTest : testing::Test {
     nsecs_t mNow = 0;
     nsecs_t mPeriod = 1000;
@@ -55,7 +57,7 @@
     static constexpr size_t kOutlierTolerancePercent = 25;
     static constexpr nsecs_t mMaxRoundingError = 100;
 
-    VSyncPredictor tracker{mPeriod, kHistorySize, kMinimumSamplesForPrediction,
+    VSyncPredictor tracker{DEFAULT_DISPLAY_ID, mPeriod, kHistorySize, kMinimumSamplesForPrediction,
                            kOutlierTolerancePercent};
 };
 
@@ -376,7 +378,8 @@
 
 // See b/151146131
 TEST_F(VSyncPredictorTest, hasEnoughPrecision) {
-    VSyncPredictor tracker{mPeriod, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+    VSyncPredictor tracker{DEFAULT_DISPLAY_ID, mPeriod, 20, kMinimumSamplesForPrediction,
+                           kOutlierTolerancePercent};
     std::vector<nsecs_t> const simulatedVsyncs{840873348817, 840890049444, 840906762675,
                                                840923581635, 840940161584, 840956868096,
                                                840973702473, 840990256277, 841007116851,
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index fb8d989..122192b 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -91,13 +91,15 @@
     return ft;
 }
 
+constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+
 class VSyncReactorTest : public testing::Test {
 protected:
     VSyncReactorTest()
           : mMockTracker(std::make_shared<NiceMock<MockVSyncTracker>>()),
             mMockClock(std::make_shared<NiceMock<MockClock>>()),
-            mReactor(std::make_unique<ClockWrapper>(mMockClock), *mMockTracker, kPendingLimit,
-                     false /* supportKernelIdleTimer */) {
+            mReactor(DEFAULT_DISPLAY_ID, std::make_unique<ClockWrapper>(mMockClock), *mMockTracker,
+                     kPendingLimit, false /* supportKernelIdleTimer */) {
         ON_CALL(*mMockClock, now()).WillByDefault(Return(mFakeNow));
         ON_CALL(*mMockTracker, currentPeriod()).WillByDefault(Return(period));
     }
@@ -192,7 +194,7 @@
     mReactor.setIgnorePresentFences(true);
 
     nsecs_t const newPeriod = 5000;
-    mReactor.startPeriodTransition(newPeriod);
+    mReactor.startPeriodTransition(newPeriod, false);
 
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
@@ -205,7 +207,7 @@
 TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) {
     nsecs_t const newPeriod = 5000;
     EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0);
-    mReactor.startPeriodTransition(newPeriod);
+    mReactor.startPeriodTransition(newPeriod, false);
 
     bool periodFlushed = true;
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(10000, std::nullopt, &periodFlushed));
@@ -224,7 +226,7 @@
 TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) {
     nsecs_t sampleTime = 0;
     nsecs_t const newPeriod = 5000;
-    mReactor.startPeriodTransition(newPeriod);
+    mReactor.startPeriodTransition(newPeriod, false);
     bool periodFlushed = true;
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
@@ -232,7 +234,7 @@
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
 
-    mReactor.startPeriodTransition(period);
+    mReactor.startPeriodTransition(period, false);
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
 }
@@ -242,13 +244,13 @@
     nsecs_t const secondPeriod = 5000;
     nsecs_t const thirdPeriod = 2000;
 
-    mReactor.startPeriodTransition(secondPeriod);
+    mReactor.startPeriodTransition(secondPeriod, false);
     bool periodFlushed = true;
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
-    mReactor.startPeriodTransition(thirdPeriod);
+    mReactor.startPeriodTransition(thirdPeriod, false);
     EXPECT_TRUE(
             mReactor.addHwVsyncTimestamp(sampleTime += secondPeriod, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
@@ -289,14 +291,14 @@
 
 TEST_F(VSyncReactorTest, presentFenceAdditionDoesNotInterruptConfirmationProcess) {
     nsecs_t const newPeriod = 5000;
-    mReactor.startPeriodTransition(newPeriod);
+    mReactor.startPeriodTransition(newPeriod, false);
     EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
 }
 
 TEST_F(VSyncReactorTest, setPeriodCalledFirstTwoEventsNewPeriod) {
     nsecs_t const newPeriod = 5000;
     EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0);
-    mReactor.startPeriodTransition(newPeriod);
+    mReactor.startPeriodTransition(newPeriod, false);
 
     bool periodFlushed = true;
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(5000, std::nullopt, &periodFlushed));
@@ -321,7 +323,7 @@
     bool periodFlushed = false;
     nsecs_t const newPeriod = 4000;
 
-    mReactor.startPeriodTransition(newPeriod);
+    mReactor.startPeriodTransition(newPeriod, false);
 
     auto time = 0;
     auto constexpr numTimestampSubmissions = 10;
@@ -346,7 +348,7 @@
     bool periodFlushed = false;
     nsecs_t const newPeriod = 4000;
 
-    mReactor.startPeriodTransition(newPeriod);
+    mReactor.startPeriodTransition(newPeriod, false);
 
     auto time = 0;
     // If the power mode is not DOZE or DOZE_SUSPEND, it is still collecting timestamps.
@@ -363,7 +365,7 @@
     auto time = 0;
     bool periodFlushed = false;
     nsecs_t const newPeriod = 4000;
-    mReactor.startPeriodTransition(newPeriod);
+    mReactor.startPeriodTransition(newPeriod, false);
 
     time += period;
     mReactor.addHwVsyncTimestamp(time, std::nullopt, &periodFlushed);
@@ -379,7 +381,7 @@
     auto time = 0;
     bool periodFlushed = false;
     nsecs_t const newPeriod = 4000;
-    mReactor.startPeriodTransition(newPeriod);
+    mReactor.startPeriodTransition(newPeriod, false);
 
     static auto constexpr numSamplesWithNewPeriod = 4;
     Sequence seq;
@@ -406,7 +408,7 @@
     auto time = 0;
     bool periodFlushed = false;
     nsecs_t const newPeriod = 4000;
-    mReactor.startPeriodTransition(newPeriod);
+    mReactor.startPeriodTransition(newPeriod, false);
 
     Sequence seq;
     EXPECT_CALL(*mMockTracker, needsMoreSamples())
@@ -426,7 +428,7 @@
     nsecs_t const newPeriod1 = 4000;
     nsecs_t const newPeriod2 = 7000;
 
-    mReactor.startPeriodTransition(newPeriod1);
+    mReactor.startPeriodTransition(newPeriod1, false);
 
     Sequence seq;
     EXPECT_CALL(*mMockTracker, needsMoreSamples())
@@ -445,7 +447,7 @@
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed));
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed));
 
-    mReactor.startPeriodTransition(newPeriod2);
+    mReactor.startPeriodTransition(newPeriod2, false);
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed));
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod2, std::nullopt, &periodFlushed));
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod2, std::nullopt, &periodFlushed));
@@ -458,7 +460,7 @@
     mReactor.setIgnorePresentFences(true);
 
     nsecs_t const newPeriod = 5000;
-    mReactor.startPeriodTransition(newPeriod);
+    mReactor.startPeriodTransition(newPeriod, false);
 
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(0, 0, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
@@ -472,8 +474,9 @@
 
 TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) {
     // Create a reactor which supports the kernel idle timer
-    auto idleReactor = VSyncReactor(std::make_unique<ClockWrapper>(mMockClock), *mMockTracker,
-                                    kPendingLimit, true /* supportKernelIdleTimer */);
+    auto idleReactor =
+            VSyncReactor(DEFAULT_DISPLAY_ID, std::make_unique<ClockWrapper>(mMockClock),
+                         *mMockTracker, kPendingLimit, true /* supportKernelIdleTimer */);
 
     bool periodFlushed = true;
     EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(4);
@@ -481,7 +484,7 @@
 
     // First, set the same period, which should only be confirmed when we receive two
     // matching callbacks
-    idleReactor.startPeriodTransition(10000);
+    idleReactor.startPeriodTransition(10000, false);
     EXPECT_TRUE(idleReactor.addHwVsyncTimestamp(0, 0, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
     // Correct period but incorrect timestamp delta
@@ -494,7 +497,7 @@
     // Then, set a new period, which should be confirmed as soon as we receive a callback
     // reporting the new period
     nsecs_t const newPeriod = 5000;
-    idleReactor.startPeriodTransition(newPeriod);
+    idleReactor.startPeriodTransition(newPeriod, false);
     // Incorrect timestamp delta and period
     EXPECT_TRUE(idleReactor.addHwVsyncTimestamp(20000, 10000, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
diff --git a/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp
new file mode 100644
index 0000000..adf0804
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <ftl/fake_guard.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#include <scheduler/Fps.h>
+#include "Scheduler/VsyncSchedule.h"
+#include "ThreadContext.h"
+#include "mock/MockSchedulerCallback.h"
+#include "mock/MockVSyncDispatch.h"
+#include "mock/MockVSyncTracker.h"
+#include "mock/MockVsyncController.h"
+
+using testing::_;
+
+namespace android {
+
+constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+
+class VsyncScheduleTest : public testing::Test {
+protected:
+    VsyncScheduleTest();
+    ~VsyncScheduleTest() override;
+
+    scheduler::mock::SchedulerCallback mCallback;
+    const std::unique_ptr<scheduler::VsyncSchedule> mVsyncSchedule =
+            std::unique_ptr<scheduler::VsyncSchedule>(
+                    new scheduler::VsyncSchedule(DEFAULT_DISPLAY_ID,
+                                                 std::make_shared<mock::VSyncTracker>(),
+                                                 std::make_shared<mock::VSyncDispatch>(),
+                                                 std::make_unique<mock::VsyncController>()));
+
+    mock::VsyncController& getController() {
+        return *static_cast<mock::VsyncController*>(&mVsyncSchedule->getController());
+    }
+};
+
+VsyncScheduleTest::VsyncScheduleTest() {
+    const ::testing::TestInfo* const test_info =
+            ::testing::UnitTest::GetInstance()->current_test_info();
+    ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+}
+
+VsyncScheduleTest::~VsyncScheduleTest() {
+    const ::testing::TestInfo* const test_info =
+            ::testing::UnitTest::GetInstance()->current_test_info();
+    ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+}
+
+namespace {
+
+using namespace testing;
+
+TEST_F(VsyncScheduleTest, InitiallyDisallowed) {
+    ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */));
+}
+
+TEST_F(VsyncScheduleTest, EnableDoesNothingWhenDisallowed) {
+    EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0);
+
+    mVsyncSchedule->enableHardwareVsync(mCallback);
+}
+
+TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisallowed) {
+    EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0);
+
+    mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */);
+}
+
+TEST_F(VsyncScheduleTest, MakeAllowed) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+}
+
+TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisabled) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0);
+
+    mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */);
+}
+
+TEST_F(VsyncScheduleTest, EnableWorksWhenDisabled) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true));
+
+    mVsyncSchedule->enableHardwareVsync(mCallback);
+}
+
+TEST_F(VsyncScheduleTest, EnableWorksOnce) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true));
+
+    mVsyncSchedule->enableHardwareVsync(mCallback);
+
+    EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0);
+    mVsyncSchedule->enableHardwareVsync(mCallback);
+}
+
+TEST_F(VsyncScheduleTest, AllowedIsSticky) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */));
+}
+
+TEST_F(VsyncScheduleTest, EnableDisable) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true));
+
+    mVsyncSchedule->enableHardwareVsync(mCallback);
+
+    EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, false));
+    mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */);
+}
+
+TEST_F(VsyncScheduleTest, StartPeriodTransition) {
+    // Note: startPeriodTransition is only called when hardware vsyncs are
+    // allowed.
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+
+    const Period period = (60_Hz).getPeriod();
+
+    EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true));
+    EXPECT_CALL(getController(), startPeriodTransition(period.ns(), false));
+
+    mVsyncSchedule->startPeriodTransition(mCallback, period, false);
+}
+
+TEST_F(VsyncScheduleTest, StartPeriodTransitionAlreadyEnabled) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    mVsyncSchedule->enableHardwareVsync(mCallback);
+
+    const Period period = (60_Hz).getPeriod();
+
+    EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0);
+    EXPECT_CALL(getController(), startPeriodTransition(period.ns(), false));
+
+    mVsyncSchedule->startPeriodTransition(mCallback, period, false);
+}
+
+TEST_F(VsyncScheduleTest, StartPeriodTransitionForce) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+
+    const Period period = (60_Hz).getPeriod();
+
+    EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, true));
+    EXPECT_CALL(getController(), startPeriodTransition(period.ns(), true));
+
+    mVsyncSchedule->startPeriodTransition(mCallback, period, true);
+}
+
+TEST_F(VsyncScheduleTest, AddResyncSampleDisallowed) {
+    const Period period = (60_Hz).getPeriod();
+    const auto timestamp = TimePoint::now();
+
+    EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0);
+    EXPECT_CALL(getController(), addHwVsyncTimestamp(_, _, _)).Times(0);
+
+    mVsyncSchedule->addResyncSample(mCallback, timestamp, period);
+}
+
+TEST_F(VsyncScheduleTest, AddResyncSampleDisabled) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    const Period period = (60_Hz).getPeriod();
+    const auto timestamp = TimePoint::now();
+
+    EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0);
+    EXPECT_CALL(getController(), addHwVsyncTimestamp(_, _, _)).Times(0);
+
+    mVsyncSchedule->addResyncSample(mCallback, timestamp, period);
+}
+
+TEST_F(VsyncScheduleTest, AddResyncSampleReturnsTrue) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    mVsyncSchedule->enableHardwareVsync(mCallback);
+
+    const Period period = (60_Hz).getPeriod();
+    const auto timestamp = TimePoint::now();
+
+    EXPECT_CALL(mCallback, setVsyncEnabled(_, _)).Times(0);
+    EXPECT_CALL(getController(),
+                addHwVsyncTimestamp(timestamp.ns(), std::optional<nsecs_t>(period.ns()), _))
+            .WillOnce(Return(true));
+
+    mVsyncSchedule->addResyncSample(mCallback, timestamp, period);
+}
+
+TEST_F(VsyncScheduleTest, AddResyncSampleReturnsFalse) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    mVsyncSchedule->enableHardwareVsync(mCallback);
+
+    const Period period = (60_Hz).getPeriod();
+    const auto timestamp = TimePoint::now();
+
+    EXPECT_CALL(mCallback, setVsyncEnabled(DEFAULT_DISPLAY_ID, false));
+    EXPECT_CALL(getController(),
+                addHwVsyncTimestamp(timestamp.ns(), std::optional<nsecs_t>(period.ns()), _))
+            .WillOnce(Return(false));
+
+    mVsyncSchedule->addResyncSample(mCallback, timestamp, period);
+}
+
+TEST_F(VsyncScheduleTest, PendingState) FTL_FAKE_GUARD(kMainThreadContext) {
+    ASSERT_FALSE(mVsyncSchedule->getPendingHardwareVsyncState());
+    mVsyncSchedule->setPendingHardwareVsyncState(true);
+    ASSERT_TRUE(mVsyncSchedule->getPendingHardwareVsyncState());
+
+    mVsyncSchedule->setPendingHardwareVsyncState(false);
+    ASSERT_FALSE(mVsyncSchedule->getPendingHardwareVsyncState());
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index f8567bd..8d57049 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -29,27 +29,28 @@
     EventThread();
     ~EventThread() override;
 
-    MOCK_CONST_METHOD2(createEventConnection,
-                       sp<EventThreadConnection>(ResyncCallback, EventRegistrationFlags));
-    MOCK_METHOD0(onScreenReleased, void());
-    MOCK_METHOD0(onScreenAcquired, void());
-    MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool));
-    MOCK_METHOD1(onModeChanged, void(const scheduler::FrameRateMode &));
-    MOCK_METHOD2(onFrameRateOverridesChanged,
-                 void(PhysicalDisplayId, std::vector<FrameRateOverride>));
-    MOCK_CONST_METHOD1(dump, void(std::string&));
-    MOCK_METHOD2(setDuration,
-                 void(std::chrono::nanoseconds workDuration,
-                      std::chrono::nanoseconds readyDuration));
-    MOCK_METHOD1(registerDisplayEventConnection,
-                 status_t(const sp<android::EventThreadConnection> &));
-    MOCK_METHOD2(setVsyncRate, void(uint32_t, const sp<android::EventThreadConnection> &));
-    MOCK_METHOD1(requestNextVsync, void(const sp<android::EventThreadConnection> &));
+    MOCK_METHOD(sp<EventThreadConnection>, createEventConnection,
+                (ResyncCallback, EventRegistrationFlags), (const, override));
+    MOCK_METHOD(void, enableSyntheticVsync, (bool), (override));
+    MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override));
+    MOCK_METHOD(void, onModeChanged, (const scheduler::FrameRateMode&), (override));
+    MOCK_METHOD(void, onFrameRateOverridesChanged,
+                (PhysicalDisplayId, std::vector<FrameRateOverride>), (override));
+    MOCK_METHOD(void, dump, (std::string&), (const, override));
+    MOCK_METHOD(void, setDuration,
+                (std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration),
+                (override));
+    MOCK_METHOD(status_t, registerDisplayEventConnection,
+                (const sp<android::EventThreadConnection>&), (override));
+    MOCK_METHOD(void, setVsyncRate, (uint32_t, const sp<android::EventThreadConnection>&),
+                (override));
+    MOCK_METHOD(void, requestNextVsync, (const sp<android::EventThreadConnection>&), (override));
     MOCK_METHOD(VsyncEventData, getLatestVsyncEventData,
-                (const sp<android::EventThreadConnection> &), (const));
-    MOCK_METHOD1(requestLatestConfig, void(const sp<android::EventThreadConnection> &));
-    MOCK_METHOD1(pauseVsyncCallback, void(bool));
-    MOCK_METHOD0(getEventThreadConnectionCount, size_t());
+                (const sp<android::EventThreadConnection>&), (const, override));
+    MOCK_METHOD(void, requestLatestConfig, (const sp<android::EventThreadConnection>&));
+    MOCK_METHOD(void, pauseVsyncCallback, (bool));
+    MOCK_METHOD(size_t, getEventThreadConnectionCount, (), (override));
+    MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr<scheduler::VsyncSchedule>), (override));
 };
 
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 7d4b159..a8eca21 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -18,19 +18,19 @@
 
 #include <gmock/gmock.h>
 
-#include "Scheduler/Scheduler.h"
+#include "Scheduler/ISchedulerCallback.h"
 
 namespace android::scheduler::mock {
 
 struct SchedulerCallback final : ISchedulerCallback {
-    MOCK_METHOD(void, setVsyncEnabled, (bool), (override));
+    MOCK_METHOD(void, setVsyncEnabled, (PhysicalDisplayId, bool), (override));
     MOCK_METHOD(void, requestDisplayModes, (std::vector<display::DisplayModeRequest>), (override));
     MOCK_METHOD(void, kernelTimerChanged, (bool), (override));
     MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override));
 };
 
 struct NoOpSchedulerCallback final : ISchedulerCallback {
-    void setVsyncEnabled(bool) override {}
+    void setVsyncEnabled(PhysicalDisplayId, bool) override {}
     void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
index 4ef91da..69ec60a 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
@@ -28,12 +28,12 @@
     ~VsyncController() override;
 
     MOCK_METHOD(bool, addPresentFence, (std::shared_ptr<FenceTime>), (override));
-    MOCK_METHOD3(addHwVsyncTimestamp, bool(nsecs_t, std::optional<nsecs_t>, bool*));
-    MOCK_METHOD1(startPeriodTransition, void(nsecs_t));
-    MOCK_METHOD1(setIgnorePresentFences, void(bool));
+    MOCK_METHOD(bool, addHwVsyncTimestamp, (nsecs_t, std::optional<nsecs_t>, bool*), (override));
+    MOCK_METHOD(void, startPeriodTransition, (nsecs_t, bool), (override));
+    MOCK_METHOD(void, setIgnorePresentFences, (bool), (override));
     MOCK_METHOD(void, setDisplayPowerMode, (hal::PowerMode), (override));
 
-    MOCK_CONST_METHOD1(dump, void(std::string&));
+    MOCK_METHOD(void, dump, (std::string&), (const, override));
 };
 
 } // namespace android::mock