Merge "Enable isolated process to use graphics allocator" into udc-dev
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index 2e0c95a..66df0de 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -297,8 +297,18 @@
     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/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
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/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/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/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/input/Android.bp b/libs/input/Android.bp
index 3809b6d..48cb72c 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",
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 9f0a314..bdbd708 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, 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, 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, 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, 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, hasFocus=%s",
+             mChannel->getName().c_str(), __func__, seq, 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, pointerCaptureEnabled=%s",
+             mChannel->getName().c_str(), __func__, seq, 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, x=%f, y=%f, isExiting=%s",
+             mChannel->getName().c_str(), __func__, seq, x, y, toString(isExiting));
 
     InputMessage msg;
     msg.header.type = InputMessage::Type::DRAG;
@@ -681,6 +740,8 @@
                              mChannel->getName().c_str(), toString(isInTouchMode));
         ATRACE_NAME(message.c_str());
     }
+    ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: seq=%u, isInTouchMode=%s",
+             mChannel->getName().c_str(), __func__, seq, toString(isInTouchMode));
 
     InputMessage msg;
     msg.header.type = InputMessage::Type::TOUCH_MODE;
@@ -691,9 +752,8 @@
 }
 
 android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveConsumerResponse() {
-    if (DEBUG_TRANSPORT_ACTIONS) {
-        ALOGD("channel '%s' publisher ~ %s", mChannel->getName().c_str(), __func__);
-    }
+    ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s", mChannel->getName().c_str(),
+             __func__);
 
     InputMessage msg;
     status_t result = mChannel->receiveMessage(&msg);
@@ -738,10 +798,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 +826,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 +844,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 +856,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 +880,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 +894,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 +908,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 +1127,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 +1150,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 +1166,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 +1182,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 +1193,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 +1246,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 +1274,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 +1323,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/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/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 621543a..b625a1b 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -14,11 +14,13 @@
  * 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>
@@ -27,13 +29,6 @@
 
 namespace android::inputdispatcher {
 
-static const bool DEBUGGABLE =
-#if defined(__ANDROID__)
-        android::base::GetBoolProperty("ro.debuggable", false);
-#else
-        true;
-#endif
-
 VerifiedKeyEvent verifiedKeyEventFromKeyEntry(const KeyEntry& entry) {
     return {{VerifiedInputEvent::Type::KEY, entry.deviceId, entry.eventTime, entry.source,
              entry.displayId},
@@ -178,7 +173,7 @@
 KeyEntry::~KeyEntry() {}
 
 std::string KeyEntry::getDescription() const {
-    if (!DEBUGGABLE) {
+    if (!IS_DEBUGGABLE_BUILD) {
         return "KeyEvent";
     }
     return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64 ", source=%s, displayId=%" PRId32
@@ -248,7 +243,7 @@
 MotionEntry::~MotionEntry() {}
 
 std::string MotionEntry::getDescription() const {
-    if (!DEBUGGABLE) {
+    if (!IS_DEBUGGABLE_BUILD) {
         return "MotionEvent";
     }
     std::string msg;
@@ -298,7 +293,7 @@
                         deviceId, inputEventSourceToString(source).c_str(),
                         ftl::enum_string(sensorType).c_str(), accuracy, hwTimestamp);
 
-    if (DEBUGGABLE) {
+    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 08ae376..ab6a116 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) {
@@ -2745,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;
@@ -4015,7 +4016,7 @@
 }
 
 void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
-    if (DEBUG_INBOUND_EVENT_DETAILS) {
+    if (debugInboundEventDetails()) {
         ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args->eventTime);
     }
 
@@ -4071,7 +4072,7 @@
 }
 
 void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
-    if (DEBUG_INBOUND_EVENT_DETAILS) {
+    if (debugInboundEventDetails()) {
         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,
@@ -4149,7 +4150,7 @@
 }
 
 void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
-    if (DEBUG_INBOUND_EVENT_DETAILS) {
+    if (debugInboundEventDetails()) {
         ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
               "displayId=%" PRId32 ", policyFlags=0x%x, "
               "action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, "
@@ -4260,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,
@@ -4288,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);
     }
@@ -4300,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);
@@ -4312,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);
     }
@@ -4332,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");
     }
@@ -4355,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 cc9cc4e..94f3813 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -247,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/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..002de29 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -412,7 +412,7 @@
     // in the order received.
     std::list<NotifyArgs> out;
     for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
-        if (DEBUG_RAW_EVENTS) {
+        if (debugRawEvents()) {
             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);
@@ -421,11 +421,11 @@
         if (mDropUntilNextSync) {
             if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
                 mDropUntilNextSync = false;
-                if (DEBUG_RAW_EVENTS) {
+                if (debugRawEvents()) {
                     ALOGD("Recovered from input event buffer overrun.");
                 }
             } else {
-                if (DEBUG_RAW_EVENTS) {
+                if (debugRawEvents()) {
                     ALOGD("Dropped input event while waiting for next input sync.");
                 }
             }
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 bd9038d..314233e 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -105,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;
@@ -1945,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
@@ -2052,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);
@@ -2490,7 +2543,7 @@
     const int32_t mouseDeviceId = 6;
     NotifyMotionArgs args;
 
-    // First touch pointer down on right window
+    // First touch pointer down
     mDispatcher->notifyMotion(&(
             args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                            .deviceId(touchDeviceId)
@@ -2523,7 +2576,7 @@
                            .build()));
 
     window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId),
-                                     WithPointerCount(2u)));
+                                     WithPointerCount(1u)));
     window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
 
     mDispatcher->notifyMotion(&(
@@ -2547,6 +2600,46 @@
 }
 
 /**
+ * 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.
@@ -3007,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));
 
@@ -3440,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.
@@ -5465,6 +5594,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 dac0916..a29be22 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(
@@ -4077,6 +4076,10 @@
     }
 }
 
+void Layer::updateLastLatchTime(nsecs_t latchTime) {
+    mLastLatchTime = latchTime;
+}
+
 // ---------------------------------------------------------------------------
 
 std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 7d40774..326e1f6 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
@@ -623,7 +624,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
@@ -860,7 +861,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..b78b92b 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -384,23 +384,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();
 }
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 347dc4a..5037860 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;
 
@@ -159,11 +156,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;
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index dab01ba..0daabe2 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -265,24 +265,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) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index a340919..67f4daa 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -157,8 +157,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);
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index d88f1d1..a32acc7 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -20,6 +20,7 @@
 #include <string>
 
 #include <ThreadContext.h>
+#include <android-base/thread_annotations.h>
 #include <ftl/enum.h>
 #include <ftl/optional.h>
 #include <scheduler/Features.h>
@@ -27,6 +28,7 @@
 
 namespace android {
 class EventThreadTest;
+class VsyncScheduleTest;
 }
 
 namespace android::fuzz {
@@ -96,6 +98,7 @@
 private:
     friend class TestableScheduler;
     friend class android::EventThreadTest;
+    friend class android::VsyncScheduleTest;
     friend class android::fuzz::SchedulerFuzzer;
 
     using TrackerPtr = std::unique_ptr<VsyncTracker>;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 5c8579c..86018d6 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -2031,15 +2031,9 @@
 
 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);
 
@@ -3576,6 +3570,10 @@
         });
     }
 
+    if (transactionFlags & eInputInfoUpdateNeeded) {
+        mUpdateInputInfo = true;
+    }
+
     doCommitTransactions();
 }
 
@@ -3863,7 +3861,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();
             }
         });
@@ -3899,7 +3897,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;
             }
@@ -3910,6 +3908,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;
@@ -5210,7 +5210,6 @@
         return;
     }
 
-    const bool isActiveDisplay = displayId == mActiveDisplayId;
     const bool isInternalDisplay = mPhysicalDisplays.get(displayId)
                                            .transform(&PhysicalDisplay::isInternal)
                                            .value_or(false);
@@ -5247,10 +5246,10 @@
             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->enableSyntheticVsync(false);
             mScheduler->resyncToHardwareVsync(true, refreshRate);
         }
 
@@ -5264,9 +5263,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) {
+        if (displayId == mActiveDisplayId && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) {
             mScheduler->disableHardwareVsync(true);
-            mScheduler->onScreenReleased(mAppConnectionHandle);
+            mScheduler->enableSyntheticVsync();
         }
 
         // Make sure HWVsync is disabled before turning off the display
@@ -5278,18 +5277,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->enableSyntheticVsync(false);
             mScheduler->resyncToHardwareVsync(true, refreshRate);
         }
     } else if (mode == hal::PowerMode::DOZE_SUSPEND) {
         // Leave display going to doze
-        if (isActiveDisplay) {
+        if (displayId == mActiveDisplayId) {
             mScheduler->disableHardwareVsync(true);
-            mScheduler->onScreenReleased(mAppConnectionHandle);
+            mScheduler->enableSyntheticVsync();
         }
         getHwComposer().setPowerMode(displayId, mode);
     } else {
@@ -5297,7 +5296,7 @@
         getHwComposer().setPowerMode(displayId, mode);
     }
 
-    if (isActiveDisplay) {
+    if (displayId == mActiveDisplayId) {
         mTimeStats->setPowerMode(mode);
         mRefreshRateStats->setPowerMode(mode);
         mScheduler->setDisplayPowerMode(mode);
@@ -7566,8 +7565,9 @@
 }
 
 status_t SurfaceFlinger::addWindowInfosListener(
-        const sp<IWindowInfosListener>& windowInfosListener) const {
+        const sp<IWindowInfosListener>& windowInfosListener) {
     mWindowInfosListenerInvoker->addWindowInfosListener(windowInfosListener);
+    setTransactionFlags(eInputInfoUpdateNeeded);
     return NO_ERROR;
 }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index b41f414..03c31bb 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;
 
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index 2f46487..475c76b 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_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index 61fb29a..80486a2 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -109,8 +109,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);
 }
 
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/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 23a8bd1..57d1d9c 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -503,14 +503,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 +523,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/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 4b15385..422fa1c 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -122,12 +122,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 +141,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);
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_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..17b4714 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -59,47 +59,34 @@
 };
 
 struct EventThreadBaseSupportedVariant {
-    static void setupVsyncAndEventThreadNoCallExpectations(DisplayTransactionTest* test) {
-        // The callback should not be notified to toggle VSYNC.
+    static void setupVsyncNoCallExpectations(DisplayTransactionTest* test) {
+        // Expect no change to hardware nor synthetic 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);
+        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.
+    static void setupEnableVsyncCallExpectations(DisplayTransactionTest* test) {
+        // Expect to enable hardware VSYNC and disable synthetic 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);
+        EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(false)).Times(1);
     }
 
-    static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) {
-        // The callback should be notified to disable VSYNC.
+    static void setupDisableVsyncCallExpectations(DisplayTransactionTest* test) {
+        // Expect to disable hardware VSYNC and enable synthetic 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);
+        EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(true)).Times(1);
     }
 };
 
@@ -133,7 +120,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 +135,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 +147,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 +160,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 +172,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 +181,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 +190,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 +199,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 +209,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 +218,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);
     }
 };
@@ -484,38 +471,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..48c5d48 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -91,6 +91,7 @@
         Scheduler::setLeaderDisplay(displayId);
     }
 
+    auto& mutableAppConnectionHandle() { return mAppConnectionHandle; }
     auto& mutableVsyncModulator() { return *mVsyncModulator; }
     auto& mutableLayerHistory() { return mLayerHistory; }
 
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 8703359..0649333 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -252,7 +252,10 @@
 
         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);
     }
@@ -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;
     };
diff --git a/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp
new file mode 100644
index 0000000..652d313
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp
@@ -0,0 +1,215 @@
+/*
+ * 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 {
+
+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(std::make_unique<mock::VSyncTracker>(),
+                                                 std::make_unique<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(true));
+
+    mVsyncSchedule->enableHardwareVsync(mCallback);
+}
+
+TEST_F(VsyncScheduleTest, EnableWorksOnce) {
+    ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+    EXPECT_CALL(mCallback, setVsyncEnabled(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(true));
+
+    mVsyncSchedule->enableHardwareVsync(mCallback);
+
+    EXPECT_CALL(mCallback, setVsyncEnabled(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(true));
+    EXPECT_CALL(getController(), startPeriodTransition(period.ns()));
+
+    mVsyncSchedule->startPeriodTransition(mCallback, period);
+}
+
+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()));
+
+    mVsyncSchedule->startPeriodTransition(mCallback, period);
+}
+
+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(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..8026a7a 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -31,8 +31,7 @@
 
     MOCK_CONST_METHOD2(createEventConnection,
                        sp<EventThreadConnection>(ResyncCallback, EventRegistrationFlags));
-    MOCK_METHOD0(onScreenReleased, void());
-    MOCK_METHOD0(onScreenAcquired, void());
+    MOCK_METHOD(void, enableSyntheticVsync, (bool), (override));
     MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool));
     MOCK_METHOD1(onModeChanged, void(const scheduler::FrameRateMode &));
     MOCK_METHOD2(onFrameRateOverridesChanged,
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 7d4b159..103beb5 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -18,7 +18,7 @@
 
 #include <gmock/gmock.h>
 
-#include "Scheduler/Scheduler.h"
+#include "Scheduler/ISchedulerCallback.h"
 
 namespace android::scheduler::mock {