Remove some new memory leaks from SensorManager am: 2f8fa1367c am: 9433e8caea

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/native/+/21548667

Change-Id: I1d1bc7e0e1b29bfb7a701b8a8a33994d12e2ce79
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/cmds/dumpstate/DumpstateInternal.cpp b/cmds/dumpstate/DumpstateInternal.cpp
index 3091f6b..6f7fea3 100644
--- a/cmds/dumpstate/DumpstateInternal.cpp
+++ b/cmds/dumpstate/DumpstateInternal.cpp
@@ -162,17 +162,16 @@
         return 0;
     }
     bool newline = false;
+    int poll_timeout_ms = 30 * 1000;
     while (true) {
-        uint64_t start_time = Nanotime();
         pollfd fds[] = { { .fd = fd, .events = POLLIN } };
-        int ret = TEMP_FAILURE_RETRY(poll(fds, arraysize(fds), 30 * 1000));
+        int ret = TEMP_FAILURE_RETRY(poll(fds, arraysize(fds), poll_timeout_ms));
         if (ret == -1) {
             dprintf(out_fd, "*** %s: poll failed: %s\n", path, strerror(errno));
             newline = true;
             break;
-        } else if (ret == 0) {
-            uint64_t elapsed = Nanotime() - start_time;
-            dprintf(out_fd, "*** %s: Timed out after %.3fs\n", path, (float)elapsed / NANOS_PER_SEC);
+        } else if (ret == 0 && poll_timeout_ms != 0) {
+            dprintf(out_fd, "*** %s: Timed out after %ds\n", path, poll_timeout_ms / 1000 );
             newline = true;
             break;
         } else {
@@ -189,6 +188,7 @@
                 break;
             }
         }
+        poll_timeout_ms = 0;
     }
 
     if (!newline) dprintf(out_fd, "\n");
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 2b94b71..ec1e89b 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -3172,6 +3172,15 @@
             "", {"cmd", service, "tracing", "save-for-bugreport"},
             CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build());
     }
+    // Additionally, include the proto logging from WMShell.
+    RunCommand(
+        // Empty name because it's not intended to be classified as a bugreport section.
+        // Actual logging files can be found as "/data/misc/wmtrace/shell_log.winscope"
+        // in the bugreport.
+        "", {"dumpsys", "activity", "service", "SystemUIService",
+             "WMShell", "protolog", "save-for-bugreport"},
+        CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build());
+
 }
 
 void Dumpstate::onUiIntensiveBugreportDumpsFinished(int32_t calling_uid) {
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index a49f563..6d3f7c3 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -100,6 +100,8 @@
 
 static constexpr const char* kDataMirrorCePath = "/data_mirror/data_ce";
 static constexpr const char* kDataMirrorDePath = "/data_mirror/data_de";
+static constexpr const char* kMiscMirrorCePath = "/data_mirror/misc_ce";
+static constexpr const char* kMiscMirrorDePath = "/data_mirror/misc_de";
 
 static constexpr const int MIN_RESTRICTED_HOME_SDK_VERSION = 24; // > M
 
@@ -3539,16 +3541,28 @@
 
     std::string mirrorVolCePath(StringPrintf("%s/%s", kDataMirrorCePath, uuid_));
     if (fs_prepare_dir(mirrorVolCePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) {
-        return error("Failed to create CE mirror");
+        return error("Failed to create CE data mirror");
     }
 
     std::string mirrorVolDePath(StringPrintf("%s/%s", kDataMirrorDePath, uuid_));
     if (fs_prepare_dir(mirrorVolDePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) {
-        return error("Failed to create DE mirror");
+        return error("Failed to create DE data mirror");
+    }
+
+    std::string mirrorVolMiscCePath(StringPrintf("%s/%s", kMiscMirrorCePath, uuid_));
+    if (fs_prepare_dir(mirrorVolMiscCePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) {
+        return error("Failed to create CE misc mirror");
+    }
+
+    std::string mirrorVolMiscDePath(StringPrintf("%s/%s", kMiscMirrorDePath, uuid_));
+    if (fs_prepare_dir(mirrorVolMiscDePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) {
+        return error("Failed to create DE misc mirror");
     }
 
     auto cePath = StringPrintf("%s/user", create_data_path(uuid_).c_str());
     auto dePath = StringPrintf("%s/user_de", create_data_path(uuid_).c_str());
+    auto miscCePath = StringPrintf("%s/misc_ce", create_data_path(uuid_).c_str());
+    auto miscDePath = StringPrintf("%s/misc_de", create_data_path(uuid_).c_str());
 
     if (access(cePath.c_str(), F_OK) != 0) {
         return error("Cannot access CE path: " + cePath);
@@ -3556,6 +3570,12 @@
     if (access(dePath.c_str(), F_OK) != 0) {
         return error("Cannot access DE path: " + dePath);
     }
+    if (access(miscCePath.c_str(), F_OK) != 0) {
+        return error("Cannot access misc CE path: " + cePath);
+    }
+    if (access(miscDePath.c_str(), F_OK) != 0) {
+        return error("Cannot access misc DE path: " + dePath);
+    }
 
     struct stat ceStat, mirrorCeStat;
     if (stat(cePath.c_str(), &ceStat) != 0) {
@@ -3583,6 +3603,21 @@
             MS_NOSUID | MS_NODEV | MS_NOATIME | MS_BIND | MS_NOEXEC, nullptr)) == -1) {
         return error("Failed to mount " + mirrorVolDePath);
     }
+
+    // Mount misc CE mirror
+    if (TEMP_FAILURE_RETRY(mount(miscCePath.c_str(), mirrorVolMiscCePath.c_str(), NULL,
+                                 MS_NOSUID | MS_NODEV | MS_NOATIME | MS_BIND | MS_NOEXEC,
+                                 nullptr)) == -1) {
+        return error("Failed to mount " + mirrorVolMiscCePath);
+    }
+
+    // Mount misc DE mirror
+    if (TEMP_FAILURE_RETRY(mount(miscDePath.c_str(), mirrorVolMiscDePath.c_str(), NULL,
+                                 MS_NOSUID | MS_NODEV | MS_NOATIME | MS_BIND | MS_NOEXEC,
+                                 nullptr)) == -1) {
+        return error("Failed to mount " + mirrorVolMiscDePath);
+    }
+
     return ok();
 }
 
@@ -3605,6 +3640,8 @@
 
     std::string mirrorCeVolPath(StringPrintf("%s/%s", kDataMirrorCePath, uuid_));
     std::string mirrorDeVolPath(StringPrintf("%s/%s", kDataMirrorDePath, uuid_));
+    std::string mirrorMiscCeVolPath(StringPrintf("%s/%s", kMiscMirrorCePath, uuid_));
+    std::string mirrorMiscDeVolPath(StringPrintf("%s/%s", kMiscMirrorDePath, uuid_));
 
     std::lock_guard<std::recursive_mutex> lock(mMountsLock);
 
@@ -3629,6 +3666,29 @@
     if (delete_dir_contents_and_dir(mirrorDeVolPath, true) != 0) {
         res = error("Failed to delete " + mirrorDeVolPath);
     }
+
+    // Unmount misc CE storage
+    if (TEMP_FAILURE_RETRY(umount(mirrorMiscCeVolPath.c_str())) != 0) {
+        if (errno != ENOENT) {
+            res = error(StringPrintf("Failed to umount %s %s", mirrorMiscCeVolPath.c_str(),
+                                     strerror(errno)));
+        }
+    }
+    if (delete_dir_contents_and_dir(mirrorMiscCeVolPath, true) != 0) {
+        res = error("Failed to delete " + mirrorMiscCeVolPath);
+    }
+
+    // Unmount misc DE storage
+    if (TEMP_FAILURE_RETRY(umount(mirrorMiscDeVolPath.c_str())) != 0) {
+        if (errno != ENOENT) {
+            res = error(StringPrintf("Failed to umount %s %s", mirrorMiscDeVolPath.c_str(),
+                                     strerror(errno)));
+        }
+    }
+    if (delete_dir_contents_and_dir(mirrorMiscDeVolPath, true) != 0) {
+        res = error("Failed to delete " + mirrorMiscDeVolPath);
+    }
+
     return res;
 }
 
diff --git a/cmds/installd/utils.cpp b/cmds/installd/utils.cpp
index 45aeab6..4d9b710 100644
--- a/cmds/installd/utils.cpp
+++ b/cmds/installd/utils.cpp
@@ -1184,8 +1184,8 @@
 int wait_child_with_timeout(pid_t pid, int timeout_ms) {
     int pidfd = pidfd_open(pid, /*flags=*/0);
     if (pidfd < 0) {
-        PLOG(ERROR) << "pidfd_open failed for pid " << pid;
-        kill(pid, SIGKILL);
+        PLOG(ERROR) << "pidfd_open failed for pid " << pid
+                    << ", waiting for child process without timeout";
         return wait_child(pid);
     }
 
diff --git a/include/android/keycodes.h b/include/android/keycodes.h
index 214559d..3357660 100644
--- a/include/android/keycodes.h
+++ b/include/android/keycodes.h
@@ -776,7 +776,39 @@
     AKEYCODE_THUMBS_DOWN = 287,
     /** Used to switch current account that is consuming content.
      * May be consumed by system to switch current viewer profile. */
-    AKEYCODE_PROFILE_SWITCH = 288
+    AKEYCODE_PROFILE_SWITCH = 288,
+    /** Video Application key #1. */
+    AKEYCODE_VIDEO_APP_1 = 289,
+    /** Video Application key #2. */
+    AKEYCODE_VIDEO_APP_2 = 290,
+    /** Video Application key #3. */
+    AKEYCODE_VIDEO_APP_3 = 291,
+    /** Video Application key #4. */
+    AKEYCODE_VIDEO_APP_4 = 292,
+    /** Video Application key #5. */
+    AKEYCODE_VIDEO_APP_5 = 293,
+    /** Video Application key #6. */
+    AKEYCODE_VIDEO_APP_6 = 294,
+    /** Video Application key #7. */
+    AKEYCODE_VIDEO_APP_7 = 295,
+    /** Video Application key #8. */
+    AKEYCODE_VIDEO_APP_8 = 296,
+    /** Featured Application key #1. */
+    AKEYCODE_FEATURED_APP_1 = 297,
+    /** Featured Application key #2. */
+    AKEYCODE_FEATURED_APP_2 = 298,
+    /** Featured Application key #3. */
+    AKEYCODE_FEATURED_APP_3 = 299,
+    /** Featured Application key #4. */
+    AKEYCODE_FEATURED_APP_4 = 300,
+    /** Demo Application key #1. */
+    AKEYCODE_DEMO_APP_1 = 301,
+    /** Demo Application key #2. */
+    AKEYCODE_DEMO_APP_2 = 302,
+    /** Demo Application key #3. */
+    AKEYCODE_DEMO_APP_3 = 303,
+    /** Demo Application key #4. */
+    AKEYCODE_DEMO_APP_4 = 304,
 
     // NOTE: If you add a new keycode here you must also add it to several other files.
     //       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
diff --git a/include/input/Input.h b/include/input/Input.h
index e7d68fc..7ea2970 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -297,6 +297,8 @@
  */
 const char* motionClassificationToString(MotionClassification classification);
 
+const char* motionToolTypeToString(int32_t toolType);
+
 /**
  * Portion of FrameMetrics timeline of interest to input code.
  */
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index c4f03c9..3585392 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -300,6 +300,8 @@
 /*
  * Gets the path of an input device configuration file, if one is available.
  * Considers both system provided and user installed configuration files.
+ * The optional suffix is appended to the end of the file name (before the
+ * extension).
  *
  * The device identifier is used to construct several default configuration file
  * names to try based on the device name, vendor, product, and version.
@@ -307,8 +309,8 @@
  * Returns an empty string if not found.
  */
 extern std::string getInputDeviceConfigurationFilePathByDeviceIdentifier(
-        const InputDeviceIdentifier& deviceIdentifier,
-        InputDeviceConfigurationFileType type);
+        const InputDeviceIdentifier& deviceIdentifier, InputDeviceConfigurationFileType type,
+        const char* suffix = "");
 
 /*
  * Gets the path of an input device configuration file, if one is available.
diff --git a/include/input/KeyLayoutMap.h b/include/input/KeyLayoutMap.h
index b2bd535..5084950 100644
--- a/include/input/KeyLayoutMap.h
+++ b/include/input/KeyLayoutMap.h
@@ -21,8 +21,8 @@
 #include <stdint.h>
 #include <utils/Errors.h>
 #include <utils/KeyedVector.h>
-#include <utils/RefBase.h>
 #include <utils/Tokenizer.h>
+#include <set>
 
 #include <input/InputDevice.h>
 
@@ -65,8 +65,8 @@
  */
 class KeyLayoutMap {
 public:
-    static base::Result<std::shared_ptr<KeyLayoutMap>> load(const std::string& filename);
-    static base::Result<std::shared_ptr<KeyLayoutMap>> load(Tokenizer* tokenizer);
+    static base::Result<std::shared_ptr<KeyLayoutMap>> load(const std::string& filename,
+                                                            const char* contents = nullptr);
     static base::Result<std::shared_ptr<KeyLayoutMap>> loadContents(const std::string& filename,
                                                                     const char* contents);
 
@@ -84,6 +84,8 @@
     virtual ~KeyLayoutMap();
 
 private:
+    static base::Result<std::shared_ptr<KeyLayoutMap>> load(Tokenizer* tokenizer);
+
     struct Key {
         int32_t keyCode;
         uint32_t flags;
@@ -104,6 +106,7 @@
     KeyedVector<int32_t, Led> mLedsByScanCode;
     KeyedVector<int32_t, Led> mLedsByUsageCode;
     std::unordered_map<int32_t, Sensor> mSensorsByAbsCode;
+    std::set<std::string> mRequiredKernelConfigs;
     std::string mLoadFileName;
 
     KeyLayoutMap();
@@ -124,6 +127,7 @@
         status_t parseAxis();
         status_t parseLed();
         status_t parseSensor();
+        status_t parseRequiredKernelConfig();
     };
 };
 
diff --git a/include/input/Keyboard.h b/include/input/Keyboard.h
index 08ad8c6..9a3e15f 100644
--- a/include/input/Keyboard.h
+++ b/include/input/Keyboard.h
@@ -61,9 +61,7 @@
     bool probeKeyMap(const InputDeviceIdentifier& deviceIdentifier, const std::string& name);
     status_t loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier, const std::string& name);
     status_t loadKeyCharacterMap(const InputDeviceIdentifier& deviceIdentifier,
-            const std::string& name);
-    std::string getPath(const InputDeviceIdentifier& deviceIdentifier,
-            const std::string& name, InputDeviceConfigurationFileType type);
+                                 const std::string& name);
 };
 
 /**
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index 0a75278..55f730b 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <map>
+#include <optional>
 #include <set>
 #include <string>
 
@@ -28,6 +29,15 @@
 }
 
 /**
+ * Convert an optional type to string.
+ */
+template <typename T>
+std::string toString(const std::optional<T>& optional,
+                     std::string (*toString)(const T&) = constToString) {
+    return optional ? toString(*optional) : "<not set>";
+}
+
+/**
  * Convert a set of integral types to string.
  */
 template <typename T>
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index a3533d8..9ebeca0 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -187,6 +187,11 @@
 
 cc_test {
     name: "binderRpcTest",
+    // b/269799024
+    test_options: {
+        unit_test: false,
+    },
+
     host_supported: true,
     target: {
         darwin: {
diff --git a/libs/graphicsenv/Android.bp b/libs/graphicsenv/Android.bp
index a96a07a..af50a29 100644
--- a/libs/graphicsenv/Android.bp
+++ b/libs/graphicsenv/Android.bp
@@ -27,10 +27,13 @@
     srcs: [
         "GpuStatsInfo.cpp",
         "GraphicsEnv.cpp",
-        "IGpuService.cpp"
+        "IGpuService.cpp",
     ],
 
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 
     shared_libs: [
         "libbase",
@@ -46,4 +49,13 @@
     ],
 
     export_include_dirs: ["include"],
+
+    product_variables: {
+        // `debuggable` is set for eng and userdebug builds
+        debuggable: {
+            cflags: [
+                "-DANDROID_DEBUGGABLE",
+            ],
+        },
+    },
 }
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 7f0cac5..e86881a 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -126,7 +126,20 @@
 }
 
 bool GraphicsEnv::isDebuggable() {
-    return prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) > 0;
+    // This flag determines if the application is marked debuggable
+    bool appDebuggable = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) > 0;
+
+    // This flag is set only in `debuggable` builds of the platform
+#if defined(ANDROID_DEBUGGABLE)
+    bool platformDebuggable = true;
+#else
+    bool platformDebuggable = false;
+#endif
+
+    ALOGV("GraphicsEnv::isDebuggable returning appDebuggable=%s || platformDebuggable=%s",
+          appDebuggable ? "true" : "false", platformDebuggable ? "true" : "false");
+
+    return appDebuggable || platformDebuggable;
 }
 
 void GraphicsEnv::setDriverPathAndSphalLibraries(const std::string path,
diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
index 56d1139..098e4a6 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -35,7 +35,7 @@
 
     // Check if the process is debuggable. It returns false except in any of the
     // following circumstances:
-    // 1. ro.debuggable=1 (global debuggable enabled).
+    // 1. ANDROID_DEBUGGABLE is defined (global debuggable enabled).
     // 2. android:debuggable="true" in the manifest for an individual app.
     // 3. An app which explicitly calls prctl(PR_SET_DUMPABLE, 1).
     // 4. GraphicsEnv calls prctl(PR_SET_DUMPABLE, 1) in the presence of
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index dbccf30..b8ea080 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -287,18 +287,17 @@
 
                 // We need to check if we were waiting for a transaction callback in order to
                 // process any pending buffers and unblock. It's possible to get transaction
-                // callbacks for previous requests so we need to ensure the frame from this
-                // transaction callback matches the last acquired buffer. Since acquireNextBuffer
-                // will stop processing buffers when mWaitForTransactionCallback is set, we know
-                // that mLastAcquiredFrameNumber is the frame we're waiting on.
-                // We also want to check if mNextTransaction is null because it's possible another
+                // callbacks for previous requests so we need to ensure that there are no pending
+                // frame numbers that were in a sync. We remove the frame from mSyncedFrameNumbers
+                // set and then check if it's empty. If there are no more pending syncs, we can
+                // proceed with flushing the shadow queue.
+                // We also want to check if mSyncTransaction is null because it's possible another
                 // sync request came in while waiting, but it hasn't started processing yet. In that
                 // case, we don't actually want to flush the frames in between since they will get
                 // processed and merged with the sync transaction and released earlier than if they
                 // were sent to SF
-                if (mWaitForTransactionCallback && mSyncTransaction == nullptr &&
-                    currFrameNumber >= mLastAcquiredFrameNumber) {
-                    mWaitForTransactionCallback = false;
+                mSyncedFrameNumbers.erase(currFrameNumber);
+                if (mSyncedFrameNumbers.empty() && mSyncTransaction == nullptr) {
                     flushShadowQueue();
                 }
             } else {
@@ -308,7 +307,6 @@
             BQA_LOGE("No matching SurfaceControls found: mSurfaceControlsWithPendingCallback was "
                      "empty.");
         }
-
         decStrong((void*)transactionCommittedCallbackThunk);
     }
 }
@@ -351,6 +349,21 @@
                                                     stat.latchTime,
                                                     stat.frameEventStats.dequeueReadyTime);
                 }
+                auto currFrameNumber = stat.frameEventStats.frameNumber;
+                std::vector<ReleaseCallbackId> staleReleases;
+                for (const auto& [key, value]: mSubmitted) {
+                    if (currFrameNumber > key.framenumber) {
+                        staleReleases.push_back(key);
+                    }
+                }
+                for (const auto& staleRelease : staleReleases) {
+                    releaseBufferCallbackLocked(staleRelease,
+                                                stat.previousReleaseFence
+                                                        ? stat.previousReleaseFence
+                                                        : Fence::NO_FENCE,
+                                                stat.currentMaxAcquiredBufferCount,
+                                                true /* fakeRelease */);
+                }
             } else {
                 BQA_LOGE("Failed to find matching SurfaceControl in transactionCallback");
             }
@@ -391,7 +404,16 @@
         const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
         std::optional<uint32_t> currentMaxAcquiredBufferCount) {
     BBQ_TRACE();
+
     std::unique_lock _lock{mMutex};
+    releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount,
+                                false /* fakeRelease */);
+}
+
+void BLASTBufferQueue::releaseBufferCallbackLocked(
+        const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
+        std::optional<uint32_t> currentMaxAcquiredBufferCount, bool fakeRelease) {
+    ATRACE_CALL();
     BQA_LOGV("releaseBufferCallback %s", id.to_string().c_str());
 
     // Calculate how many buffers we need to hold before we release them back
@@ -409,16 +431,27 @@
 
     const auto numPendingBuffersToHold =
             isEGL ? std::max(0u, mMaxAcquiredBuffers - mCurrentMaxAcquiredBufferCount) : 0;
-    mPendingRelease.emplace_back(ReleasedBuffer{id, releaseFence});
+
+    auto rb = ReleasedBuffer{id, releaseFence};
+    if (std::find(mPendingRelease.begin(), mPendingRelease.end(), rb) == mPendingRelease.end()) {
+        mPendingRelease.emplace_back(rb);
+        if (fakeRelease) {
+            BQA_LOGE("Faking releaseBufferCallback from transactionCompleteCallback %" PRIu64,
+                     id.framenumber);
+            BBQ_TRACE("FakeReleaseCallback");
+        }
+    }
 
     // Release all buffers that are beyond the ones that we need to hold
     while (mPendingRelease.size() > numPendingBuffersToHold) {
         const auto releasedBuffer = mPendingRelease.front();
         mPendingRelease.pop_front();
         releaseBuffer(releasedBuffer.callbackId, releasedBuffer.releaseFence);
-        // Don't process the transactions here if mWaitForTransactionCallback is set. Instead, let
-        // onFrameAvailable handle processing them since it will merge with the syncTransaction.
-        if (!mWaitForTransactionCallback) {
+        // Don't process the transactions here if mSyncedFrameNumbers is not empty. That means
+        // are still transactions that have sync buffers in them that have not been applied or
+        // dropped. Instead, let onFrameAvailable handle processing them since it will merge with
+        // the syncTransaction.
+        if (mSyncedFrameNumbers.empty()) {
             acquireNextBufferLocked(std::nullopt);
         }
     }
@@ -442,22 +475,31 @@
     BQA_LOGV("released %s", callbackId.to_string().c_str());
     mBufferItemConsumer->releaseBuffer(it->second, releaseFence);
     mSubmitted.erase(it);
+    // Remove the frame number from mSyncedFrameNumbers since we can get a release callback
+    // without getting a transaction committed if the buffer was dropped.
+    mSyncedFrameNumbers.erase(callbackId.framenumber);
 }
 
-void BLASTBufferQueue::acquireNextBufferLocked(
+status_t BLASTBufferQueue::acquireNextBufferLocked(
         const std::optional<SurfaceComposerClient::Transaction*> transaction) {
-    // If the next transaction is set, we want to guarantee the our acquire will not fail, so don't
-    // include the extra buffer when checking if we can acquire the next buffer.
-    const bool includeExtraAcquire = !transaction;
-    const bool maxAcquired = maxBuffersAcquired(includeExtraAcquire);
-    if (mNumFrameAvailable == 0 || maxAcquired) {
-        BQA_LOGV("Can't process next buffer maxBuffersAcquired=%s", boolToString(maxAcquired));
-        return;
+    // Check if we have frames available and we have not acquired the maximum number of buffers.
+    // Even with this check, the consumer can fail to acquire an additional buffer if the consumer
+    // has already acquired (mMaxAcquiredBuffers + 1) and the new buffer is not droppable. In this
+    // case mBufferItemConsumer->acquireBuffer will return with NO_BUFFER_AVAILABLE.
+    if (mNumFrameAvailable == 0) {
+        BQA_LOGV("Can't acquire next buffer. No available frames");
+        return BufferQueue::NO_BUFFER_AVAILABLE;
+    }
+
+    if (mNumAcquired >= (mMaxAcquiredBuffers + 2)) {
+        BQA_LOGV("Can't acquire next buffer. Already acquired max frames %d max:%d + 2",
+                 mNumAcquired, mMaxAcquiredBuffers);
+        return BufferQueue::NO_BUFFER_AVAILABLE;
     }
 
     if (mSurfaceControl == nullptr) {
         BQA_LOGE("ERROR : surface control is null");
-        return;
+        return NAME_NOT_FOUND;
     }
 
     SurfaceComposerClient::Transaction localTransaction;
@@ -474,10 +516,10 @@
             mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false);
     if (status == BufferQueue::NO_BUFFER_AVAILABLE) {
         BQA_LOGV("Failed to acquire a buffer, err=NO_BUFFER_AVAILABLE");
-        return;
+        return status;
     } else if (status != OK) {
         BQA_LOGE("Failed to acquire a buffer, err=%s", statusToString(status).c_str());
-        return;
+        return status;
     }
 
     auto buffer = bufferItem.mGraphicBuffer;
@@ -487,7 +529,7 @@
     if (buffer == nullptr) {
         mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
         BQA_LOGE("Buffer was empty");
-        return;
+        return BAD_VALUE;
     }
 
     if (rejectBuffer(bufferItem)) {
@@ -496,8 +538,7 @@
                  mSize.width, mSize.height, mRequestedSize.width, mRequestedSize.height,
                  buffer->getWidth(), buffer->getHeight(), bufferItem.mTransform);
         mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
-        acquireNextBufferLocked(transaction);
-        return;
+        return acquireNextBufferLocked(transaction);
     }
 
     mNumAcquired++;
@@ -551,9 +592,23 @@
         t->setDesiredPresentTime(bufferItem.mTimestamp);
     }
 
-    if (!mNextFrameTimelineInfoQueue.empty()) {
-        t->setFrameTimelineInfo(mNextFrameTimelineInfoQueue.front());
-        mNextFrameTimelineInfoQueue.pop();
+    // Drop stale frame timeline infos
+    while (!mPendingFrameTimelines.empty() &&
+           mPendingFrameTimelines.front().first < bufferItem.mFrameNumber) {
+        ATRACE_FORMAT_INSTANT("dropping stale frameNumber: %" PRIu64 " vsyncId: %" PRId64,
+                              mPendingFrameTimelines.front().first,
+                              mPendingFrameTimelines.front().second.vsyncId);
+        mPendingFrameTimelines.pop();
+    }
+
+    if (!mPendingFrameTimelines.empty() &&
+        mPendingFrameTimelines.front().first == bufferItem.mFrameNumber) {
+        ATRACE_FORMAT_INSTANT("Transaction::setFrameTimelineInfo frameNumber: %" PRIu64
+                              " vsyncId: %" PRId64,
+                              bufferItem.mFrameNumber,
+                              mPendingFrameTimelines.front().second.vsyncId);
+        t->setFrameTimelineInfo(mPendingFrameTimelines.front().second);
+        mPendingFrameTimelines.pop();
     }
 
     {
@@ -585,6 +640,7 @@
              bufferItem.mTimestamp, bufferItem.mIsAutoTimestamp ? "(auto)" : "",
              static_cast<uint32_t>(mPendingTransactions.size()), bufferItem.mGraphicBuffer->getId(),
              bufferItem.mAutoRefresh ? " mAutoRefresh" : "", bufferItem.mTransform);
+    return OK;
 }
 
 Rect BLASTBufferQueue::computeCrop(const BufferItem& item) {
@@ -607,42 +663,19 @@
     mBufferItemConsumer->releaseBuffer(bufferItem, bufferItem.mFence);
 }
 
-void BLASTBufferQueue::flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock) {
-    if (mWaitForTransactionCallback && mNumFrameAvailable > 0) {
-        // We are waiting on a previous sync's transaction callback so allow another sync
-        // transaction to proceed.
-        //
-        // We need to first flush out the transactions that were in between the two syncs.
-        // We do this by merging them into mSyncTransaction so any buffer merging will get
-        // a release callback invoked. The release callback will be async so we need to wait
-        // on max acquired to make sure we have the capacity to acquire another buffer.
-        if (maxBuffersAcquired(false /* includeExtraAcquire */)) {
-            BQA_LOGD("waiting to flush shadow queue...");
-            mCallbackCV.wait(lock);
-        }
-        while (mNumFrameAvailable > 0) {
-            // flush out the shadow queue
-            acquireAndReleaseBuffer();
-        }
-    }
-
-    while (maxBuffersAcquired(false /* includeExtraAcquire */)) {
-        BQA_LOGD("waiting for free buffer.");
-        mCallbackCV.wait(lock);
-    }
-}
-
 void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
     std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
     SurfaceComposerClient::Transaction* prevTransaction = nullptr;
+
     {
-        BBQ_TRACE();
         std::unique_lock _lock{mMutex};
+        BBQ_TRACE();
+
+        bool waitForTransactionCallback = !mSyncedFrameNumbers.empty();
         const bool syncTransactionSet = mTransactionReadyCallback != nullptr;
         BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet));
 
         if (syncTransactionSet) {
-            bool mayNeedToWaitForBuffer = true;
             // If we are going to re-use the same mSyncTransaction, release the buffer that may
             // already be set in the Transaction. This is to allow us a free slot early to continue
             // processing a new buffer.
@@ -653,20 +686,35 @@
                              bufferData->frameNumber);
                     releaseBuffer(bufferData->generateReleaseCallbackId(),
                                   bufferData->acquireFence);
-                    // Because we just released a buffer, we know there's no need to wait for a free
-                    // buffer.
-                    mayNeedToWaitForBuffer = false;
                 }
             }
 
-            if (mayNeedToWaitForBuffer) {
-                flushAndWaitForFreeBuffer(_lock);
+            if (waitForTransactionCallback) {
+                // We are waiting on a previous sync's transaction callback so allow another sync
+                // transaction to proceed.
+                //
+                // We need to first flush out the transactions that were in between the two syncs.
+                // We do this by merging them into mSyncTransaction so any buffer merging will get
+                // a release callback invoked.
+                while (mNumFrameAvailable > 0) {
+                    // flush out the shadow queue
+                    acquireAndReleaseBuffer();
+                }
+            } else {
+                // Make sure the frame available count is 0 before proceeding with a sync to ensure
+                // the correct frame is used for the sync. The only way mNumFrameAvailable would be
+                // greater than 0 is if we already ran out of buffers previously. This means we
+                // need to flush the buffers before proceeding with the sync.
+                while (mNumFrameAvailable > 0) {
+                    BQA_LOGD("waiting until no queued buffers");
+                    mCallbackCV.wait(_lock);
+                }
             }
         }
 
         // add to shadow queue
         mNumFrameAvailable++;
-        if (mWaitForTransactionCallback && mNumFrameAvailable >= 2) {
+        if (waitForTransactionCallback && mNumFrameAvailable >= 2) {
             acquireAndReleaseBuffer();
         }
         ATRACE_INT(mQueuedBufferTrace.c_str(),
@@ -676,21 +724,30 @@
                  item.mFrameNumber, boolToString(syncTransactionSet));
 
         if (syncTransactionSet) {
-            acquireNextBufferLocked(mSyncTransaction);
+            // Add to mSyncedFrameNumbers before waiting in case any buffers are released
+            // while waiting for a free buffer. The release and commit callback will try to
+            // acquire buffers if there are any available, but we don't want it to acquire
+            // in the case where a sync transaction wants the buffer.
+            mSyncedFrameNumbers.emplace(item.mFrameNumber);
+            // If there's no available buffer and we're in a sync transaction, we need to wait
+            // instead of returning since we guarantee a buffer will be acquired for the sync.
+            while (acquireNextBufferLocked(mSyncTransaction) == BufferQueue::NO_BUFFER_AVAILABLE) {
+                BQA_LOGD("waiting for available buffer");
+                mCallbackCV.wait(_lock);
+            }
 
             // Only need a commit callback when syncing to ensure the buffer that's synced has been
             // sent to SF
             incStrong((void*)transactionCommittedCallbackThunk);
             mSyncTransaction->addTransactionCommittedCallback(transactionCommittedCallbackThunk,
                                                               static_cast<void*>(this));
-            mWaitForTransactionCallback = true;
             if (mAcquireSingleBuffer) {
                 prevCallback = mTransactionReadyCallback;
                 prevTransaction = mSyncTransaction;
                 mTransactionReadyCallback = nullptr;
                 mSyncTransaction = nullptr;
             }
-        } else if (!mWaitForTransactionCallback) {
+        } else if (!waitForTransactionCallback) {
             acquireNextBufferLocked(std::nullopt);
         }
     }
@@ -786,15 +843,6 @@
     return mSize != bufferSize;
 }
 
-// Check if we have acquired the maximum number of buffers.
-// Consumer can acquire an additional buffer if that buffer is not droppable. Set
-// includeExtraAcquire is true to include this buffer to the count. Since this depends on the state
-// of the buffer, the next acquire may return with NO_BUFFER_AVAILABLE.
-bool BLASTBufferQueue::maxBuffersAcquired(bool includeExtraAcquire) const {
-    int maxAcquiredBuffers = mMaxAcquiredBuffers + (includeExtraAcquire ? 2 : 1);
-    return mNumAcquired >= maxAcquiredBuffers;
-}
-
 class BBQSurface : public Surface {
 private:
     std::mutex mMutex;
@@ -831,12 +879,13 @@
         return mBbq->setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
     }
 
-    status_t setFrameTimelineInfo(const FrameTimelineInfo& frameTimelineInfo) override {
+    status_t setFrameTimelineInfo(uint64_t frameNumber,
+                                  const FrameTimelineInfo& frameTimelineInfo) override {
         std::unique_lock _lock{mMutex};
         if (mDestroyed) {
             return DEAD_OBJECT;
         }
-        return mBbq->setFrameTimelineInfo(frameTimelineInfo);
+        return mBbq->setFrameTimelineInfo(frameNumber, frameTimelineInfo);
     }
 
     void destroy() override {
@@ -858,9 +907,12 @@
     return t.setFrameRate(mSurfaceControl, frameRate, compatibility, shouldBeSeamless).apply();
 }
 
-status_t BLASTBufferQueue::setFrameTimelineInfo(const FrameTimelineInfo& frameTimelineInfo) {
+status_t BLASTBufferQueue::setFrameTimelineInfo(uint64_t frameNumber,
+                                                const FrameTimelineInfo& frameTimelineInfo) {
+    ATRACE_FORMAT("%s(%s) frameNumber: %" PRIu64 " vsyncId: %" PRId64, __func__, mName.c_str(),
+                  frameNumber, frameTimelineInfo.vsyncId);
     std::unique_lock _lock{mMutex};
-    mNextFrameTimelineInfoQueue.push(frameTimelineInfo);
+    mPendingFrameTimelines.push({frameNumber, frameTimelineInfo});
     return OK;
 }
 
@@ -1097,9 +1149,9 @@
     }
 
     // Clear sync states
-    if (mWaitForTransactionCallback) {
-        BQA_LOGD("mWaitForTransactionCallback cleared");
-        mWaitForTransactionCallback = false;
+    if (!mSyncedFrameNumbers.empty()) {
+        BQA_LOGD("mSyncedFrameNumbers cleared");
+        mSyncedFrameNumbers.clear();
     }
 
     if (mSyncTransaction != nullptr) {
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 54b6d6a..4c80792 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -1862,12 +1862,13 @@
 
 int Surface::dispatchSetFrameTimelineInfo(va_list args) {
     ATRACE_CALL();
+    auto frameNumber = static_cast<uint64_t>(va_arg(args, uint64_t));
     auto frameTimelineVsyncId = static_cast<int64_t>(va_arg(args, int64_t));
     auto inputEventId = static_cast<int32_t>(va_arg(args, int32_t));
     auto startTimeNanos = static_cast<int64_t>(va_arg(args, int64_t));
 
     ALOGV("Surface::%s", __func__);
-    return setFrameTimelineInfo({frameTimelineVsyncId, inputEventId, startTimeNanos});
+    return setFrameTimelineInfo(frameNumber, {frameTimelineVsyncId, inputEventId, startTimeNanos});
 }
 
 bool Surface::transformToDisplayInverse() const {
@@ -2637,7 +2638,8 @@
                                            changeFrameRateStrategy);
 }
 
-status_t Surface::setFrameTimelineInfo(const FrameTimelineInfo& frameTimelineInfo) {
+status_t Surface::setFrameTimelineInfo(uint64_t /*frameNumber*/,
+                                       const FrameTimelineInfo& frameTimelineInfo) {
     return composerService()->setFrameTimelineInfo(mGraphicBufferProducer, frameTimelineInfo);
 }
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 47d801a..0f5192d 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -352,7 +352,8 @@
                                       transactionStats.latchTime, surfaceStats.acquireTimeOrFence,
                                       transactionStats.presentFence,
                                       surfaceStats.previousReleaseFence, surfaceStats.transformHint,
-                                      surfaceStats.eventStats);
+                                      surfaceStats.eventStats,
+                                      surfaceStats.currentMaxAcquiredBufferCount);
             }
 
             callbackFunction(transactionStats.latchTime, transactionStats.presentFence,
@@ -377,7 +378,8 @@
                                       transactionStats.latchTime, surfaceStats.acquireTimeOrFence,
                                       transactionStats.presentFence,
                                       surfaceStats.previousReleaseFence, surfaceStats.transformHint,
-                                      surfaceStats.eventStats);
+                                      surfaceStats.eventStats,
+                                      surfaceStats.currentMaxAcquiredBufferCount);
                 if (callbacksMap[callbackId].surfaceControls[surfaceStats.surfaceControl]) {
                     callbacksMap[callbackId]
                             .surfaceControls[surfaceStats.surfaceControl]
@@ -897,6 +899,10 @@
     mApplyToken = nullptr;
 }
 
+uint64_t SurfaceComposerClient::Transaction::getId() {
+    return mId;
+}
+
 void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) {
     sp<ISurfaceComposer> sf(ComposerService::getComposerService());
 
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 9328a54..4007667 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -95,9 +95,13 @@
                                      const std::vector<SurfaceControlStats>& stats);
     void releaseBufferCallback(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
                                std::optional<uint32_t> currentMaxAcquiredBufferCount);
+    void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
+                                     std::optional<uint32_t> currentMaxAcquiredBufferCount,
+                                     bool fakeRelease);
     void syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
                              bool acquireSingleBuffer = true);
     void stopContinuousSyncTransaction();
+
     void mergeWithNextTransaction(SurfaceComposerClient::Transaction* t, uint64_t frameNumber);
     void applyPendingTransactions(uint64_t frameNumber);
     SurfaceComposerClient::Transaction* gatherPendingTransactions(uint64_t frameNumber);
@@ -105,7 +109,7 @@
     void update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height, int32_t format);
 
     status_t setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless);
-    status_t setFrameTimelineInfo(const FrameTimelineInfo& info);
+    status_t setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& info);
 
     void setSidebandStream(const sp<NativeHandle>& stream);
 
@@ -132,12 +136,11 @@
     void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
                            sp<IGraphicBufferConsumer>* outConsumer);
 
-    void acquireNextBufferLocked(
+    status_t acquireNextBufferLocked(
             const std::optional<SurfaceComposerClient::Transaction*> transaction) REQUIRES(mMutex);
     Rect computeCrop(const BufferItem& item) REQUIRES(mMutex);
     // Return true if we need to reject the buffer based on the scaling mode and the buffer size.
     bool rejectBuffer(const BufferItem& item) REQUIRES(mMutex);
-    bool maxBuffersAcquired(bool includeExtraAcquire) const REQUIRES(mMutex);
     static PixelFormat convertBufferFormat(PixelFormat& format);
     void mergePendingTransactions(SurfaceComposerClient::Transaction* t, uint64_t frameNumber)
             REQUIRES(mMutex);
@@ -146,7 +149,6 @@
     void acquireAndReleaseBuffer() REQUIRES(mMutex);
     void releaseBuffer(const ReleaseCallbackId& callbackId, const sp<Fence>& releaseFence)
             REQUIRES(mMutex);
-    void flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock);
 
     std::string mName;
     // Represents the queued buffer count from buffer queue,
@@ -177,6 +179,12 @@
     struct ReleasedBuffer {
         ReleaseCallbackId callbackId;
         sp<Fence> releaseFence;
+        bool operator==(const ReleasedBuffer& rhs) const {
+            // Only compare Id so if we somehow got two callbacks
+            // with different fences we don't decrement mNumAcquired
+            // too far.
+            return rhs.callbackId == callbackId;
+        }
     };
     std::deque<ReleasedBuffer> mPendingRelease GUARDED_BY(mMutex);
 
@@ -229,7 +237,7 @@
     std::vector<std::tuple<uint64_t /* framenumber */, SurfaceComposerClient::Transaction>>
             mPendingTransactions GUARDED_BY(mMutex);
 
-    std::queue<FrameTimelineInfo> mNextFrameTimelineInfoQueue GUARDED_BY(mMutex);
+    std::queue<std::pair<uint64_t, FrameTimelineInfo>> mPendingFrameTimelines GUARDED_BY(mMutex);
 
     // Tracks the last acquired frame number
     uint64_t mLastAcquiredFrameNumber GUARDED_BY(mMutex) = 0;
@@ -251,7 +259,6 @@
     std::queue<sp<SurfaceControl>> mSurfaceControlsWithPendingCallback GUARDED_BY(mMutex);
 
     uint32_t mCurrentMaxAcquiredBufferCount;
-    bool mWaitForTransactionCallback GUARDED_BY(mMutex) = false;
 
     // Flag to determine if syncTransaction should only acquire a single buffer and then clear or
     // continue to acquire buffers until explicitly cleared
@@ -279,6 +286,8 @@
     uint64_t mLastAppliedFrameNumber = 0;
 
     std::function<void(bool)> mTransactionHangCallback;
+
+    std::unordered_set<uint64_t> mSyncedFrameNumbers GUARDED_BY(mMutex);
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index ab9ebaa..8d4e15b 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -193,7 +193,7 @@
 
     virtual status_t setFrameRate(float frameRate, int8_t compatibility,
                                   int8_t changeFrameRateStrategy);
-    virtual status_t setFrameTimelineInfo(const FrameTimelineInfo& info);
+    virtual status_t setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& info);
 
 protected:
     virtual ~Surface();
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index efbdb36..9033e17 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -65,14 +65,16 @@
     SurfaceControlStats(const sp<SurfaceControl>& sc, nsecs_t latchTime,
                         std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence,
                         const sp<Fence>& presentFence, const sp<Fence>& prevReleaseFence,
-                        uint32_t hint, FrameEventHistoryStats eventStats)
+                        uint32_t hint, FrameEventHistoryStats eventStats,
+                        uint32_t currentMaxAcquiredBufferCount)
           : surfaceControl(sc),
             latchTime(latchTime),
             acquireTimeOrFence(std::move(acquireTimeOrFence)),
             presentFence(presentFence),
             previousReleaseFence(prevReleaseFence),
             transformHint(hint),
-            frameEventStats(eventStats) {}
+            frameEventStats(eventStats),
+            currentMaxAcquiredBufferCount(currentMaxAcquiredBufferCount) {}
 
     sp<SurfaceControl> surfaceControl;
     nsecs_t latchTime = -1;
@@ -81,6 +83,7 @@
     sp<Fence> previousReleaseFence;
     uint32_t transformHint = 0;
     FrameEventHistoryStats frameEventStats;
+    uint32_t currentMaxAcquiredBufferCount = 0;
 };
 
 using TransactionCompletedCallbackTakesContext =
@@ -461,6 +464,10 @@
         // Clears the contents of the transaction without applying it.
         void clear();
 
+        // Returns the current id of the transaction.
+        // The id is updated every time the transaction is applied.
+        uint64_t getId();
+
         status_t apply(bool synchronous = false, bool oneWay = false);
         // Merge another transaction in to this one, clearing other
         // as if it had been applied.
diff --git a/libs/gui/include/gui/TraceUtils.h b/libs/gui/include/gui/TraceUtils.h
index e5d2684..0009615 100644
--- a/libs/gui/include/gui/TraceUtils.h
+++ b/libs/gui/include/gui/TraceUtils.h
@@ -27,6 +27,8 @@
 
 #define ATRACE_FORMAT_BEGIN(fmt, ...) TraceUtils::atraceFormatBegin(fmt, ##__VA_ARGS__)
 
+#define ATRACE_FORMAT_INSTANT(fmt, ...) TraceUtils::intantFormat(fmt, ##__VA_ARGS__)
+
 namespace android {
 
 class TraceUtils {
@@ -50,6 +52,20 @@
         ATRACE_BEGIN(buf);
     }
 
+    static void intantFormat(const char* fmt, ...) {
+        if (CC_LIKELY(!ATRACE_ENABLED())) return;
+
+        const int BUFFER_SIZE = 256;
+        va_list ap;
+        char buf[BUFFER_SIZE];
+
+        va_start(ap, fmt);
+        vsnprintf(buf, BUFFER_SIZE, fmt, ap);
+        va_end(ap);
+
+        ATRACE_INSTANT(buf);
+    }
+
 }; // class TraceUtils
 
 } /* namespace android */
diff --git a/libs/gui/include/gui/test/CallbackUtils.h b/libs/gui/include/gui/test/CallbackUtils.h
index 62d1496..08785b4 100644
--- a/libs/gui/include/gui/test/CallbackUtils.h
+++ b/libs/gui/include/gui/test/CallbackUtils.h
@@ -135,7 +135,8 @@
         void verifySurfaceControlStats(const SurfaceControlStats& surfaceControlStats,
                                        nsecs_t latchTime) const {
             const auto& [surfaceControl, latch, acquireTimeOrFence, presentFence,
-                         previousReleaseFence, transformHint, frameEvents] = surfaceControlStats;
+                         previousReleaseFence, transformHint, frameEvents, ignore] =
+                surfaceControlStats;
 
             ASSERT_TRUE(std::holds_alternative<nsecs_t>(acquireTimeOrFence));
             ASSERT_EQ(std::get<nsecs_t>(acquireTimeOrFence) > 0,
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index cb7e94c..b993289 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -161,6 +161,10 @@
         ASSERT_EQ(numFramesSubmitted, mBlastBufferQueueAdapter->mSubmitted.size());
     }
 
+    void mergeWithNextTransaction(Transaction* merge, uint64_t frameNumber) {
+        mBlastBufferQueueAdapter->mergeWithNextTransaction(merge, frameNumber);
+    }
+
 private:
     sp<TestBLASTBufferQueue> mBlastBufferQueueAdapter;
 };
@@ -1111,6 +1115,39 @@
     ASSERT_TRUE(receivedCallback);
 }
 
+TEST_F(BLASTBufferQueueTest, SyncNextTransactionDropBuffer) {
+    uint8_t r = 255;
+    uint8_t g = 0;
+    uint8_t b = 0;
+
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+
+    Transaction sync;
+    adapter.setSyncTransaction(sync);
+    queueBuffer(igbProducer, 0, 255, 0, 0);
+
+    // Merge a transaction that has a complete callback into the next frame so we can get notified
+    // when to take a screenshot
+    CallbackHelper transactionCallback;
+    Transaction t;
+    t.addTransactionCompletedCallback(transactionCallback.function,
+                                      transactionCallback.getContext());
+    adapter.mergeWithNextTransaction(&t, 2);
+    queueBuffer(igbProducer, r, g, b, 0);
+
+    // Drop the buffer, but ensure the next one continues to get processed.
+    sync.setBuffer(mSurfaceControl, nullptr);
+
+    CallbackData callbackData;
+    transactionCallback.getCallbackData(&callbackData);
+    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+}
+
 // This test will currently fail because the old surfacecontrol will steal the last presented buffer
 // until the old surface control is destroyed. This is not necessarily a bug but to document a
 // limitation with the update API and to test any changes to make the api more robust. The current
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index c6cdeb7..2637f59 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -1184,18 +1184,23 @@
     std::vector<sp<IGraphicBufferProducer>> mProducers;
 };
 
-TEST_F(MultiDisplayTests, drop_input_if_layer_on_invalid_display) {
+TEST_F(MultiDisplayTests, drop_touch_if_layer_on_invalid_display) {
     ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
     // Do not create a display associated with the LayerStack.
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); });
     surface->showAt(100, 100);
 
+    // Touches should be dropped if the layer is on an invalid display.
     injectTapOnDisplay(101, 101, layerStack.id);
-    surface->requestFocus(layerStack.id);
-    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
-
     EXPECT_EQ(surface->consumeEvent(100), nullptr);
+
+    // However, we still let the window be focused and receive keys.
+    surface->requestFocus(layerStack.id);
+    surface->assertFocusChange(true);
+
+    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+    surface->expectKey(AKEYCODE_V);
 }
 
 TEST_F(MultiDisplayTests, virtual_display_receives_input) {
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 5d7874a..1335e4d 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -67,6 +67,7 @@
         "libbase",
         "liblog",
         "libcutils",
+        "libvintf",
     ],
 
     static_libs: [
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 13ca9ec..155cb04 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -64,9 +64,10 @@
 }
 
 bool shouldDisregardTransformation(uint32_t source) {
-    // Do not apply any transformations to axes from joysticks or touchpads.
+    // Do not apply any transformations to axes from joysticks, touchpads, or relative mice.
     return isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK) ||
-            isFromSource(source, AINPUT_SOURCE_CLASS_POSITION);
+            isFromSource(source, AINPUT_SOURCE_CLASS_POSITION) ||
+            isFromSource(source, AINPUT_SOURCE_MOUSE_RELATIVE);
 }
 
 bool shouldDisregardOffset(uint32_t source) {
@@ -89,6 +90,25 @@
     }
 }
 
+const char* motionToolTypeToString(int32_t toolType) {
+    switch (toolType) {
+        case AMOTION_EVENT_TOOL_TYPE_UNKNOWN:
+            return "UNKNOWN";
+        case AMOTION_EVENT_TOOL_TYPE_FINGER:
+            return "FINGER";
+        case AMOTION_EVENT_TOOL_TYPE_STYLUS:
+            return "STYLUS";
+        case AMOTION_EVENT_TOOL_TYPE_MOUSE:
+            return "MOUSE";
+        case AMOTION_EVENT_TOOL_TYPE_ERASER:
+            return "ERASER";
+        case AMOTION_EVENT_TOOL_TYPE_PALM:
+            return "PALM";
+        default:
+            return "INVALID";
+    }
+}
+
 // --- IdGenerator ---
 IdGenerator::IdGenerator(Source source) : mSource(source) {}
 
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 0bee1b6..a908969 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -53,33 +53,39 @@
 }
 
 std::string getInputDeviceConfigurationFilePathByDeviceIdentifier(
-        const InputDeviceIdentifier& deviceIdentifier,
-        InputDeviceConfigurationFileType type) {
+        const InputDeviceIdentifier& deviceIdentifier, InputDeviceConfigurationFileType type,
+        const char* suffix) {
     if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
         if (deviceIdentifier.version != 0) {
             // Try vendor product version.
-            std::string versionPath = getInputDeviceConfigurationFilePathByName(
-                    StringPrintf("Vendor_%04x_Product_%04x_Version_%04x",
-                            deviceIdentifier.vendor, deviceIdentifier.product,
-                            deviceIdentifier.version),
-                    type);
+            std::string versionPath =
+                    getInputDeviceConfigurationFilePathByName(StringPrintf("Vendor_%04x_Product_%"
+                                                                           "04x_Version_%04x%s",
+                                                                           deviceIdentifier.vendor,
+                                                                           deviceIdentifier.product,
+                                                                           deviceIdentifier.version,
+                                                                           suffix),
+                                                              type);
             if (!versionPath.empty()) {
                 return versionPath;
             }
         }
 
         // Try vendor product.
-        std::string productPath = getInputDeviceConfigurationFilePathByName(
-                StringPrintf("Vendor_%04x_Product_%04x",
-                        deviceIdentifier.vendor, deviceIdentifier.product),
-                type);
+        std::string productPath =
+                getInputDeviceConfigurationFilePathByName(StringPrintf("Vendor_%04x_Product_%04x%s",
+                                                                       deviceIdentifier.vendor,
+                                                                       deviceIdentifier.product,
+                                                                       suffix),
+                                                          type);
         if (!productPath.empty()) {
             return productPath;
         }
     }
 
     // Try device name.
-    return getInputDeviceConfigurationFilePathByName(deviceIdentifier.getCanonicalName(), type);
+    return getInputDeviceConfigurationFilePathByName(deviceIdentifier.getCanonicalName() + suffix,
+                                                     type);
 }
 
 std::string getInputDeviceConfigurationFilePathByName(
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index c0aa2e2..2d768ce 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -314,7 +314,23 @@
     DEFINE_KEYCODE(REFRESH), \
     DEFINE_KEYCODE(THUMBS_UP), \
     DEFINE_KEYCODE(THUMBS_DOWN), \
-    DEFINE_KEYCODE(PROFILE_SWITCH)
+    DEFINE_KEYCODE(PROFILE_SWITCH), \
+    DEFINE_KEYCODE(VIDEO_APP_1), \
+    DEFINE_KEYCODE(VIDEO_APP_2), \
+    DEFINE_KEYCODE(VIDEO_APP_3), \
+    DEFINE_KEYCODE(VIDEO_APP_4), \
+    DEFINE_KEYCODE(VIDEO_APP_5), \
+    DEFINE_KEYCODE(VIDEO_APP_6), \
+    DEFINE_KEYCODE(VIDEO_APP_7), \
+    DEFINE_KEYCODE(VIDEO_APP_8), \
+    DEFINE_KEYCODE(FEATURED_APP_1), \
+    DEFINE_KEYCODE(FEATURED_APP_2), \
+    DEFINE_KEYCODE(FEATURED_APP_3), \
+    DEFINE_KEYCODE(FEATURED_APP_4), \
+    DEFINE_KEYCODE(DEMO_APP_1), \
+    DEFINE_KEYCODE(DEMO_APP_2), \
+    DEFINE_KEYCODE(DEMO_APP_3), \
+    DEFINE_KEYCODE(DEMO_APP_4)
 
 // NOTE: If you add a new axis here you must also add it to several other files.
 //       Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp
index 7c25cda..170e748 100644
--- a/libs/input/KeyLayoutMap.cpp
+++ b/libs/input/KeyLayoutMap.cpp
@@ -21,24 +21,33 @@
 #include <input/InputEventLabels.h>
 #include <input/KeyLayoutMap.h>
 #include <input/Keyboard.h>
+#include <log/log.h>
 #include <utils/Errors.h>
-#include <utils/Log.h>
 #include <utils/Timers.h>
 #include <utils/Tokenizer.h>
+#include <vintf/RuntimeInfo.h>
+#include <vintf/VintfObject.h>
 
 #include <cstdlib>
 #include <string_view>
 #include <unordered_map>
 
-// Enables debug output for the parser.
-#define DEBUG_PARSER 0
+/**
+ * Log debug output for the parser.
+ * Enable this via "adb shell setprop log.tag.KeyLayoutMapParser DEBUG" (requires restart)
+ */
+const bool DEBUG_PARSER =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Parser", ANDROID_LOG_INFO);
 
 // Enables debug output for parser performance.
 #define DEBUG_PARSER_PERFORMANCE 0
 
-// Enables debug output for mapping.
-#define DEBUG_MAPPING 0
-
+/**
+ * Log debug output for mapping.
+ * Enable this via "adb shell setprop log.tag.KeyLayoutMapMapping DEBUG" (requires restart)
+ */
+const bool DEBUG_MAPPING =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Mapping", ANDROID_LOG_INFO);
 
 namespace android {
 namespace {
@@ -69,6 +78,29 @@
          sensorPair<InputDeviceSensorType::GYROSCOPE_UNCALIBRATED>(),
          sensorPair<InputDeviceSensorType::SIGNIFICANT_MOTION>()};
 
+bool kernelConfigsArePresent(const std::set<std::string>& configs) {
+    std::shared_ptr<const android::vintf::RuntimeInfo> runtimeInfo =
+            android::vintf::VintfObject::GetInstance()->getRuntimeInfo(
+                    vintf::RuntimeInfo::FetchFlag::CONFIG_GZ);
+    LOG_ALWAYS_FATAL_IF(runtimeInfo == nullptr, "Kernel configs could not be fetched");
+
+    const std::map<std::string, std::string>& kernelConfigs = runtimeInfo->kernelConfigs();
+    for (const std::string& requiredConfig : configs) {
+        const auto configIt = kernelConfigs.find(requiredConfig);
+        if (configIt == kernelConfigs.end()) {
+            ALOGI("Required kernel config %s is not found", requiredConfig.c_str());
+            return false;
+        }
+        const std::string& option = configIt->second;
+        if (option != "y" && option != "m") {
+            ALOGI("Required kernel config %s has option %s", requiredConfig.c_str(),
+                  option.c_str());
+            return false;
+        }
+    }
+    return true;
+}
+
 } // namespace
 
 KeyLayoutMap::KeyLayoutMap() = default;
@@ -76,32 +108,34 @@
 
 base::Result<std::shared_ptr<KeyLayoutMap>> KeyLayoutMap::loadContents(const std::string& filename,
                                                                        const char* contents) {
-    Tokenizer* tokenizer;
-    status_t status = Tokenizer::fromContents(String8(filename.c_str()), contents, &tokenizer);
-    if (status) {
-        ALOGE("Error %d opening key layout map.", status);
-        return Errorf("Error {} opening key layout map file {}.", status, filename.c_str());
-    }
-    std::unique_ptr<Tokenizer> t(tokenizer);
-    auto ret = load(t.get());
-    if (ret.ok()) {
-        (*ret)->mLoadFileName = filename;
-    }
-    return ret;
+    return load(filename, contents);
 }
 
-base::Result<std::shared_ptr<KeyLayoutMap>> KeyLayoutMap::load(const std::string& filename) {
+base::Result<std::shared_ptr<KeyLayoutMap>> KeyLayoutMap::load(const std::string& filename,
+                                                               const char* contents) {
     Tokenizer* tokenizer;
-    status_t status = Tokenizer::open(String8(filename.c_str()), &tokenizer);
+    status_t status;
+    if (contents == nullptr) {
+        status = Tokenizer::open(String8(filename.c_str()), &tokenizer);
+    } else {
+        status = Tokenizer::fromContents(String8(filename.c_str()), contents, &tokenizer);
+    }
     if (status) {
         ALOGE("Error %d opening key layout map file %s.", status, filename.c_str());
         return Errorf("Error {} opening key layout map file {}.", status, filename.c_str());
     }
     std::unique_ptr<Tokenizer> t(tokenizer);
     auto ret = load(t.get());
-    if (ret.ok()) {
-        (*ret)->mLoadFileName = filename;
+    if (!ret.ok()) {
+        return ret;
     }
+    const std::shared_ptr<KeyLayoutMap>& map = *ret;
+    LOG_ALWAYS_FATAL_IF(map == nullptr, "Returned map should not be null if there's no error");
+    if (!kernelConfigsArePresent(map->mRequiredKernelConfigs)) {
+        ALOGI("Not loading %s because the required kernel configs are not set", filename.c_str());
+        return Errorf("Missing kernel config");
+    }
+    map->mLoadFileName = filename;
     return ret;
 }
 
@@ -134,9 +168,8 @@
         int32_t* outKeyCode, uint32_t* outFlags) const {
     const Key* key = getKey(scanCode, usageCode);
     if (!key) {
-#if DEBUG_MAPPING
-        ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Failed.", scanCode, usageCode);
-#endif
+        ALOGD_IF(DEBUG_MAPPING, "mapKey: scanCode=%d, usageCode=0x%08x ~ Failed.", scanCode,
+                 usageCode);
         *outKeyCode = AKEYCODE_UNKNOWN;
         *outFlags = 0;
         return NAME_NOT_FOUND;
@@ -145,10 +178,9 @@
     *outKeyCode = key->keyCode;
     *outFlags = key->flags;
 
-#if DEBUG_MAPPING
-    ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d, outFlags=0x%08x.",
-            scanCode, usageCode, *outKeyCode, *outFlags);
-#endif
+    ALOGD_IF(DEBUG_MAPPING,
+             "mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d, outFlags=0x%08x.",
+             scanCode, usageCode, *outKeyCode, *outFlags);
     return NO_ERROR;
 }
 
@@ -156,17 +188,12 @@
 base::Result<std::pair<InputDeviceSensorType, int32_t>> KeyLayoutMap::mapSensor(int32_t absCode) {
     auto it = mSensorsByAbsCode.find(absCode);
     if (it == mSensorsByAbsCode.end()) {
-#if DEBUG_MAPPING
-        ALOGD("mapSensor: absCode=%d, ~ Failed.", absCode);
-#endif
+        ALOGD_IF(DEBUG_MAPPING, "mapSensor: absCode=%d, ~ Failed.", absCode);
         return Errorf("Can't find abs code {}.", absCode);
     }
     const Sensor& sensor = it->second;
-
-#if DEBUG_MAPPING
-    ALOGD("mapSensor: absCode=%d, sensorType=%s, sensorDataIndex=0x%x.", absCode,
-          ftl::enum_string(sensor.sensorType).c_str(), sensor.sensorDataIndex);
-#endif
+    ALOGD_IF(DEBUG_MAPPING, "mapSensor: absCode=%d, sensorType=%s, sensorDataIndex=0x%x.", absCode,
+             ftl::enum_string(sensor.sensorType).c_str(), sensor.sensorDataIndex);
     return std::make_pair(sensor.sensorType, sensor.sensorDataIndex);
 }
 
@@ -200,21 +227,18 @@
 status_t KeyLayoutMap::mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const {
     ssize_t index = mAxes.indexOfKey(scanCode);
     if (index < 0) {
-#if DEBUG_MAPPING
-        ALOGD("mapAxis: scanCode=%d ~ Failed.", scanCode);
-#endif
+        ALOGD_IF(DEBUG_MAPPING, "mapAxis: scanCode=%d ~ Failed.", scanCode);
         return NAME_NOT_FOUND;
     }
 
     *outAxisInfo = mAxes.valueAt(index);
 
-#if DEBUG_MAPPING
-    ALOGD("mapAxis: scanCode=%d ~ Result mode=%d, axis=%d, highAxis=%d, "
-            "splitValue=%d, flatOverride=%d.",
-            scanCode,
-            outAxisInfo->mode, outAxisInfo->axis, outAxisInfo->highAxis,
-            outAxisInfo->splitValue, outAxisInfo->flatOverride);
-#endif
+    ALOGD_IF(DEBUG_MAPPING,
+             "mapAxis: scanCode=%d ~ Result mode=%d, axis=%d, highAxis=%d, "
+             "splitValue=%d, flatOverride=%d.",
+             scanCode, outAxisInfo->mode, outAxisInfo->axis, outAxisInfo->highAxis,
+             outAxisInfo->splitValue, outAxisInfo->flatOverride);
+
     return NO_ERROR;
 }
 
@@ -223,15 +247,12 @@
     for (size_t i = 0; i < N; i++) {
         if (mLedsByScanCode.valueAt(i).ledCode == ledCode) {
             *outScanCode = mLedsByScanCode.keyAt(i);
-#if DEBUG_MAPPING
-            ALOGD("findScanCodeForLed: ledCode=%d, scanCode=%d.", ledCode, *outScanCode);
-#endif
+            ALOGD_IF(DEBUG_MAPPING, "findScanCodeForLed: ledCode=%d, scanCode=%d.", ledCode,
+                     *outScanCode);
             return NO_ERROR;
         }
     }
-#if DEBUG_MAPPING
-            ALOGD("findScanCodeForLed: ledCode=%d ~ Not found.", ledCode);
-#endif
+    ALOGD_IF(DEBUG_MAPPING, "findScanCodeForLed: ledCode=%d ~ Not found.", ledCode);
     return NAME_NOT_FOUND;
 }
 
@@ -240,15 +261,12 @@
     for (size_t i = 0; i < N; i++) {
         if (mLedsByUsageCode.valueAt(i).ledCode == ledCode) {
             *outUsageCode = mLedsByUsageCode.keyAt(i);
-#if DEBUG_MAPPING
-            ALOGD("findUsageForLed: ledCode=%d, usage=%x.", ledCode, *outUsageCode);
-#endif
+            ALOGD_IF(DEBUG_MAPPING, "%s: ledCode=%d, usage=%x.", __func__, ledCode, *outUsageCode);
             return NO_ERROR;
         }
     }
-#if DEBUG_MAPPING
-            ALOGD("findUsageForLed: ledCode=%d ~ Not found.", ledCode);
-#endif
+    ALOGD_IF(DEBUG_MAPPING, "%s: ledCode=%d ~ Not found.", __func__, ledCode);
+
     return NAME_NOT_FOUND;
 }
 
@@ -264,10 +282,8 @@
 
 status_t KeyLayoutMap::Parser::parse() {
     while (!mTokenizer->isEof()) {
-#if DEBUG_PARSER
-        ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
-                mTokenizer->peekRemainderOfLine().string());
-#endif
+        ALOGD_IF(DEBUG_PARSER, "Parsing %s: '%s'.", mTokenizer->getLocation().string(),
+                 mTokenizer->peekRemainderOfLine().string());
 
         mTokenizer->skipDelimiters(WHITESPACE);
 
@@ -289,6 +305,10 @@
                 mTokenizer->skipDelimiters(WHITESPACE);
                 status_t status = parseSensor();
                 if (status) return status;
+            } else if (keywordToken == "requires_kernel_config") {
+                mTokenizer->skipDelimiters(WHITESPACE);
+                status_t status = parseRequiredKernelConfig();
+                if (status) return status;
             } else {
                 ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
                         keywordToken.string());
@@ -361,10 +381,9 @@
         flags |= flag;
     }
 
-#if DEBUG_PARSER
-    ALOGD("Parsed key %s: code=%d, keyCode=%d, flags=0x%08x.",
-            mapUsage ? "usage" : "scan code", code, keyCode, flags);
-#endif
+    ALOGD_IF(DEBUG_PARSER, "Parsed key %s: code=%d, keyCode=%d, flags=0x%08x.",
+             mapUsage ? "usage" : "scan code", code, keyCode, flags);
+
     Key key;
     key.keyCode = keyCode;
     key.flags = flags;
@@ -462,13 +481,12 @@
         }
     }
 
-#if DEBUG_PARSER
-    ALOGD("Parsed axis: scanCode=%d, mode=%d, axis=%d, highAxis=%d, "
-            "splitValue=%d, flatOverride=%d.",
-            scanCode,
-            axisInfo.mode, axisInfo.axis, axisInfo.highAxis,
-            axisInfo.splitValue, axisInfo.flatOverride);
-#endif
+    ALOGD_IF(DEBUG_PARSER,
+             "Parsed axis: scanCode=%d, mode=%d, axis=%d, highAxis=%d, "
+             "splitValue=%d, flatOverride=%d.",
+             scanCode, axisInfo.mode, axisInfo.axis, axisInfo.highAxis, axisInfo.splitValue,
+             axisInfo.flatOverride);
+
     mMap->mAxes.add(scanCode, axisInfo);
     return NO_ERROR;
 }
@@ -505,10 +523,8 @@
         return BAD_VALUE;
     }
 
-#if DEBUG_PARSER
-    ALOGD("Parsed led %s: code=%d, ledCode=%d.",
-            mapUsage ? "usage" : "scan code", code, ledCode);
-#endif
+    ALOGD_IF(DEBUG_PARSER, "Parsed led %s: code=%d, ledCode=%d.", mapUsage ? "usage" : "scan code",
+             code, ledCode);
 
     Led led;
     led.ledCode = ledCode;
@@ -584,10 +600,8 @@
     }
     int32_t sensorDataIndex = indexOpt.value();
 
-#if DEBUG_PARSER
-    ALOGD("Parsed sensor: abs code=%d, sensorType=%s, sensorDataIndex=%d.", code,
-          ftl::enum_string(sensorType).c_str(), sensorDataIndex);
-#endif
+    ALOGD_IF(DEBUG_PARSER, "Parsed sensor: abs code=%d, sensorType=%s, sensorDataIndex=%d.", code,
+             ftl::enum_string(sensorType).c_str(), sensorDataIndex);
 
     Sensor sensor;
     sensor.sensorType = sensorType;
@@ -596,4 +610,23 @@
     return NO_ERROR;
 }
 
+// Parse the name of a required kernel config.
+// The layout won't be used if the specified kernel config is not present
+// Examples:
+// requires_kernel_config CONFIG_HID_PLAYSTATION
+status_t KeyLayoutMap::Parser::parseRequiredKernelConfig() {
+    String8 codeToken = mTokenizer->nextToken(WHITESPACE);
+    std::string configName = codeToken.string();
+
+    const auto result = mMap->mRequiredKernelConfigs.emplace(configName);
+    if (!result.second) {
+        ALOGE("%s: Duplicate entry for required kernel config %s.",
+              mTokenizer->getLocation().string(), configName.c_str());
+        return BAD_VALUE;
+    }
+
+    ALOGD_IF(DEBUG_PARSER, "Parsed required kernel config: name=%s", configName.c_str());
+    return NO_ERROR;
+}
+
 } // namespace android
diff --git a/libs/input/Keyboard.cpp b/libs/input/Keyboard.cpp
index f0895b3..c3f5151 100644
--- a/libs/input/Keyboard.cpp
+++ b/libs/input/Keyboard.cpp
@@ -20,16 +20,23 @@
 #include <unistd.h>
 #include <limits.h>
 
-#include <input/Keyboard.h>
-#include <input/InputEventLabels.h>
-#include <input/KeyLayoutMap.h>
-#include <input/KeyCharacterMap.h>
 #include <input/InputDevice.h>
+#include <input/InputEventLabels.h>
+#include <input/KeyCharacterMap.h>
+#include <input/KeyLayoutMap.h>
+#include <input/Keyboard.h>
+#include <log/log.h>
 #include <utils/Errors.h>
-#include <utils/Log.h>
 
 namespace android {
 
+static std::string getPath(const InputDeviceIdentifier& deviceIdentifier, const std::string& name,
+                           InputDeviceConfigurationFileType type) {
+    return name.empty()
+            ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type)
+            : getInputDeviceConfigurationFilePathByName(name, type);
+}
+
 // --- KeyMap ---
 
 KeyMap::KeyMap() {
@@ -111,11 +118,25 @@
     }
 
     base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(path);
+    if (ret.ok()) {
+        keyLayoutMap = *ret;
+        keyLayoutFile = path;
+        return OK;
+    }
+
+    // Try to load fallback layout if the regular layout could not be loaded due to missing
+    // kernel modules
+    std::string fallbackPath(
+            getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier,
+                                                                  InputDeviceConfigurationFileType::
+                                                                          KEY_LAYOUT,
+                                                                  "_fallback"));
+    ret = KeyLayoutMap::load(fallbackPath);
     if (!ret.ok()) {
         return ret.error().code();
     }
     keyLayoutMap = *ret;
-    keyLayoutFile = path;
+    keyLayoutFile = fallbackPath;
     return OK;
 }
 
@@ -137,14 +158,6 @@
     return OK;
 }
 
-std::string KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier,
-        const std::string& name, InputDeviceConfigurationFileType type) {
-    return name.empty()
-            ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type)
-            : getInputDeviceConfigurationFilePathByName(name, type);
-}
-
-
 // --- Global functions ---
 
 bool isKeyboardSpecialFunction(const PropertyMap* config) {
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 6ffe851..d947cd9 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -36,8 +36,9 @@
         "liblog",
         "libui",
         "libutils",
+        "libvintf",
     ],
-    data: ["data/*.kcm"],
+    data: ["data/*"],
     test_suites: ["device-tests"],
 }
 
diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp
index 61e88df..e872fa4 100644
--- a/libs/input/tests/InputDevice_test.cpp
+++ b/libs/input/tests/InputDevice_test.cpp
@@ -64,13 +64,11 @@
         mKeyMap.keyCharacterMapFile = path;
     }
 
-    virtual void SetUp() override {
+    void SetUp() override {
         loadKeyLayout("Generic");
         loadKeyCharacterMap("Generic");
     }
 
-    virtual void TearDown() override {}
-
     KeyMap mKeyMap;
 };
 
@@ -132,4 +130,20 @@
     ASSERT_EQ(*mKeyMap.keyCharacterMap, *frenchOverlaidKeyCharacterMap);
 }
 
+TEST(InputDeviceKeyLayoutTest, DoesNotLoadWhenRequiredKernelConfigIsMissing) {
+    std::string klPath = base::GetExecutableDirectory() + "/data/kl_with_required_fake_config.kl";
+    base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
+    ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath;
+    // We assert error message here because it's used by 'validatekeymaps' tool
+    ASSERT_EQ("Missing kernel config", ret.error().message());
+}
+
+TEST(InputDeviceKeyLayoutTest, LoadsWhenRequiredKernelConfigIsPresent) {
+    std::string klPath = base::GetExecutableDirectory() + "/data/kl_with_required_real_config.kl";
+    base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
+    ASSERT_TRUE(ret.ok()) << "Cannot load KeyLayout at " << klPath;
+    const std::shared_ptr<KeyLayoutMap>& map = *ret;
+    ASSERT_NE(nullptr, map) << "Map should be valid because CONFIG_UHID should always be present";
+}
+
 } // namespace android
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index a92016b..4b31246 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -715,10 +715,10 @@
 }
 
 TEST_F(MotionEventTest, JoystickAndTouchpadAreNotTransformed) {
-    constexpr static std::array kNonTransformedSources = {std::pair(AINPUT_SOURCE_TOUCHPAD,
-                                                                    AMOTION_EVENT_ACTION_DOWN),
-                                                          std::pair(AINPUT_SOURCE_JOYSTICK,
-                                                                    AMOTION_EVENT_ACTION_MOVE)};
+    constexpr static std::array kNonTransformedSources =
+            {std::pair(AINPUT_SOURCE_TOUCHPAD, AMOTION_EVENT_ACTION_DOWN),
+             std::pair(AINPUT_SOURCE_JOYSTICK, AMOTION_EVENT_ACTION_MOVE),
+             std::pair(AINPUT_SOURCE_MOUSE_RELATIVE, AMOTION_EVENT_ACTION_MOVE)};
     // Create a rotate-90 transform with an offset (like a window which isn't fullscreen).
     ui::Transform transform(ui::Transform::ROT_90, 800, 400);
     transform.set(transform.tx() + 20, transform.ty() + 40);
@@ -738,7 +738,7 @@
 TEST_F(MotionEventTest, NonPointerSourcesAreNotTranslated) {
     constexpr static std::array kNonPointerSources = {std::pair(AINPUT_SOURCE_TRACKBALL,
                                                                 AMOTION_EVENT_ACTION_DOWN),
-                                                      std::pair(AINPUT_SOURCE_MOUSE_RELATIVE,
+                                                      std::pair(AINPUT_SOURCE_TOUCH_NAVIGATION,
                                                                 AMOTION_EVENT_ACTION_MOVE)};
     // Create a rotate-90 transform with an offset (like a window which isn't fullscreen).
     ui::Transform transform(ui::Transform::ROT_90, 800, 400);
diff --git a/libs/input/tests/data/kl_with_required_fake_config.kl b/libs/input/tests/data/kl_with_required_fake_config.kl
new file mode 100644
index 0000000..2d0a507
--- /dev/null
+++ b/libs/input/tests/data/kl_with_required_fake_config.kl
@@ -0,0 +1,20 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This KL should not be loaded unless the below config is present in the kernel
+# This config will never exist, and therefore this KL should never be loaded
+requires_kernel_config CONFIG_HID_FAKEMODULE
+
+# An arbitrary mapping taken from another file
+key 0x130    BUTTON_X
\ No newline at end of file
diff --git a/libs/input/tests/data/kl_with_required_real_config.kl b/libs/input/tests/data/kl_with_required_real_config.kl
new file mode 100644
index 0000000..303b23e
--- /dev/null
+++ b/libs/input/tests/data/kl_with_required_real_config.kl
@@ -0,0 +1,21 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This KL should not be loaded unless the below config is present in the kernel
+# The CONFIG_UHID option has been required for a while, and therefore it's safe
+# to assume that this will always be loaded
+requires_kernel_config CONFIG_UHID
+
+# An arbitrary mapping taken from another file
+key 0x130    BUTTON_X
\ No newline at end of file
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index a54af1f..86e76c4 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1043,11 +1043,12 @@
 }
 
 static inline int native_window_set_frame_timeline_info(struct ANativeWindow* window,
+                                                        uint64_t frameNumber,
                                                         int64_t frameTimelineVsyncId,
                                                         int32_t inputEventId,
                                                         int64_t startTimeNanos) {
-    return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameTimelineVsyncId,
-                           inputEventId, startTimeNanos);
+    return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameNumber,
+                           frameTimelineVsyncId, inputEventId, startTimeNanos);
 }
 
 // ------------------------------------------------------------------------------------------------
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index 22dd866..6dc01b9 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -921,7 +921,8 @@
 
     // Finally, we cut the layer into 3 parts, with top and bottom parts having rounded corners
     // and the middle part without rounded corners.
-    const int32_t radius = ceil(layer.geometry.roundedCornersRadius);
+    const int32_t radius = ceil(
+            (layer.geometry.roundedCornersRadius.x + layer.geometry.roundedCornersRadius.y) / 2.0);
     const Rect topRect(bounds.left, bounds.top, bounds.right, bounds.top + radius);
     setScissor(topRect);
     drawMesh(mesh);
@@ -1266,23 +1267,24 @@
 
         const half3 solidColor = layer.source.solidColor;
         const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer.alpha);
+        const float radius =
+                (layer.geometry.roundedCornersRadius.x + layer.geometry.roundedCornersRadius.y) /
+                2.0f;
         // Buffer sources will have a black solid color ignored in the shader,
         // so in that scenario the solid color passed here is arbitrary.
-        setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color,
-                           layer.geometry.roundedCornersRadius);
+        setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color, radius);
         if (layer.disableBlending) {
             glDisable(GL_BLEND);
         }
         setSourceDataSpace(layer.sourceDataspace);
 
         if (layer.shadow.length > 0.0f) {
-            handleShadow(layer.geometry.boundaries, layer.geometry.roundedCornersRadius,
-                         layer.shadow);
+            handleShadow(layer.geometry.boundaries, radius, layer.shadow);
         }
         // We only want to do a special handling for rounded corners when having rounded corners
         // is the only reason it needs to turn on blending, otherwise, we handle it like the
         // usual way since it needs to turn on blending anyway.
-        else if (layer.geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) {
+        else if (radius > 0.0 && color.a >= 1.0f && isOpaque) {
             handleRoundedCorners(display, layer, mesh);
         } else {
             drawMesh(mesh);
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index 154e526..b3a617c 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -87,7 +87,7 @@
     // rectangle to figure out how to apply the radius for this layer. The crop rectangle will be
     // in local layer coordinate space, so we have to take the layer transform into account when
     // walking up the tree.
-    float roundedCornersRadius = 0.0;
+    vec2 roundedCornersRadius = vec2(0.0f, 0.0f);
 
     // Rectangle within which corners will be rounded.
     FloatRect roundedCornersCrop = FloatRect();
@@ -258,7 +258,8 @@
     PrintTo(settings.boundaries, os);
     *os << "\n    .positionTransform = ";
     PrintMatrix(settings.positionTransform, os);
-    *os << "\n    .roundedCornersRadius = " << settings.roundedCornersRadius;
+    *os << "\n    .roundedCornersRadiusX = " << settings.roundedCornersRadius.x;
+    *os << "\n    .roundedCornersRadiusY = " << settings.roundedCornersRadius.y;
     *os << "\n    .roundedCornersCrop = ";
     PrintTo(settings.roundedCornersCrop, os);
     *os << "\n}";
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index f3064f3..c39f0a9 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -66,7 +66,7 @@
                     Geometry{
                             .boundaries = rect,
                             .roundedCornersCrop = rect,
-                            .roundedCornersRadius = 50.f,
+                            .roundedCornersRadius = {50.f, 50.f},
                     },
             // drawShadow ignores alpha
             .shadow =
@@ -87,7 +87,7 @@
                     Geometry{
                             .boundaries = smallerRect,
                             .roundedCornersCrop = rect,
-                            .roundedCornersRadius = 50.f,
+                            .roundedCornersRadius = {50.f, 50.f},
                     },
             .source =
                     PixelSource{
@@ -148,7 +148,7 @@
         // In reduced shader mode, all non-zero round rect radii get the same code path.
         for (float roundedCornersRadius : {0.0f, 50.0f}) {
             // roundedCornersCrop is always set, but the radius triggers the behavior
-            layer.geometry.roundedCornersRadius = roundedCornersRadius;
+            layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius};
             for (bool isOpaque : {true, false}) {
                 layer.source.buffer.isOpaque = isOpaque;
                 for (auto alpha : {half(.2f), half(1.0f)}) {
@@ -181,7 +181,7 @@
     for (auto transform : {mat4(), kScaleAndTranslate}) {
         layer.geometry.positionTransform = transform;
         for (float roundedCornersRadius : {0.0f, 50.f}) {
-            layer.geometry.roundedCornersRadius = roundedCornersRadius;
+            layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius};
             auto layers = std::vector<LayerSettings>{layer};
             renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
                                      base::unique_fd());
@@ -238,7 +238,7 @@
             .geometry =
                     Geometry{
                             .boundaries = rect,
-                            .roundedCornersRadius = 27, // larger than the 20 above.
+                            .roundedCornersRadius = {27.f, 27.f},
                             .roundedCornersCrop =
                                     FloatRect(0, 0, displayRect.width(), displayRect.height()),
                     },
@@ -275,9 +275,9 @@
                             // larger than the layer bounds.
                             .positionTransform = kFlip,
                             .boundaries = rect,
-                            .roundedCornersRadius = 94.2551,
-                            .roundedCornersCrop = FloatRect(
-                                -93.75, 0, displayRect.width() + 93.75, displayRect.height()),
+                            .roundedCornersRadius = {94.2551f, 94.2551f},
+                            .roundedCornersCrop = FloatRect(-93.75, 0, displayRect.width() + 93.75,
+                                                            displayRect.height()),
                     },
             .source = PixelSource{.buffer =
                                           Buffer{
@@ -307,10 +307,11 @@
                             // the boundaries have to be smaller than the rounded crop so that
                             // clipRRect is used instead of drawRRect
                             .boundaries = small,
-                            .roundedCornersRadius = 50.f,
+                            .roundedCornersRadius = {50.f, 50.f},
                             .roundedCornersCrop = rect,
                     },
-            .source = PixelSource{
+            .source =
+                    PixelSource{
                             .solidColor = half3(0.f, 0.f, 0.f),
                     },
             .sourceDataspace = kDestDataSpace,
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 97271cb..0caa9f2 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -1276,7 +1276,7 @@
  *  produce the insected roundRect. If false, the returned state of the radii param is undefined.
  */
 static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop,
-                                    const SkRect& insetCrop, float cornerRadius,
+                                    const SkRect& insetCrop, const vec2& cornerRadius,
                                     SkVector radii[4]) {
     const bool leftEqual = bounds.fLeft == crop.fLeft;
     const bool topEqual = bounds.fTop == crop.fTop;
@@ -1288,8 +1288,8 @@
     // In particular the round rect implementation will scale the value of all corner radii
     // if the sum of the radius along any edge is greater than the length of that edge.
     // See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
-    const bool requiredWidth = bounds.width() > (cornerRadius * 2);
-    const bool requiredHeight = bounds.height() > (cornerRadius * 2);
+    const bool requiredWidth = bounds.width() > (cornerRadius.x * 2);
+    const bool requiredHeight = bounds.height() > (cornerRadius.y * 2);
     if (!requiredWidth || !requiredHeight) {
         return false;
     }
@@ -1298,7 +1298,7 @@
     // contained within the cropped shape and does not need rounded.
     // compute the UpperLeft corner radius
     if (leftEqual && topEqual) {
-        radii[0].set(cornerRadius, cornerRadius);
+        radii[0].set(cornerRadius.x, cornerRadius.y);
     } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) ||
                (topEqual && bounds.fLeft >= insetCrop.fLeft)) {
         radii[0].set(0, 0);
@@ -1307,7 +1307,7 @@
     }
     // compute the UpperRight corner radius
     if (rightEqual && topEqual) {
-        radii[1].set(cornerRadius, cornerRadius);
+        radii[1].set(cornerRadius.x, cornerRadius.y);
     } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) ||
                (topEqual && bounds.fRight <= insetCrop.fRight)) {
         radii[1].set(0, 0);
@@ -1316,7 +1316,7 @@
     }
     // compute the BottomRight corner radius
     if (rightEqual && bottomEqual) {
-        radii[2].set(cornerRadius, cornerRadius);
+        radii[2].set(cornerRadius.x, cornerRadius.y);
     } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) ||
                (bottomEqual && bounds.fRight <= insetCrop.fRight)) {
         radii[2].set(0, 0);
@@ -1325,7 +1325,7 @@
     }
     // compute the BottomLeft corner radius
     if (leftEqual && bottomEqual) {
-        radii[3].set(cornerRadius, cornerRadius);
+        radii[3].set(cornerRadius.x, cornerRadius.y);
     } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) ||
                (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) {
         radii[3].set(0, 0);
@@ -1338,22 +1338,22 @@
 
 inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect,
                                                                         const FloatRect& cropRect,
-                                                                        const float cornerRadius) {
+                                                                        const vec2& cornerRadius) {
     const SkRect bounds = getSkRect(boundsRect);
     const SkRect crop = getSkRect(cropRect);
 
     SkRRect clip;
-    if (cornerRadius > 0) {
+    if (cornerRadius.x > 0 && cornerRadius.y > 0) {
         // it the crop and the bounds are equivalent or there is no crop then we don't need a clip
         if (bounds == crop || crop.isEmpty()) {
-            return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), clip};
+            return {SkRRect::MakeRectXY(bounds, cornerRadius.x, cornerRadius.y), clip};
         }
 
         // This makes an effort to speed up common, simple bounds + clip combinations by
         // converting them to a single RRect draw. It is possible there are other cases
         // that can be converted.
         if (crop.contains(bounds)) {
-            const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius);
+            const auto insetCrop = crop.makeInset(cornerRadius.x, cornerRadius.y);
             if (insetCrop.contains(bounds)) {
                 return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required
             }
@@ -1367,7 +1367,7 @@
         }
 
         // we didn't hit any of our fast paths so set the clip to the cropRect
-        clip.setRectXY(crop, cornerRadius, cornerRadius);
+        clip.setRectXY(crop, cornerRadius.x, cornerRadius.y);
     }
 
     // if we hit this point then we either don't have rounded corners or we are going to rely
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index 5ef9944..68c3363 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -90,7 +90,8 @@
     inline SkRect getSkRect(const FloatRect& layer);
     inline SkRect getSkRect(const Rect& layer);
     inline std::pair<SkRRect, SkRRect> getBoundsAndClip(const FloatRect& bounds,
-                                                        const FloatRect& crop, float cornerRadius);
+                                                        const FloatRect& crop,
+                                                        const vec2& cornerRadius);
     inline bool layerHasBlur(const LayerSettings& layer, bool colorTransformModifiesAlpha);
     inline SkColor getSkColor(const vec4& color);
     inline SkM44 getSkM44(const mat4& matrix);
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 7c70a74..8889f76 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -444,7 +444,9 @@
                            const ubyte4& backgroundColor) {
         const Rect casterRect(castingLayer.geometry.boundaries);
         Region casterRegion = Region(casterRect);
-        const float casterCornerRadius = castingLayer.geometry.roundedCornersRadius;
+        const float casterCornerRadius = (castingLayer.geometry.roundedCornersRadius.x +
+                                          castingLayer.geometry.roundedCornersRadius.y) /
+                2.0;
         if (casterCornerRadius > 0.0f) {
             // ignore the corners if a corner radius is set
             Rect cornerRect(casterCornerRadius, casterCornerRadius);
@@ -1129,7 +1131,7 @@
     renderengine::LayerSettings layer;
     layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     layer.geometry.boundaries = fullscreenRect().toFloatRect();
-    layer.geometry.roundedCornersRadius = 5.0f;
+    layer.geometry.roundedCornersRadius = {5.0f, 5.0f};
     layer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect();
     SourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
     layer.alpha = 1.0f;
@@ -2131,7 +2133,7 @@
     casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
     renderengine::LayerSettings castingLayer;
     castingLayer.geometry.boundaries = casterBounds.toFloatRect();
-    castingLayer.geometry.roundedCornersRadius = 3.0f;
+    castingLayer.geometry.roundedCornersRadius = {3.0f, 3.0f};
     castingLayer.geometry.roundedCornersCrop = casterBounds.toFloatRect();
     castingLayer.alpha = 1.0f;
     renderengine::ShadowSettings settings =
@@ -2219,7 +2221,8 @@
     renderengine::LayerSettings redLayer;
     redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     redLayer.geometry.boundaries = fullscreenRect().toFloatRect();
-    redLayer.geometry.roundedCornersRadius = 5.0f;
+    redLayer.geometry.roundedCornersRadius = {5.0f, 5.0f};
+
     redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect();
     // Red background.
     redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
@@ -2231,7 +2234,7 @@
     renderengine::LayerSettings greenLayer;
     greenLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     greenLayer.geometry.boundaries = fullscreenRect().toFloatRect();
-    greenLayer.geometry.roundedCornersRadius = 5.0f;
+    greenLayer.geometry.roundedCornersRadius = {5.0f, 5.0f};
     // Bottom right corner is not going to be rounded.
     greenLayer.geometry.roundedCornersCrop =
             Rect(DEFAULT_DISPLAY_WIDTH / 3, DEFAULT_DISPLAY_HEIGHT / 3, DEFAULT_DISPLAY_HEIGHT,
@@ -2268,7 +2271,7 @@
     renderengine::LayerSettings redLayer;
     redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     redLayer.geometry.boundaries = fullscreenRect().toFloatRect();
-    redLayer.geometry.roundedCornersRadius = 5.0f;
+    redLayer.geometry.roundedCornersRadius = {5.0f, 5.0f};
     redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect();
     // Red background.
     redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
@@ -2313,7 +2316,7 @@
     renderengine::LayerSettings redLayer;
     redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     redLayer.geometry.boundaries = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 32);
-    redLayer.geometry.roundedCornersRadius = 64;
+    redLayer.geometry.roundedCornersRadius = {64.0f, 64.0f};
     redLayer.geometry.roundedCornersCrop = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 128);
     // Red background.
     redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
@@ -2334,6 +2337,49 @@
     expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, 31), 255, 0, 0, 255);
 }
 
+TEST_P(RenderEngineTest, testRoundedCornersXY) {
+    if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) {
+        GTEST_SKIP();
+    }
+
+    initializeRenderEngine();
+
+    renderengine::DisplaySettings settings;
+    settings.physicalDisplay = fullscreenRect();
+    settings.clip = fullscreenRect();
+    settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+
+    std::vector<renderengine::LayerSettings> layers;
+
+    renderengine::LayerSettings redLayer;
+    redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+    redLayer.geometry.boundaries = fullscreenRect().toFloatRect();
+    redLayer.geometry.roundedCornersRadius = {5.0f, 20.0f};
+    redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect();
+    // Red background.
+    redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
+    redLayer.alpha = 1.0f;
+
+    layers.push_back(redLayer);
+
+    invokeDraw(settings, layers);
+
+    // Due to roundedCornersRadius, the corners are untouched.
+    expectBufferColor(Point(0, 0), 0, 0, 0, 0);
+    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 0), 0, 0, 0, 0);
+    expectBufferColor(Point(0, DEFAULT_DISPLAY_HEIGHT - 1), 0, 0, 0, 0);
+    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, DEFAULT_DISPLAY_HEIGHT - 1), 0, 0, 0, 0);
+
+    // Y-axis draws a larger radius, check that its untouched as well
+    expectBufferColor(Point(0, DEFAULT_DISPLAY_HEIGHT - 5), 0, 0, 0, 0);
+    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, DEFAULT_DISPLAY_HEIGHT - 5), 0, 0, 0, 0);
+    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 5), 0, 0, 0, 0);
+    expectBufferColor(Point(0, 5), 0, 0, 0, 0);
+
+    //  middle should be red
+    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, DEFAULT_DISPLAY_HEIGHT / 2), 255, 0, 0, 255);
+}
+
 TEST_P(RenderEngineTest, testClear) {
     initializeRenderEngine();
 
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
index 2a3924b..dce327e 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -196,9 +196,20 @@
         }
         coords += StringPrintf("{%" PRIu32 ": ", i);
         coords +=
-                StringPrintf("id=%" PRIu32 " x=%.1f y=%.1f, pressure=%.1f", pointerProperties[i].id,
+                StringPrintf("id=%" PRIu32 " x=%.1f y=%.1f pressure=%.1f", pointerProperties[i].id,
                              pointerCoords[i].getX(), pointerCoords[i].getY(),
                              pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+        const int32_t toolType = pointerProperties[i].toolType;
+        if (toolType != AMOTION_EVENT_TOOL_TYPE_FINGER) {
+            coords += StringPrintf(" toolType=%s", motionToolTypeToString(toolType));
+        }
+        const float major = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
+        const float minor = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
+        const float orientation = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+        if (major != 0 || minor != 0) {
+            coords += StringPrintf(" major=%.1f minor=%.1f orientation=%.1f", major, minor,
+                                   orientation);
+        }
         coords += "}";
     }
     return StringPrintf("NotifyMotionArgs(id=%" PRId32 ", eventTime=%" PRId64 ", deviceId=%" PRId32
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
index f57ff33..ec41025 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.cpp
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -29,8 +29,40 @@
 
 using android::base::StringPrintf;
 
+/**
+ * This type is declared here to ensure consistency between the instantiated type (used in the
+ * constructor via std::make_unique) and the cast-to type (used in PalmRejector::dump() with
+ * static_cast). Due to the lack of rtti support, dynamic_cast is not available, so this can't be
+ * checked at runtime to avoid undefined behaviour.
+ */
+using PalmFilterImplementation = ::ui::NeuralStylusPalmDetectionFilter;
+
 namespace android {
 
+/**
+ * Log detailed debug messages about each inbound motion event notification to the blocker.
+ * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerInboundMotion DEBUG"
+ * (requires restart)
+ */
+const bool DEBUG_INBOUND_MOTION =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundMotion", ANDROID_LOG_INFO);
+
+/**
+ * Log detailed debug messages about each outbound motion event processed by the blocker.
+ * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerOutboundMotion DEBUG"
+ * (requires restart)
+ */
+const bool DEBUG_OUTBOUND_MOTION =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "OutboundMotion", ANDROID_LOG_INFO);
+
+/**
+ * Log the data sent to the model and received back from the model.
+ * Enable this via "adb shell setprop log.tag.UnwantedInteractionBlockerModel DEBUG"
+ * (requires restart)
+ */
+const bool DEBUG_MODEL =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Model", ANDROID_LOG_INFO);
+
 // Category (=namespace) name for the input settings that are applied at boot time
 static const char* INPUT_NATIVE_BOOT = "input_native_boot";
 /**
@@ -45,13 +77,11 @@
 }
 
 static bool isFromTouchscreen(int32_t source) {
-    return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN) &&
-            !isFromSource(source, AINPUT_SOURCE_STYLUS);
+    return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN);
 }
 
 static ::base::TimeTicks toChromeTimestamp(nsecs_t eventTime) {
-    return ::base::TimeTicks::UnixEpoch() +
-            ::base::Milliseconds(static_cast<float>(ns2ms(eventTime)));
+    return ::base::TimeTicks::UnixEpoch() + ::base::TimeDelta::FromNanosecondsD(eventTime);
 }
 
 /**
@@ -61,39 +91,22 @@
 static bool isPalmRejectionEnabled() {
     std::string value = toLower(
             server_configurable_flags::GetServerConfigurableFlag(INPUT_NATIVE_BOOT,
-                                                                 PALM_REJECTION_ENABLED, "false"));
-    if (value == "true" || value == "1") {
+                                                                 PALM_REJECTION_ENABLED, "0"));
+    if (value == "1") {
         return true;
     }
     return false;
 }
 
-static int getLinuxToolType(int32_t toolType) {
-    switch (toolType) {
-        case AMOTION_EVENT_TOOL_TYPE_FINGER:
-            return MT_TOOL_FINGER;
-        case AMOTION_EVENT_TOOL_TYPE_STYLUS:
-            return MT_TOOL_PEN;
-        case AMOTION_EVENT_TOOL_TYPE_PALM:
-            return MT_TOOL_PALM;
+static int getLinuxToolCode(int toolType) {
+    if (toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS) {
+        return BTN_TOOL_PEN;
     }
-    ALOGW("Got tool type %" PRId32 ", converting to MT_TOOL_FINGER", toolType);
-    return MT_TOOL_FINGER;
-}
-
-static std::string dumpDeviceInfo(const AndroidPalmFilterDeviceInfo& info) {
-    std::string out;
-    out += StringPrintf("max_x = %.2f\n", info.max_x);
-    out += StringPrintf("max_y = %.2f\n", info.max_y);
-    out += StringPrintf("x_res = %.2f\n", info.x_res);
-    out += StringPrintf("y_res = %.2f\n", info.y_res);
-    out += StringPrintf("major_radius_res = %.2f\n", info.major_radius_res);
-    out += StringPrintf("minor_radius_res = %.2f\n", info.minor_radius_res);
-    out += StringPrintf("minor_radius_supported = %s\n",
-                        info.minor_radius_supported ? "true" : "false");
-    out += StringPrintf("touch_major_res = %" PRId32 "\n", info.touch_major_res);
-    out += StringPrintf("touch_minor_res = %" PRId32 "\n", info.touch_minor_res);
-    return out;
+    if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
+        return BTN_TOOL_FINGER;
+    }
+    ALOGW("Got tool type %" PRId32 ", converting to BTN_TOOL_FINGER", toolType);
+    return BTN_TOOL_FINGER;
 }
 
 static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
@@ -128,32 +141,6 @@
     return AMOTION_EVENT_ACTION_MOVE;
 }
 
-std::string toString(const ::ui::InProgressTouchEvdev& touch) {
-    return StringPrintf("x=%.1f, y=%.1f, tracking_id=%i, slot=%zu,"
-                        " pressure=%.1f, major=%i, minor=%i, "
-                        "tool_type=%i, altered=%s, was_touching=%s, touching=%s",
-                        touch.x, touch.y, touch.tracking_id, touch.slot, touch.pressure,
-                        touch.major, touch.minor, touch.tool_type, toString(touch.altered),
-                        toString(touch.was_touching), toString(touch.touching));
-}
-
-/**
- * Remove the data for the provided pointers from the args. The pointers are identified by their
- * pointerId, not by the index inside the array.
- * Return the new NotifyMotionArgs struct that has the remaining pointers.
- * The only fields that may be different in the returned args from the provided args are:
- *     - action
- *     - pointerCount
- *     - pointerProperties
- *     - pointerCoords
- * Action might change because it contains a pointer index. If another pointer is removed, the
- * active pointer index would be shifted.
- * Do not call this function for events with POINTER_UP or POINTER_DOWN events when removed pointer
- * id is the acting pointer id.
- *
- * @param args the args from which the pointers should be removed
- * @param pointerIds the pointer ids of the pointers that should be removed
- */
 NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
                                   const std::set<int32_t>& pointerIds) {
     const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
@@ -199,6 +186,26 @@
     return newArgs;
 }
 
+/**
+ * Remove stylus pointers from the provided NotifyMotionArgs.
+ *
+ * Return NotifyMotionArgs where the stylus pointers have been removed.
+ * If this results in removal of the active pointer, then return nullopt.
+ */
+static std::optional<NotifyMotionArgs> removeStylusPointerIds(const NotifyMotionArgs& args) {
+    std::set<int32_t> stylusPointerIds;
+    for (uint32_t i = 0; i < args.pointerCount; i++) {
+        if (args.pointerProperties[i].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS) {
+            stylusPointerIds.insert(args.pointerProperties[i].id);
+        }
+    }
+    NotifyMotionArgs withoutStylusPointers = removePointerIds(args, stylusPointerIds);
+    if (withoutStylusPointers.pointerCount == 0 || withoutStylusPointers.action == ACTION_UNKNOWN) {
+        return std::nullopt;
+    }
+    return withoutStylusPointers;
+}
+
 std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
         const InputDeviceInfo& deviceInfo) {
     if (!isFromTouchscreen(deviceInfo.getSources())) {
@@ -326,6 +333,7 @@
 }
 
 void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) {
+    ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args->dump().c_str());
     { // acquire lock
         std::scoped_lock lock(mLock);
         const std::vector<NotifyMotionArgs> processedArgs =
@@ -339,17 +347,22 @@
     mQueuedListener.flush();
 }
 
+void UnwantedInteractionBlocker::enqueueOutboundMotionLocked(const NotifyMotionArgs& args) {
+    ALOGD_IF(DEBUG_OUTBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
+    mQueuedListener.notifyMotion(&args);
+}
+
 void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs* args) {
     auto it = mPalmRejectors.find(args->deviceId);
     const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source);
     if (!sendToPalmRejector) {
-        mQueuedListener.notifyMotion(args);
+        enqueueOutboundMotionLocked(*args);
         return;
     }
 
     std::vector<NotifyMotionArgs> processedArgs = it->second.processMotion(*args);
     for (const NotifyMotionArgs& loopArgs : processedArgs) {
-        mQueuedListener.notifyMotion(&loopArgs);
+        enqueueOutboundMotionLocked(loopArgs);
     }
 }
 
@@ -428,9 +441,10 @@
     dump += "UnwantedInteractionBlocker:\n";
     dump += "  mPreferStylusOverTouchBlocker:\n";
     dump += addLinePrefix(mPreferStylusOverTouchBlocker.dump(), "    ");
-    dump += StringPrintf("  mEnablePalmRejection: %s\n", toString(mEnablePalmRejection));
+    dump += StringPrintf("  mEnablePalmRejection: %s\n",
+                         std::to_string(mEnablePalmRejection).c_str());
     dump += StringPrintf("  isPalmRejectionEnabled (flag value): %s\n",
-                         toString(isPalmRejectionEnabled()));
+                         std::to_string(isPalmRejectionEnabled()).c_str());
     dump += mPalmRejectors.empty() ? "  mPalmRejectors: None\n" : "  mPalmRejectors:\n";
     for (const auto& [deviceId, palmRejector] : mPalmRejectors) {
         dump += StringPrintf("    deviceId = %" PRId32 ":\n", deviceId);
@@ -512,6 +526,15 @@
     return out;
 }
 
+class AndroidPalmRejectionModel : public ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel {
+public:
+    AndroidPalmRejectionModel()
+          : ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel(/*default version*/ "",
+                                                                     std::vector<float>()) {
+        config_.resample_period = ::ui::kResamplePeriod;
+    }
+};
+
 PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info,
                            std::unique_ptr<::ui::PalmDetectionFilter> filter)
       : mSharedPalmState(std::make_unique<::ui::SharedPalmDetectionFilterState>()),
@@ -523,11 +546,9 @@
         return;
     }
     std::unique_ptr<::ui::NeuralStylusPalmDetectionFilterModel> model =
-            std::make_unique<::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel>(
-                    std::vector<float>());
-    mPalmDetectionFilter =
-            std::make_unique<::ui::NeuralStylusPalmDetectionFilter>(mDeviceInfo, std::move(model),
-                                                                    mSharedPalmState.get());
+            std::make_unique<AndroidPalmRejectionModel>();
+    mPalmDetectionFilter = std::make_unique<PalmFilterImplementation>(mDeviceInfo, std::move(model),
+                                                                      mSharedPalmState.get());
 }
 
 std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
@@ -541,7 +562,7 @@
         touches.emplace_back(::ui::InProgressTouchEvdev());
         touches.back().major = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
         touches.back().minor = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
-        touches.back().tool_type = getLinuxToolType(args.pointerProperties[i].toolType);
+        // The field 'tool_type' is not used for palm rejection
 
         // Whether there is new information for the touch.
         touches.back().altered = true;
@@ -588,15 +609,57 @@
 
         // The fields 'radius_x' and 'radius_x' are not used for palm rejection
         touches.back().pressure = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
-        touches.back().tool_code = BTN_TOOL_FINGER;
+        touches.back().tool_code = getLinuxToolCode(args.pointerProperties[i].toolType);
         // The field 'orientation' is not used for palm rejection
         // The fields 'tilt_x' and 'tilt_y' are not used for palm rejection
-        touches.back().reported_tool_type = ::ui::EventPointerType::kTouch;
+        // The field 'reported_tool_type' is not used for palm rejection
         touches.back().stylus_button = false;
     }
     return touches;
 }
 
+std::set<int32_t> PalmRejector::detectPalmPointers(const NotifyMotionArgs& args) {
+    std::bitset<::ui::kNumTouchEvdevSlots> slotsToHold;
+    std::bitset<::ui::kNumTouchEvdevSlots> slotsToSuppress;
+
+    // Store the slot state before we call getTouches and update it. This way, we can find
+    // the slots that have been removed due to the incoming event.
+    SlotState oldSlotState = mSlotState;
+    mSlotState.update(args);
+
+    std::vector<::ui::InProgressTouchEvdev> touches =
+            getTouches(args, mDeviceInfo, oldSlotState, mSlotState);
+    ::base::TimeTicks chromeTimestamp = toChromeTimestamp(args.eventTime);
+
+    if (DEBUG_MODEL) {
+        std::stringstream touchesStream;
+        for (const ::ui::InProgressTouchEvdev& touch : touches) {
+            touchesStream << touch.tracking_id << " : " << touch << "\n";
+        }
+        ALOGD("Filter: touches = %s", touchesStream.str().c_str());
+    }
+
+    mPalmDetectionFilter->Filter(touches, chromeTimestamp, &slotsToHold, &slotsToSuppress);
+
+    ALOGD_IF(DEBUG_MODEL, "Response: slotsToHold = %s, slotsToSuppress = %s",
+             slotsToHold.to_string().c_str(), slotsToSuppress.to_string().c_str());
+
+    // Now that we know which slots should be suppressed, let's convert those to pointer id's.
+    std::set<int32_t> newSuppressedIds;
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        const int32_t pointerId = args.pointerProperties[i].id;
+        std::optional<size_t> slot = oldSlotState.getSlotForPointerId(pointerId);
+        if (!slot) {
+            slot = mSlotState.getSlotForPointerId(pointerId);
+            LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer id %" PRId32, pointerId);
+        }
+        if (slotsToSuppress.test(*slot)) {
+            newSuppressedIds.insert(pointerId);
+        }
+    }
+    return newSuppressedIds;
+}
+
 std::vector<NotifyMotionArgs> PalmRejector::processMotion(const NotifyMotionArgs& args) {
     if (mPalmDetectionFilter == nullptr) {
         return {args};
@@ -614,32 +677,17 @@
     if (args.action == AMOTION_EVENT_ACTION_DOWN) {
         mSuppressedPointerIds.clear();
     }
-    std::bitset<::ui::kNumTouchEvdevSlots> slotsToHold;
-    std::bitset<::ui::kNumTouchEvdevSlots> slotsToSuppress;
 
-    // Store the slot state before we call getTouches and update it. This way, we can find
-    // the slots that have been removed due to the incoming event.
-    SlotState oldSlotState = mSlotState;
-    mSlotState.update(args);
-    std::vector<::ui::InProgressTouchEvdev> touches =
-            getTouches(args, mDeviceInfo, oldSlotState, mSlotState);
-    ::base::TimeTicks chromeTimestamp = toChromeTimestamp(args.eventTime);
-
-    mPalmDetectionFilter->Filter(touches, chromeTimestamp, &slotsToHold, &slotsToSuppress);
-
-    // Now that we know which slots should be suppressed, let's convert those to pointer id's.
     std::set<int32_t> oldSuppressedIds;
     std::swap(oldSuppressedIds, mSuppressedPointerIds);
-    for (size_t i = 0; i < args.pointerCount; i++) {
-        const int32_t pointerId = args.pointerProperties[i].id;
-        std::optional<size_t> slot = oldSlotState.getSlotForPointerId(pointerId);
-        if (!slot) {
-            slot = mSlotState.getSlotForPointerId(pointerId);
-            LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer id %" PRId32, pointerId);
-        }
-        if (slotsToSuppress.test(*slot)) {
-            mSuppressedPointerIds.insert(pointerId);
-        }
+
+    std::optional<NotifyMotionArgs> touchOnlyArgs = removeStylusPointerIds(args);
+    if (touchOnlyArgs) {
+        mSuppressedPointerIds = detectPalmPointers(*touchOnlyArgs);
+    } else {
+        // This is a stylus-only event.
+        // We can skip this event and just keep the suppressed pointer ids the same as before.
+        mSuppressedPointerIds = oldSuppressedIds;
     }
 
     std::vector<NotifyMotionArgs> argsWithoutUnwantedPointers =
@@ -648,29 +696,40 @@
         LOG_ALWAYS_FATAL_IF(checkArgs.action == ACTION_UNKNOWN, "%s", checkArgs.dump().c_str());
     }
 
-    if (mSuppressedPointerIds != oldSuppressedIds) {
-        if (argsWithoutUnwantedPointers.size() != 1 ||
-            argsWithoutUnwantedPointers[0].pointerCount != args.pointerCount) {
-            ALOGI("Palm detected, removing pointer ids %s from %s",
-                  dumpSet(mSuppressedPointerIds).c_str(), args.dump().c_str());
-        }
+    // Only log if new pointers are getting rejected. That means mSuppressedPointerIds is not a
+    // subset of oldSuppressedIds.
+    if (!std::includes(oldSuppressedIds.begin(), oldSuppressedIds.end(),
+                       mSuppressedPointerIds.begin(), mSuppressedPointerIds.end())) {
+        ALOGI("Palm detected, removing pointer ids %s after %" PRId64 "ms from %s",
+              dumpSet(mSuppressedPointerIds).c_str(), ns2ms(args.eventTime - args.downTime),
+              args.dump().c_str());
     }
 
     return argsWithoutUnwantedPointers;
 }
 
-const AndroidPalmFilterDeviceInfo& PalmRejector::getPalmFilterDeviceInfo() {
+const AndroidPalmFilterDeviceInfo& PalmRejector::getPalmFilterDeviceInfo() const {
     return mDeviceInfo;
 }
 
 std::string PalmRejector::dump() const {
     std::string out;
     out += "mDeviceInfo:\n";
-    out += addLinePrefix(dumpDeviceInfo(mDeviceInfo), "  ");
+    std::stringstream deviceInfo;
+    deviceInfo << mDeviceInfo << ", touch_major_res=" << mDeviceInfo.touch_major_res
+               << ", touch_minor_res=" << mDeviceInfo.touch_minor_res << "\n";
+    out += addLinePrefix(deviceInfo.str(), "  ");
     out += "mSlotState:\n";
     out += addLinePrefix(mSlotState.dump(), "  ");
     out += "mSuppressedPointerIds: ";
     out += dumpSet(mSuppressedPointerIds) + "\n";
+    std::stringstream state;
+    state << *mSharedPalmState;
+    out += "mSharedPalmState: " + state.str() + "\n";
+    std::stringstream filter;
+    filter << static_cast<const PalmFilterImplementation&>(*mPalmDetectionFilter);
+    out += "mPalmDetectionFilter:\n";
+    out += addLinePrefix(filter.str(), "  ") + "\n";
     return out;
 }
 
diff --git a/services/inputflinger/UnwantedInteractionBlocker.h b/services/inputflinger/UnwantedInteractionBlocker.h
index a433764..5d0dde8 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.h
+++ b/services/inputflinger/UnwantedInteractionBlocker.h
@@ -43,6 +43,25 @@
 
 static constexpr int32_t ACTION_UNKNOWN = -1;
 
+/**
+ * Remove the data for the provided pointers from the args. The pointers are identified by their
+ * pointerId, not by the index inside the array.
+ * Return the new NotifyMotionArgs struct that has the remaining pointers.
+ * The only fields that may be different in the returned args from the provided args are:
+ *     - action
+ *     - pointerCount
+ *     - pointerProperties
+ *     - pointerCoords
+ * Action might change because it contains a pointer index. If another pointer is removed, the
+ * active pointer index would be shifted.
+ *
+ * If the active pointer id is removed (for example, for events like
+ * POINTER_UP or POINTER_DOWN), then the action is set to ACTION_UNKNOWN. It is up to the caller
+ * to set the action appropriately after the call.
+ *
+ * @param args the args from which the pointers should be removed
+ * @param pointerIds the pointer ids of the pointers that should be removed
+ */
 NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
                                   const std::set<int32_t>& pointerIds);
 
@@ -101,6 +120,9 @@
     std::map<int32_t /*deviceId*/, PalmRejector> mPalmRejectors GUARDED_BY(mLock);
     // TODO(b/210159205): delete this when simultaneous stylus and touch is supported
     void notifyMotionLocked(const NotifyMotionArgs* args) REQUIRES(mLock);
+
+    // Call this function for outbound events so that they can be logged when logging is enabled.
+    void enqueueOutboundMotionLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
 };
 
 class SlotState {
@@ -144,13 +166,21 @@
     std::vector<NotifyMotionArgs> processMotion(const NotifyMotionArgs& args);
 
     // Get the device info of this device, for comparison purposes
-    const AndroidPalmFilterDeviceInfo& getPalmFilterDeviceInfo();
+    const AndroidPalmFilterDeviceInfo& getPalmFilterDeviceInfo() const;
     std::string dump() const;
 
 private:
     PalmRejector(const PalmRejector&) = delete;
     PalmRejector& operator=(const PalmRejector&) = delete;
 
+    /**
+     * Update the slot state and send this event to the palm rejection model for palm detection.
+     * Return the pointer ids that should be suppressed.
+     *
+     * This function is not const because it has side-effects. It will update the slot state using
+     * the incoming args! Also, it will call Filter(..), which has side-effects.
+     */
+    std::set<int32_t> detectPalmPointers(const NotifyMotionArgs& args);
     std::unique_ptr<::ui::SharedPalmDetectionFilterState> mSharedPalmState;
     AndroidPalmFilterDeviceInfo mDeviceInfo;
     std::unique_ptr<::ui::PalmDetectionFilter> mPalmDetectionFilter;
diff --git a/services/inputflinger/dispatcher/DebugConfig.h b/services/inputflinger/dispatcher/DebugConfig.h
index 4f8995f..9a2aea6 100644
--- a/services/inputflinger/dispatcher/DebugConfig.h
+++ b/services/inputflinger/dispatcher/DebugConfig.h
@@ -75,11 +75,8 @@
 
 /**
  * Log debug messages about touch occlusion
- * Enable this via "adb shell setprop log.tag.InputDispatcherTouchOcclusion DEBUG" (requires
- * restart)
  */
-const bool DEBUG_TOUCH_OCCLUSION =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "TouchOcclusion", ANDROID_LOG_INFO);
+constexpr bool DEBUG_TOUCH_OCCLUSION = true;
 
 /**
  * Log debug messages about the app switch latency optimization.
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 5e9427a..4ad9f42 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2195,12 +2195,32 @@
 
             // Update the temporary touch state.
             BitSet32 pointerIds;
-            if (isSplit) {
-                uint32_t pointerId = entry.pointerProperties[pointerIndex].id;
-                pointerIds.markBit(pointerId);
-            }
-
+            pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
             tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds);
+
+            // If this is the pointer going down and the touched window has a wallpaper
+            // then also add the touched wallpaper windows so they are locked in for the duration
+            // of the touch gesture.
+            // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
+            // engine only supports touch events.  We would need to add a mechanism similar
+            // to View.onGenericMotionEvent to enable wallpapers to handle these events.
+            if (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
+                maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+                if ((targetFlags & InputTarget::FLAG_FOREGROUND) &&
+                    windowHandle->getInfo()->inputConfig.test(
+                            gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
+                    sp<WindowInfoHandle> wallpaper = findWallpaperWindowBelow(windowHandle);
+                    if (wallpaper != nullptr) {
+                        int32_t wallpaperFlags = InputTarget::FLAG_WINDOW_IS_OBSCURED |
+                                InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED |
+                                InputTarget::FLAG_DISPATCH_AS_IS;
+                        if (isSplit) {
+                            wallpaperFlags |= InputTarget::FLAG_SPLIT;
+                        }
+                        tempTouchState.addOrUpdateWindow(wallpaper, wallpaperFlags, pointerIds);
+                    }
+                }
+            }
         }
     } else {
         /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
@@ -2275,10 +2295,26 @@
                 }
 
                 BitSet32 pointerIds;
-                if (isSplit) {
-                    pointerIds.markBit(entry.pointerProperties[0].id);
-                }
+                pointerIds.markBit(entry.pointerProperties[0].id);
                 tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
+
+                // Check if the wallpaper window should deliver the corresponding event.
+                slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle,
+                                   tempTouchState, pointerIds);
+            }
+        }
+
+        // Update the pointerIds for non-splittable when it received pointer down.
+        if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+            // If no split, we suppose all touched windows should receive pointer down.
+            const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+            for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
+                TouchedWindow& touchedWindow = tempTouchState.windows[i];
+                // Ignore drag window for it should just track one pointer.
+                if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
+                    continue;
+                }
+                touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
             }
         }
     }
@@ -2370,37 +2406,6 @@
         }
     }
 
-    // If this is the first pointer going down and the touched window has a wallpaper
-    // then also add the touched wallpaper windows so they are locked in for the duration
-    // of the touch gesture.
-    // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
-    // engine only supports touch events.  We would need to add a mechanism similar
-    // to View.onGenericMotionEvent to enable wallpapers to handle these events.
-    if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
-        sp<WindowInfoHandle> foregroundWindowHandle =
-                tempTouchState.getFirstForegroundWindowHandle();
-        if (foregroundWindowHandle &&
-            foregroundWindowHandle->getInfo()->inputConfig.test(
-                    WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
-            const std::vector<sp<WindowInfoHandle>>& windowHandles =
-                    getWindowHandlesLocked(displayId);
-            for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
-                const WindowInfo* info = windowHandle->getInfo();
-                if (info->displayId == displayId &&
-                    windowHandle->getInfo()->inputConfig.test(
-                            WindowInfo::InputConfig::IS_WALLPAPER)) {
-                    tempTouchState
-                            .addOrUpdateWindow(windowHandle,
-                                               InputTarget::FLAG_WINDOW_IS_OBSCURED |
-                                                       InputTarget::
-                                                               FLAG_WINDOW_IS_PARTIALLY_OBSCURED |
-                                                       InputTarget::FLAG_DISPATCH_AS_IS,
-                                               BitSet32(0));
-                }
-            }
-        }
-    }
-
     // Success!  Output targets.
     injectionResult = InputEventInjectionResult::SUCCEEDED;
 
@@ -2453,21 +2458,17 @@
             }
         } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
             // One pointer went up.
-            if (isSplit) {
-                int32_t pointerIndex = getMotionEventActionPointerIndex(action);
-                uint32_t pointerId = entry.pointerProperties[pointerIndex].id;
+            int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+            uint32_t pointerId = entry.pointerProperties[pointerIndex].id;
 
-                for (size_t i = 0; i < tempTouchState.windows.size();) {
-                    TouchedWindow& touchedWindow = tempTouchState.windows[i];
-                    if (touchedWindow.targetFlags & InputTarget::FLAG_SPLIT) {
-                        touchedWindow.pointerIds.clearBit(pointerId);
-                        if (touchedWindow.pointerIds.isEmpty()) {
-                            tempTouchState.windows.erase(tempTouchState.windows.begin() + i);
-                            continue;
-                        }
-                    }
-                    i += 1;
+            for (size_t i = 0; i < tempTouchState.windows.size();) {
+                TouchedWindow& touchedWindow = tempTouchState.windows[i];
+                touchedWindow.pointerIds.clearBit(pointerId);
+                if (touchedWindow.pointerIds.isEmpty()) {
+                    tempTouchState.windows.erase(tempTouchState.windows.begin() + i);
+                    continue;
                 }
+                i += 1;
             }
         }
 
@@ -3678,7 +3679,7 @@
 }
 
 void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
-        const sp<Connection>& connection) {
+        const sp<Connection>& connection, int32_t targetFlags) {
     if (connection->status == Connection::Status::BROKEN) {
         return;
     }
@@ -3706,7 +3707,7 @@
         target.globalScaleFactor = windowInfo->globalScaleFactor;
     }
     target.inputChannel = connection->inputChannel;
-    target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+    target.flags = targetFlags;
 
     for (std::unique_ptr<EventEntry>& downEventEntry : downEvents) {
         switch (downEventEntry->type) {
@@ -3737,6 +3738,16 @@
     startDispatchCycleLocked(currentTime, connection);
 }
 
+void InputDispatcher::synthesizeCancelationEventsForWindowLocked(
+        const sp<WindowInfoHandle>& windowHandle, const CancelationOptions& options) {
+    if (windowHandle != nullptr) {
+        sp<Connection> wallpaperConnection = getConnectionLocked(windowHandle->getToken());
+        if (wallpaperConnection != nullptr) {
+            synthesizeCancelationEventsForConnectionLocked(wallpaperConnection, options);
+        }
+    }
+}
+
 std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
         const MotionEntry& originalMotionEntry, BitSet32 pointerIds) {
     ALOG_ASSERT(pointerIds.value != 0);
@@ -4470,6 +4481,14 @@
     if (it == mDisplayInfos.end()) return;
     const auto& transformToDisplay = it->second.transform.inverse() * injectedTransform;
 
+    if (entry.xCursorPosition != AMOTION_EVENT_INVALID_CURSOR_POSITION &&
+        entry.yCursorPosition != AMOTION_EVENT_INVALID_CURSOR_POSITION) {
+        const vec2 cursor =
+                MotionEvent::calculateTransformedXY(entry.source, transformToDisplay,
+                                                    {entry.xCursorPosition, entry.yCursorPosition});
+        entry.xCursorPosition = cursor.x;
+        entry.yCursorPosition = cursor.y;
+    }
     for (uint32_t i = 0; i < entry.pointerCount; i++) {
         entry.pointerCoords[i] =
                 MotionEvent::calculateTransformedCoords(entry.source, transformToDisplay,
@@ -4747,14 +4766,7 @@
                         touchedWindow.windowHandle->getInfo()->inputConfig.test(
                                 gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
                         sp<WindowInfoHandle> wallpaper = state.getWallpaperWindow();
-                        if (wallpaper != nullptr) {
-                            sp<Connection> wallpaperConnection =
-                                    getConnectionLocked(wallpaper->getToken());
-                            if (wallpaperConnection != nullptr) {
-                                synthesizeCancelationEventsForConnectionLocked(wallpaperConnection,
-                                                                               options);
-                            }
-                        }
+                        synthesizeCancelationEventsForWindowLocked(wallpaper, options);
                     }
                 }
                 state.windows.erase(state.windows.begin() + i);
@@ -5067,6 +5079,7 @@
         // Erase old window.
         int32_t oldTargetFlags = touchedWindow->targetFlags;
         BitSet32 pointerIds = touchedWindow->pointerIds;
+        sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
         state->removeWindowByToken(fromToken);
 
         // Add new window.
@@ -5079,14 +5092,13 @@
 
         // Store the dragging window.
         if (isDragDrop) {
-            if (pointerIds.count() > 1) {
-                ALOGW("The drag and drop cannot be started when there is more than 1 pointer on the"
-                      " window.");
+            if (pointerIds.count() != 1) {
+                ALOGW("The drag and drop cannot be started when there is no pointer or more than 1"
+                      " pointer on the window.");
                 return false;
             }
-            // If the window didn't not support split or the source is mouse, the pointerIds count
-            // would be 0, so we have to track the pointer 0.
-            const int32_t id = pointerIds.count() == 0 ? 0 : pointerIds.firstMarkedBit();
+            // Track the pointer id for drag window and generate the drag state.
+            const int32_t id = pointerIds.firstMarkedBit();
             mDragState = std::make_unique<DragState>(toWindowHandle, id);
         }
 
@@ -5099,7 +5111,10 @@
                     options(CancelationOptions::CANCEL_POINTER_EVENTS,
                             "transferring touch focus from this window to another window");
             synthesizeCancelationEventsForConnectionLocked(fromConnection, options);
-            synthesizePointerDownEventsForConnectionLocked(toConnection);
+            synthesizePointerDownEventsForConnectionLocked(toConnection, newTargetFlags);
+            // Check if the wallpaper window should deliver the corresponding event.
+            transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle,
+                                   *state, pointerIds);
         }
 
         if (DEBUG_FOCUS) {
@@ -6322,6 +6337,13 @@
 
     { // acquire lock
         std::scoped_lock _l(mLock);
+
+        // Ensure that we have an entry created for all existing displays so that if a displayId has
+        // no windows, we can tell that the windows were removed from the display.
+        for (const auto& [displayId, _] : mWindowHandlesByDisplay) {
+            handlesPerDisplay[displayId];
+        }
+
         mDisplayInfos.clear();
         for (const auto& displayInfo : displayInfos) {
             mDisplayInfos.emplace(displayInfo.displayId, displayInfo);
@@ -6377,4 +6399,97 @@
     mMonitorDispatchingTimeout = timeout;
 }
 
+void InputDispatcher::slipWallpaperTouch(int32_t targetFlags,
+                                         const sp<WindowInfoHandle>& oldWindowHandle,
+                                         const sp<WindowInfoHandle>& newWindowHandle,
+                                         TouchState& state, const BitSet32& pointerIds) {
+    const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
+            gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+    const bool newHasWallpaper = (targetFlags & InputTarget::FLAG_FOREGROUND) &&
+            newWindowHandle->getInfo()->inputConfig.test(
+                    gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+    const sp<WindowInfoHandle> oldWallpaper =
+            oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+    const sp<WindowInfoHandle> newWallpaper =
+            newHasWallpaper ? findWallpaperWindowBelow(newWindowHandle) : nullptr;
+    if (oldWallpaper == newWallpaper) {
+        return;
+    }
+
+    if (oldWallpaper != nullptr) {
+        state.addOrUpdateWindow(oldWallpaper, InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT,
+                                BitSet32(0));
+    }
+
+    if (newWallpaper != nullptr) {
+        state.addOrUpdateWindow(newWallpaper,
+                                InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER |
+                                        InputTarget::FLAG_WINDOW_IS_OBSCURED |
+                                        InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+                                pointerIds);
+    }
+}
+
+void InputDispatcher::transferWallpaperTouch(int32_t oldTargetFlags, int32_t newTargetFlags,
+                                             const sp<WindowInfoHandle> fromWindowHandle,
+                                             const sp<WindowInfoHandle> toWindowHandle,
+                                             TouchState& state, const BitSet32& pointerIds) {
+    const bool oldHasWallpaper = (oldTargetFlags & InputTarget::FLAG_FOREGROUND) &&
+            fromWindowHandle->getInfo()->inputConfig.test(
+                    gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+    const bool newHasWallpaper = (newTargetFlags & InputTarget::FLAG_FOREGROUND) &&
+            toWindowHandle->getInfo()->inputConfig.test(
+                    gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+
+    const sp<WindowInfoHandle> oldWallpaper =
+            oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+    const sp<WindowInfoHandle> newWallpaper =
+            newHasWallpaper ? findWallpaperWindowBelow(toWindowHandle) : nullptr;
+    if (oldWallpaper == newWallpaper) {
+        return;
+    }
+
+    if (oldWallpaper != nullptr) {
+        CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+                                   "transferring touch focus to another window");
+        state.removeWindowByToken(oldWallpaper->getToken());
+        synthesizeCancelationEventsForWindowLocked(oldWallpaper, options);
+    }
+
+    if (newWallpaper != nullptr) {
+        int32_t wallpaperFlags =
+                oldTargetFlags & (InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS);
+        wallpaperFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED |
+                InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+        state.addOrUpdateWindow(newWallpaper, wallpaperFlags, pointerIds);
+        sp<Connection> wallpaperConnection = getConnectionLocked(newWallpaper->getToken());
+        if (wallpaperConnection != nullptr) {
+            sp<Connection> toConnection = getConnectionLocked(toWindowHandle->getToken());
+            toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState);
+            synthesizePointerDownEventsForConnectionLocked(wallpaperConnection, wallpaperFlags);
+        }
+    }
+}
+
+sp<WindowInfoHandle> InputDispatcher::findWallpaperWindowBelow(
+        const sp<WindowInfoHandle>& windowHandle) const {
+    const std::vector<sp<WindowInfoHandle>>& windowHandles =
+            getWindowHandlesLocked(windowHandle->getInfo()->displayId);
+    bool foundWindow = false;
+    for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
+        if (!foundWindow && otherHandle != windowHandle) {
+            continue;
+        }
+        if (windowHandle == otherHandle) {
+            foundWindow = true;
+            continue;
+        }
+
+        if (otherHandle->getInfo()->inputConfig.test(WindowInfo::InputConfig::IS_WALLPAPER)) {
+            return otherHandle;
+        }
+    }
+    return nullptr;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index ed89ed0..0df5894 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -621,8 +621,12 @@
                                                         const CancelationOptions& options)
             REQUIRES(mLock);
 
-    void synthesizePointerDownEventsForConnectionLocked(const sp<Connection>& connection)
-            REQUIRES(mLock);
+    void synthesizePointerDownEventsForConnectionLocked(const sp<Connection>& connection,
+                                                        int32_t targetFlags) REQUIRES(mLock);
+
+    void synthesizeCancelationEventsForWindowLocked(
+            const sp<android::gui::WindowInfoHandle>& windowHandle,
+            const CancelationOptions& options) REQUIRES(mLock);
 
     // Splitting motion events across windows.
     std::unique_ptr<MotionEntry> splitMotionEvent(const MotionEntry& originalMotionEntry,
@@ -684,6 +688,18 @@
     bool recentWindowsAreOwnedByLocked(int32_t pid, int32_t uid) REQUIRES(mLock);
 
     sp<InputReporterInterface> mReporter;
+
+    void slipWallpaperTouch(int32_t targetFlags,
+                            const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
+                            const sp<android::gui::WindowInfoHandle>& newWindowHandle,
+                            TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock);
+    void transferWallpaperTouch(int32_t oldTargetFlags, int32_t newTargetFlags,
+                                const sp<android::gui::WindowInfoHandle> fromWindowHandle,
+                                const sp<android::gui::WindowInfoHandle> toWindowHandle,
+                                TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock);
+
+    sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
+            const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index d39113b..2df97d9 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -43,8 +43,8 @@
 }
 
 void InputTarget::addPointers(BitSet32 newPointerIds, const ui::Transform& transform) {
-    // The pointerIds can be empty, but still a valid InputTarget. This can happen for Monitors
-    // and non splittable windows since we will just use all the pointers from the input event.
+    // The pointerIds can be empty, but still a valid InputTarget. This can happen when there is no
+    // valid pointer property from the input event.
     if (newPointerIds.isEmpty()) {
         setDefaultPointerTransform(transform);
         return;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 4c31ec3..6783022 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -29,7 +29,7 @@
 struct TouchedWindow {
     sp<gui::WindowInfoHandle> windowHandle;
     int32_t targetFlags;
-    BitSet32 pointerIds; // zero unless target flag FLAG_SPLIT is set
+    BitSet32 pointerIds;
 };
 
 } // namespace inputdispatcher
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index ba5083b..989700f 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -110,7 +110,8 @@
         dump += "<none>\n";
     }
     dump += StringPrintf(INDENT2 "HasMic:     %s\n", toString(mHasMic));
-    dump += StringPrintf(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
+    dump += StringPrintf(INDENT2 "Sources: %s\n",
+                         inputEventSourceToString(deviceInfo.getSources()).c_str());
     dump += StringPrintf(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
     dump += StringPrintf(INDENT2 "ControllerNum: %d\n", deviceInfo.getControllerNumber());
 
@@ -128,10 +129,10 @@
                 snprintf(name, sizeof(name), "%d", range.axis);
             }
             dump += StringPrintf(INDENT3
-                                 "%s: source=0x%08x, "
+                                 "%s: source=%s, "
                                  "min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f, resolution=%0.3f\n",
-                                 name, range.source, range.min, range.max, range.flat, range.fuzz,
-                                 range.resolution);
+                                 name, inputEventSourceToString(range.source).c_str(), range.min,
+                                 range.max, range.flat, range.fuzz, range.resolution);
         }
     }
 
@@ -231,6 +232,10 @@
 }
 
 void InputDevice::removeEventHubDevice(int32_t eventHubId) {
+    if (mController != nullptr && mController->getEventHubId() == eventHubId) {
+        // Delete mController, since the corresponding eventhub device is going away
+        mController = nullptr;
+    }
     mDevices.erase(eventHubId);
 }
 
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 9c5a129..9bcf463 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -196,9 +196,9 @@
               "(ignored non-input device)",
               device->getId(), eventHubId, identifier.name.c_str(), identifier.descriptor.c_str());
     } else {
-        ALOGI("Device added: id=%d, eventHubId=%d, name='%s', descriptor='%s',sources=0x%08x",
+        ALOGI("Device added: id=%d, eventHubId=%d, name='%s', descriptor='%s',sources=%s",
               device->getId(), eventHubId, identifier.name.c_str(), identifier.descriptor.c_str(),
-              device->getSources());
+              inputEventSourceToString(device->getSources()).c_str());
     }
 
     mDevices.emplace(eventHubId, device);
@@ -250,9 +250,10 @@
               device->getId(), eventHubId, device->getName().c_str(),
               device->getDescriptor().c_str());
     } else {
-        ALOGI("Device removed: id=%d, eventHubId=%d, name='%s', descriptor='%s', sources=0x%08x",
+        ALOGI("Device removed: id=%d, eventHubId=%d, name='%s', descriptor='%s', sources=%s",
               device->getId(), eventHubId, device->getName().c_str(),
-              device->getDescriptor().c_str(), device->getSources());
+              device->getDescriptor().c_str(),
+              inputEventSourceToString(device->getSources()).c_str());
     }
 
     device->removeEventHubDevice(eventHubId);
diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp
index a693496..8065f57 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -524,4 +524,8 @@
     return light->getLightPlayerId();
 }
 
+int32_t PeripheralController::getEventHubId() const {
+    return getDeviceContext().getEventHubId();
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/controller/PeripheralController.h b/services/inputflinger/reader/controller/PeripheralController.h
index b1bc8c7..ac951eb 100644
--- a/services/inputflinger/reader/controller/PeripheralController.h
+++ b/services/inputflinger/reader/controller/PeripheralController.h
@@ -31,6 +31,7 @@
     explicit PeripheralController(InputDeviceContext& deviceContext);
     ~PeripheralController() override;
 
+    int32_t getEventHubId() const override;
     void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     void dump(std::string& dump) override;
     bool setLightColor(int32_t lightId, int32_t color) override;
@@ -43,6 +44,7 @@
 private:
     inline int32_t getDeviceId() { return mDeviceContext.getId(); }
     inline InputDeviceContext& getDeviceContext() { return mDeviceContext; }
+    inline InputDeviceContext& getDeviceContext() const { return mDeviceContext; }
 
     InputDeviceContext& mDeviceContext;
     void configureLights();
diff --git a/services/inputflinger/reader/controller/PeripheralControllerInterface.h b/services/inputflinger/reader/controller/PeripheralControllerInterface.h
index 7688a43..306e361 100644
--- a/services/inputflinger/reader/controller/PeripheralControllerInterface.h
+++ b/services/inputflinger/reader/controller/PeripheralControllerInterface.h
@@ -33,6 +33,8 @@
     PeripheralControllerInterface() {}
     virtual ~PeripheralControllerInterface() {}
 
+    virtual int32_t getEventHubId() const = 0;
+
     // Interface methods for Battery
     virtual std::optional<int32_t> getBatteryCapacity(int32_t batteryId) = 0;
     virtual std::optional<int32_t> getBatteryStatus(int32_t batteryId) = 0;
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index a9a4c71..8233682 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -25,6 +25,8 @@
 #include "PointerControllerInterface.h"
 #include "TouchCursorInputMapperCommon.h"
 
+#include "input/PrintTools.h"
+
 namespace android {
 
 // The default velocity control parameters that has no effect.
@@ -76,7 +78,7 @@
 void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
     InputMapper::populateDeviceInfo(info);
 
-    if (mParameters.mode == Parameters::MODE_POINTER) {
+    if (mParameters.mode == Parameters::Mode::POINTER) {
         float minX, minY, maxX, maxY;
         if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
             info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f, 0.0f);
@@ -113,6 +115,7 @@
                          toString(mCursorScrollAccumulator.haveRelativeHWheel()));
     dump += StringPrintf(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale);
     dump += StringPrintf(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale);
+    dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str());
     dump += StringPrintf(INDENT3 "Orientation: %d\n", mOrientation);
     dump += StringPrintf(INDENT3 "ButtonState: 0x%08x\n", mButtonState);
     dump += StringPrintf(INDENT3 "Down: %s\n", toString(isPointerDown(mButtonState)));
@@ -131,12 +134,12 @@
 
         // Configure device mode.
         switch (mParameters.mode) {
-            case Parameters::MODE_POINTER_RELATIVE:
+            case Parameters::Mode::POINTER_RELATIVE:
                 // Should not happen during first time configuration.
                 ALOGE("Cannot start a device in MODE_POINTER_RELATIVE, starting in MODE_POINTER");
-                mParameters.mode = Parameters::MODE_POINTER;
+                mParameters.mode = Parameters::Mode::POINTER;
                 [[fallthrough]];
-            case Parameters::MODE_POINTER:
+            case Parameters::Mode::POINTER:
                 mSource = AINPUT_SOURCE_MOUSE;
                 mXPrecision = 1.0f;
                 mYPrecision = 1.0f;
@@ -144,7 +147,7 @@
                 mYScale = 1.0f;
                 mPointerController = getContext()->getPointerController(getDeviceId());
                 break;
-            case Parameters::MODE_NAVIGATION:
+            case Parameters::Mode::NAVIGATION:
                 mSource = AINPUT_SOURCE_TRACKBALL;
                 mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
                 mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
@@ -157,12 +160,13 @@
         mHWheelScale = 1.0f;
     }
 
-    const bool configurePointerCapture = (!changes && config->pointerCaptureRequest.enable) ||
-            (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+    const bool configurePointerCapture = mParameters.mode != Parameters::Mode::NAVIGATION &&
+            ((!changes && config->pointerCaptureRequest.enable) ||
+             (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE));
     if (configurePointerCapture) {
         if (config->pointerCaptureRequest.enable) {
-            if (mParameters.mode == Parameters::MODE_POINTER) {
-                mParameters.mode = Parameters::MODE_POINTER_RELATIVE;
+            if (mParameters.mode == Parameters::Mode::POINTER) {
+                mParameters.mode = Parameters::Mode::POINTER_RELATIVE;
                 mSource = AINPUT_SOURCE_MOUSE_RELATIVE;
                 // Keep PointerController around in order to preserve the pointer position.
                 mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
@@ -170,8 +174,8 @@
                 ALOGE("Cannot request pointer capture, device is not in MODE_POINTER");
             }
         } else {
-            if (mParameters.mode == Parameters::MODE_POINTER_RELATIVE) {
-                mParameters.mode = Parameters::MODE_POINTER;
+            if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) {
+                mParameters.mode = Parameters::Mode::POINTER;
                 mSource = AINPUT_SOURCE_MOUSE;
             } else {
                 ALOGE("Cannot release pointer capture, device is not in MODE_POINTER_RELATIVE");
@@ -186,8 +190,8 @@
 
     if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED) ||
         configurePointerCapture) {
-        if (config->pointerCaptureRequest.enable) {
-            // Disable any acceleration or scaling when Pointer Capture is enabled.
+        if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) {
+            // Disable any acceleration or scaling for the pointer when Pointer Capture is enabled.
             mPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
             mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
             mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
@@ -198,21 +202,36 @@
         }
     }
 
-    if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+    if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO) ||
+        configurePointerCapture) {
+        const bool isPointer = mParameters.mode == Parameters::Mode::POINTER;
+
+        mDisplayId = ADISPLAY_ID_NONE;
+        if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) {
+            // This InputDevice is associated with a viewport.
+            // Only generate events for the associated display.
+            const bool mismatchedPointerDisplay =
+                    isPointer && (viewport->displayId != mPointerController->getDisplayId());
+            mDisplayId = mismatchedPointerDisplay ? std::nullopt
+                                                  : std::make_optional(viewport->displayId);
+        } else if (isPointer) {
+            // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
+            mDisplayId = mPointerController->getDisplayId();
+        }
+
         mOrientation = DISPLAY_ORIENTATION_0;
         const bool isOrientedDevice =
                 (mParameters.orientationAware && mParameters.hasAssociatedDisplay);
-
         // InputReader works in the un-rotated display coordinate space, so we don't need to do
         // anything if the device is already orientation-aware. If the device is not
         // orientation-aware, then we need to apply the inverse rotation of the display so that
         // when the display rotation is applied later as a part of the per-window transform, we
-        // get the expected screen coordinates.
-        if (!isOrientedDevice) {
-            std::optional<DisplayViewport> internalViewport =
-                    config->getDisplayViewportByType(ViewportType::INTERNAL);
-            if (internalViewport) {
-                mOrientation = getInverseRotation(internalViewport->orientation);
+        // get the expected screen coordinates. When pointer capture is enabled, we do not apply any
+        // rotations and report values directly from the input device.
+        if (!isOrientedDevice && mDisplayId &&
+            mParameters.mode != Parameters::Mode::POINTER_RELATIVE) {
+            if (auto viewport = config->getDisplayViewportById(*mDisplayId); viewport) {
+                mOrientation = getInverseRotation(viewport->orientation);
             }
         }
 
@@ -221,12 +240,12 @@
 }
 
 void CursorInputMapper::configureParameters() {
-    mParameters.mode = Parameters::MODE_POINTER;
+    mParameters.mode = Parameters::Mode::POINTER;
     String8 cursorModeString;
     if (getDeviceContext().getConfiguration().tryGetProperty(String8("cursor.mode"),
                                                              cursorModeString)) {
         if (cursorModeString == "navigation") {
-            mParameters.mode = Parameters::MODE_NAVIGATION;
+            mParameters.mode = Parameters::Mode::NAVIGATION;
         } else if (cursorModeString != "pointer" && cursorModeString != "default") {
             ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.string());
         }
@@ -237,7 +256,7 @@
                                                          mParameters.orientationAware);
 
     mParameters.hasAssociatedDisplay = false;
-    if (mParameters.mode == Parameters::MODE_POINTER || mParameters.orientationAware) {
+    if (mParameters.mode == Parameters::Mode::POINTER || mParameters.orientationAware) {
         mParameters.hasAssociatedDisplay = true;
     }
 }
@@ -246,21 +265,7 @@
     dump += INDENT3 "Parameters:\n";
     dump += StringPrintf(INDENT4 "HasAssociatedDisplay: %s\n",
                          toString(mParameters.hasAssociatedDisplay));
-
-    switch (mParameters.mode) {
-        case Parameters::MODE_POINTER:
-            dump += INDENT4 "Mode: pointer\n";
-            break;
-        case Parameters::MODE_POINTER_RELATIVE:
-            dump += INDENT4 "Mode: relative pointer\n";
-            break;
-        case Parameters::MODE_NAVIGATION:
-            dump += INDENT4 "Mode: navigation\n";
-            break;
-        default:
-            ALOG_ASSERT(false);
-    }
-
+    dump += StringPrintf(INDENT4 "Mode: %s\n", ftl::enum_string(mParameters.mode).c_str());
     dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
 }
 
@@ -290,6 +295,11 @@
 }
 
 void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) {
+    if (!mDisplayId) {
+        // Ignore events when there is no target display configured.
+        return;
+    }
+
     int32_t lastButtonState = mButtonState;
     int32_t currentButtonState = mCursorButtonAccumulator.getButtonState();
     mButtonState = currentButtonState;
@@ -335,7 +345,6 @@
 
     mPointerVelocityControl.move(when, &deltaX, &deltaY);
 
-    int32_t displayId = ADISPLAY_ID_NONE;
     float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     if (mSource == AINPUT_SOURCE_MOUSE) {
@@ -359,7 +368,6 @@
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
-        displayId = mPointerController->getDisplayId();
     } else {
         // Pointer capture and navigation modes
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX);
@@ -381,7 +389,7 @@
 
     // Synthesize key down from buttons if needed.
     synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(),
-                         mSource, displayId, policyFlags, lastButtonState, currentButtonState);
+                         mSource, *mDisplayId, policyFlags, lastButtonState, currentButtonState);
 
     // Send motion event.
     if (downChanged || moved || scrolled || buttonsChanged) {
@@ -402,7 +410,7 @@
                 int32_t actionButton = BitSet32::valueForBit(released.clearFirstMarkedBit());
                 buttonState &= ~actionButton;
                 NotifyMotionArgs releaseArgs(getContext()->getNextId(), when, readTime,
-                                             getDeviceId(), mSource, displayId, policyFlags,
+                                             getDeviceId(), mSource, *mDisplayId, policyFlags,
                                              AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0,
                                              metaState, buttonState, MotionClassification::NONE,
                                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
@@ -414,7 +422,7 @@
         }
 
         NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
-                              displayId, policyFlags, motionEventAction, 0, 0, metaState,
+                              *mDisplayId, policyFlags, motionEventAction, 0, 0, metaState,
                               currentButtonState, MotionClassification::NONE,
                               AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords,
                               mXPrecision, mYPrecision, xCursorPosition, yCursorPosition, downTime,
@@ -427,7 +435,7 @@
                 int32_t actionButton = BitSet32::valueForBit(pressed.clearFirstMarkedBit());
                 buttonState |= actionButton;
                 NotifyMotionArgs pressArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
-                                           mSource, displayId, policyFlags,
+                                           mSource, *mDisplayId, policyFlags,
                                            AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0,
                                            metaState, buttonState, MotionClassification::NONE,
                                            AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
@@ -443,7 +451,7 @@
         // Send hover move after UP to tell the application that the mouse is hovering now.
         if (motionEventAction == AMOTION_EVENT_ACTION_UP && (mSource == AINPUT_SOURCE_MOUSE)) {
             NotifyMotionArgs hoverArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
-                                       mSource, displayId, policyFlags,
+                                       mSource, *mDisplayId, policyFlags,
                                        AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
                                        currentButtonState, MotionClassification::NONE,
                                        AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
@@ -458,7 +466,7 @@
             pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
 
             NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
-                                        mSource, displayId, policyFlags,
+                                        mSource, *mDisplayId, policyFlags,
                                         AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState,
                                         currentButtonState, MotionClassification::NONE,
                                         AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
@@ -470,7 +478,7 @@
 
     // Synthesize key up from buttons if needed.
     synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), mSource,
-                         displayId, policyFlags, lastButtonState, currentButtonState);
+                         *mDisplayId, policyFlags, lastButtonState, currentButtonState);
 
     mCursorMotionAccumulator.finishSync();
     mCursorScrollAccumulator.finishSync();
@@ -485,16 +493,7 @@
 }
 
 std::optional<int32_t> CursorInputMapper::getAssociatedDisplayId() {
-    if (mParameters.hasAssociatedDisplay) {
-        if (mParameters.mode == Parameters::MODE_POINTER) {
-            return std::make_optional(mPointerController->getDisplayId());
-        } else {
-            // If the device is orientationAware and not a mouse,
-            // it expects to dispatch events to any display
-            return std::make_optional(ADISPLAY_ID_NONE);
-        }
-    }
-    return std::nullopt;
+    return mDisplayId;
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index c84c6c4..60b3dd9 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -74,10 +74,17 @@
 
     // Immutable configuration parameters.
     struct Parameters {
-        enum Mode {
-            MODE_POINTER,
-            MODE_POINTER_RELATIVE,
-            MODE_NAVIGATION,
+        enum class Mode {
+            // In POINTER mode, the device is a mouse that controls the mouse cursor on the screen,
+            // reporting absolute screen locations using SOURCE_MOUSE.
+            POINTER,
+            // A mouse device in POINTER mode switches to the POINTER_RELATIVE mode when Pointer
+            // Capture is enabled, and reports relative values only using SOURCE_MOUSE_RELATIVE.
+            POINTER_RELATIVE,
+            // A device in NAVIGATION mode emits relative values using SOURCE_TRACKBALL.
+            NAVIGATION,
+
+            ftl_last = NAVIGATION,
         };
 
         Mode mode;
@@ -104,6 +111,10 @@
     VelocityControl mWheelXVelocityControl;
     VelocityControl mWheelYVelocityControl;
 
+    // The display that events generated by this mapper should target. This can be set to
+    // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
+    // std::nullopt), all events will be ignored.
+    std::optional<int32_t> mDisplayId;
     int32_t mOrientation;
 
     std::shared_ptr<PointerControllerInterface> mPointerController;
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 41a8426..cc323ad 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -48,11 +48,8 @@
 
     delete[] mSlots;
     mSlots = new Slot[slotCount];
-}
 
-void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) {
-    // Unfortunately there is no way to read the initial contents of the slots.
-    // So when we reset the accumulator, we must assume they are all zeroes.
+    mCurrentSlot = -1;
     if (mUsingSlotsProtocol) {
         // Query the driver for the current slot index and use it as the initial slot
         // before we start reading events from the device.  It is possible that the
@@ -64,24 +61,22 @@
         // This can cause the touch point to "jump", but at least there will be
         // no stuck touches.
         int32_t initialSlot;
-        status_t status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
-        if (status) {
-            ALOGD("Could not retrieve current multitouch slot index.  status=%d", status);
-            initialSlot = -1;
+        if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
+            status == OK) {
+            mCurrentSlot = initialSlot;
+        } else {
+            ALOGD("Could not retrieve current multi-touch slot index. status=%d", status);
         }
-        clearSlots(initialSlot);
-    } else {
-        clearSlots(-1);
     }
 }
 
-void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) {
+void MultiTouchMotionAccumulator::resetSlots() {
     if (mSlots) {
         for (size_t i = 0; i < mSlotCount; i++) {
             mSlots[i].clear();
         }
     }
-    mCurrentSlot = initialSlot;
+    mCurrentSlot = -1;
 }
 
 void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
@@ -169,7 +164,7 @@
 
 void MultiTouchMotionAccumulator::finishSync() {
     if (!mUsingSlotsProtocol) {
-        clearSlots(-1);
+        resetSlots();
     }
 }
 
@@ -230,10 +225,12 @@
 MultiTouchInputMapper::~MultiTouchInputMapper() {}
 
 void MultiTouchInputMapper::reset(nsecs_t when) {
-    mMultiTouchMotionAccumulator.reset(getDeviceContext());
-
-    mPointerIdBits.clear();
-
+    // The evdev multi-touch protocol does not allow userspace applications to query the initial or
+    // current state of the pointers at any time. This means if we clear our accumulated state when
+    // resetting the input mapper, there's no way to rebuild the full initial state of the pointers.
+    // We can only wait for updates to all the pointers and axes. Rather than clearing the state and
+    // rebuilding the state from scratch, we work around this kernel API limitation by never
+    // fully clearing any state specific to the multi-touch protocol.
     TouchInputMapper::reset(when);
 }
 
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index b7c3457..e7d9350 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -71,7 +71,6 @@
     ~MultiTouchMotionAccumulator();
 
     void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol);
-    void reset(InputDeviceContext& deviceContext);
     void process(const RawEvent* rawEvent);
     void finishSync();
     bool hasStylus() const;
@@ -86,7 +85,7 @@
     bool mUsingSlotsProtocol;
     bool mHaveStylus;
 
-    void clearSlots(int32_t initialSlot);
+    void resetSlots();
     void warnIfNotInUse(const RawEvent& event, const Slot& slot);
 };
 
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 637b1cb..5a70167 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -44,6 +44,8 @@
 
 // --- Static Definitions ---
 
+static const DisplayViewport kUninitializedViewport;
+
 template <typename T>
 inline static void swap(T& a, T& b) {
     T temp = a;
@@ -390,6 +392,10 @@
     }
 
     if (changes && resetNeeded) {
+        // If the device needs to be reset, cancel any ongoing gestures and reset the state.
+        cancelTouch(when, when);
+        reset(when);
+
         // Send reset, unless this is the first time the device has been configured,
         // in which case the reader will call reset itself after all mappers are ready.
         NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId());
@@ -877,7 +883,7 @@
 }
 
 void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) {
-    DeviceMode oldDeviceMode = mDeviceMode;
+    const DeviceMode oldDeviceMode = mDeviceMode;
 
     resolveExternalStylusPresence();
 
@@ -906,42 +912,37 @@
         mDeviceMode = DeviceMode::UNSCALED;
     }
 
-    // Ensure we have valid X and Y axes.
+    const std::optional<DisplayViewport> newViewportOpt = findViewport();
+
+    // Ensure the device is valid and can be used.
     if (!mRawPointerAxes.x.valid || !mRawPointerAxes.y.valid) {
         ALOGW("Touch device '%s' did not report support for X or Y axis!  "
               "The device will be inoperable.",
               getDeviceName().c_str());
         mDeviceMode = DeviceMode::DISABLED;
-        return;
-    }
-
-    // Get associated display dimensions.
-    std::optional<DisplayViewport> newViewport = findViewport();
-    if (!newViewport) {
+    } else if (!newViewportOpt) {
         ALOGI("Touch device '%s' could not query the properties of its associated "
               "display.  The device will be inoperable until the display size "
               "becomes available.",
               getDeviceName().c_str());
         mDeviceMode = DeviceMode::DISABLED;
-        return;
-    }
-
-    if (!newViewport->isActive) {
+    } else if (!newViewportOpt->isActive) {
         ALOGI("Disabling %s (device %i) because the associated viewport is not active",
               getDeviceName().c_str(), getDeviceId());
         mDeviceMode = DeviceMode::DISABLED;
-        return;
     }
 
     // Raw width and height in the natural orientation.
     const int32_t rawWidth = mRawPointerAxes.getRawWidth();
     const int32_t rawHeight = mRawPointerAxes.getRawHeight();
 
-    const bool viewportChanged = mViewport != *newViewport;
+    const DisplayViewport& newViewport = newViewportOpt.value_or(kUninitializedViewport);
+    const bool viewportChanged = mViewport != newViewport;
     bool skipViewportUpdate = false;
     if (viewportChanged) {
-        const bool viewportOrientationChanged = mViewport.orientation != newViewport->orientation;
-        mViewport = *newViewport;
+        const bool viewportOrientationChanged = mViewport.orientation != newViewport.orientation;
+        const bool viewportDisplayIdChanged = mViewport.displayId != newViewport.displayId;
+        mViewport = newViewport;
 
         if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) {
             // Convert rotated viewport to the natural orientation.
@@ -1016,8 +1017,9 @@
                     : getInverseRotation(mViewport.orientation);
             // For orientation-aware devices that work in the un-rotated coordinate space, the
             // viewport update should be skipped if it is only a change in the orientation.
-            skipViewportUpdate = mParameters.orientationAware && mDisplayWidth == oldDisplayWidth &&
-                    mDisplayHeight == oldDisplayHeight && viewportOrientationChanged;
+            skipViewportUpdate = !viewportDisplayIdChanged && mParameters.orientationAware &&
+                    mDisplayWidth == oldDisplayWidth && mDisplayHeight == oldDisplayHeight &&
+                    viewportOrientationChanged;
 
             // Apply the input device orientation for the device.
             mInputDeviceOrientation =
@@ -1094,10 +1096,6 @@
             // of the diagonal axis of the touch pad.  Touches that are wider than this are
             // translated into freeform gestures.
             mPointerGestureMaxSwipeWidth = mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal;
-
-            // Abort current pointer usages because the state has changed.
-            const nsecs_t readTime = when; // synthetic event
-            abortPointerUsage(when, readTime, 0 /*policyFlags*/);
         }
 
         // Inform the dispatcher about the changes.
@@ -1470,6 +1468,10 @@
 }
 
 void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) {
+    if (mDeviceMode == DeviceMode::DISABLED) {
+        // Only save the last pending state when the device is disabled.
+        mRawStatesPending.clear();
+    }
     // Push a new state.
     mRawStatesPending.emplace_back();
 
@@ -1926,6 +1928,10 @@
 }
 
 void TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
+    if (mCurrentMotionAborted) {
+        // Current motion event was already aborted.
+        return;
+    }
     BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
     if (!currentIdBits.isEmpty()) {
         int32_t metaState = getContext()->getGlobalMetaState();
@@ -3515,6 +3521,8 @@
 
 void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
                                              bool down, bool hovering) {
+    LOG_ALWAYS_FATAL_IF(mDeviceMode != DeviceMode::POINTER,
+                        "%s cannot be used when the device is not in POINTER mode.", __func__);
     int32_t metaState = getContext()->getGlobalMetaState();
 
     if (down || hovering) {
@@ -3637,6 +3645,10 @@
     if (down || hovering) {
         mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords);
         mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties);
+        mPointerSimple.displayId = displayId;
+        mPointerSimple.source = mSource;
+        mPointerSimple.lastCursorX = xCursorPosition;
+        mPointerSimple.lastCursorY = yCursorPosition;
     } else {
         mPointerSimple.reset();
     }
@@ -3646,7 +3658,23 @@
     mPointerSimple.currentCoords.clear();
     mPointerSimple.currentProperties.clear();
 
-    dispatchPointerSimple(when, readTime, policyFlags, false, false);
+    if (mPointerSimple.down || mPointerSimple.hovering) {
+        int32_t metaState = getContext()->getGlobalMetaState();
+        NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(),
+                              mPointerSimple.source, mPointerSimple.displayId, policyFlags,
+                              AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED,
+                              metaState, mLastRawState.buttonState, MotionClassification::NONE,
+                              AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties,
+                              &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision,
+                              mPointerSimple.lastCursorX, mPointerSimple.lastCursorY,
+                              mPointerSimple.downTime,
+                              /* videoFrames */ {});
+        getListener().notifyMotion(&args);
+        if (mPointerController != nullptr) {
+            mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
+        }
+    }
+    mPointerSimple.reset();
 }
 
 void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index c948f56..2937bf8 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -327,6 +327,8 @@
         int32_t rawVScroll;
         int32_t rawHScroll;
 
+        explicit inline RawState() { clear(); }
+
         void copyFrom(const RawState& other) {
             when = other.when;
             readTime = other.readTime;
@@ -712,6 +714,12 @@
         // Time the pointer last went down.
         nsecs_t downTime;
 
+        // Values reported for the last pointer event.
+        uint32_t source;
+        int32_t displayId;
+        float lastCursorX;
+        float lastCursorY;
+
         void reset() {
             currentCoords.clear();
             currentProperties.clear();
@@ -720,6 +728,10 @@
             down = false;
             hovering = false;
             downTime = 0;
+            source = 0;
+            displayId = ADISPLAY_ID_NONE;
+            lastCursorX = 0.f;
+            lastCursorY = 0.f;
         }
     } mPointerSimple;
 
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 76a7c19..75cd9da 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -60,6 +60,7 @@
     },
     static_libs: [
         "libc++fs",
+        "libgmock",
     ],
     require_root: true,
     test_suites: ["device-tests"],
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index df43071..ca0ef2a 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -58,6 +58,8 @@
         AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 static constexpr int32_t POINTER_2_DOWN =
         AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_0_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 static constexpr int32_t POINTER_1_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
@@ -73,6 +75,9 @@
 
 static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms;
 
+static constexpr int expectedWallpaperFlags =
+        AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+
 struct PointF {
     float x;
     float y;
@@ -1670,8 +1675,6 @@
     sp<FakeWindowHandle> wallpaperWindow =
             new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
-    constexpr int expectedWallpaperFlags =
-            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1714,8 +1717,6 @@
     sp<FakeWindowHandle> wallpaperWindow =
             new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
-    constexpr int expectedWallpaperFlags =
-            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1745,24 +1746,27 @@
     foregroundWindow->consumeMotionCancel();
 }
 
+class ShouldSplitTouchFixture : public InputDispatcherTest,
+                                public ::testing::WithParamInterface<bool> {};
+INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture,
+                         ::testing::Values(true, false));
 /**
  * A single window that receives touch (on top), and a wallpaper window underneath it.
  * The top window gets a multitouch gesture.
  * Ensure that wallpaper gets the same gesture.
  */
-TEST_F(InputDispatcherTest, WallpaperWindow_ReceivesMultiTouch) {
+TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
-    window->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> foregroundWindow =
+            new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+    foregroundWindow->setDupTouchToWallpaper(true);
+    foregroundWindow->setPreventSplitting(GetParam());
 
     sp<FakeWindowHandle> wallpaperWindow =
             new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
-    constexpr int expectedWallpaperFlags =
-            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}});
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
 
     // Touch down on top window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1771,7 +1775,7 @@
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both top window and its wallpaper should receive the touch down
-    window->consumeMotionDown();
+    foregroundWindow->consumeMotionDown();
     wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     // Second finger down on the top window
@@ -1790,11 +1794,34 @@
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    window->consumeMotionPointerDown(1 /* pointerIndex */);
+    foregroundWindow->consumeMotionPointerDown(1 /* pointerIndex */);
     wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
                                               expectedWallpaperFlags);
-    window->assertNoEvents();
-    wallpaperWindow->assertNoEvents();
+
+    const MotionEvent secondFingerUpEvent =
+            MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(100)
+                                     .y(100))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(150)
+                                     .y(150))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    foregroundWindow->consumeMotionPointerUp(0);
+    wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                             {100, 100}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 }
 
 /**
@@ -1821,8 +1848,6 @@
             new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setFrame(Rect(0, 0, 400, 200));
     wallpaperWindow->setIsWallpaper(true);
-    constexpr int expectedWallpaperFlags =
-            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
     mDispatcher->setInputWindows(
             {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
@@ -1888,6 +1913,51 @@
 }
 
 /**
+ * Two windows: a window on the left with dup touch to wallpaper and window on the right without it.
+ * The touch slips to the right window. so left window and wallpaper should receive ACTION_CANCEL
+ * The right window should receive ACTION_DOWN.
+ */
+TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+    leftWindow->setDupTouchToWallpaper(true);
+    leftWindow->setSlippery(true);
+
+    sp<FakeWindowHandle> rightWindow =
+            new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    sp<FakeWindowHandle> wallpaperWindow =
+            new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->setIsWallpaper(true);
+
+    mDispatcher->setInputWindows(
+            {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
+
+    // Touch down on left window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 100}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Both foreground window and its wallpaper should receive the touch down
+    leftWindow->consumeMotionDown();
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    // Move to right window, the left window should receive cancel.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {201, 100}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    leftWindow->consumeMotionCancel();
+    rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+}
+
+/**
  * On the display, have a single window, and also an area where there's no window.
  * First pointer touches the "no window" area of the screen. Second pointer touches the window.
  * Make sure that the window receives the second pointer, and first pointer is simply ignored.
@@ -2293,6 +2363,72 @@
     thirdWindow->consumeMotionDown();
 }
 
+TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+    window->setFocusable(true);
+
+    mDispatcher->onWindowInfosChanged({*window->getInfo()}, {});
+    setFocusedWindow(window);
+
+    window->consumeFocusEvent(true);
+
+    NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(&keyDown);
+    mDispatcher->notifyKey(&keyUp);
+
+    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+
+    // All windows are removed from the display. Ensure that we can no longer dispatch to it.
+    mDispatcher->onWindowInfosChanged({}, {});
+
+    window->consumeFocusEvent(false);
+
+    mDispatcher->notifyKey(&keyDown);
+    mDispatcher->notifyKey(&keyUp);
+    window->assertNoEvents();
+}
+
+TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    // Ensure window is non-split and have some transform.
+    window->setPreventSplitting(true);
+    window->setWindowOffset(20, 40);
+    mDispatcher->onWindowInfosChanged({*window->getInfo()}, {});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {50, 50}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(-30)
+                                     .y(-50))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    const MotionEvent* event = window->consumeMotion();
+    EXPECT_EQ(POINTER_1_DOWN, event->getAction());
+    EXPECT_EQ(70, event->getX(0));  // 50 + 20
+    EXPECT_EQ(90, event->getY(0));  // 50 + 40
+    EXPECT_EQ(-10, event->getX(1)); // -30 + 20
+    EXPECT_EQ(-10, event->getY(1)); // -50 + 40
+}
+
 /**
  * Ensure the correct coordinate spaces are used by InputDispatcher.
  *
@@ -2443,20 +2579,26 @@
     // Create a couple of windows
     sp<FakeWindowHandle> firstWindow =
             new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+    firstWindow->setDupTouchToWallpaper(true);
+
     sp<FakeWindowHandle> secondWindow =
             new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
-
+    sp<FakeWindowHandle> wallpaper =
+            new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+    wallpaper->setIsWallpaper(true);
     // Add the windows to the dispatcher
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow, wallpaper}}});
 
     // Send down to the first window
     NotifyMotionArgs downMotionArgs =
             generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                                ADISPLAY_ID_DEFAULT);
     mDispatcher->notifyMotion(&downMotionArgs);
+
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
+    wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     // Transfer touch to the second window
     TransferFunction f = GetParam();
@@ -2465,6 +2607,7 @@
     // The first window gets cancel and the second gets down
     firstWindow->consumeMotionCancel();
     secondWindow->consumeMotionDown();
+    wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     // Send up event to the second window
     NotifyMotionArgs upMotionArgs =
@@ -2474,6 +2617,7 @@
     // The first  window gets no events and the second gets up
     firstWindow->assertNoEvents();
     secondWindow->consumeMotionUp();
+    wallpaper->assertNoEvents();
 }
 
 /**
@@ -2595,6 +2739,65 @@
     secondWindow->consumeMotionUp();
 }
 
+TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    // Create a couple of windows
+    sp<FakeWindowHandle> firstWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+                                       ADISPLAY_ID_DEFAULT);
+    firstWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> secondWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+                                       ADISPLAY_ID_DEFAULT);
+    secondWindow->setDupTouchToWallpaper(true);
+
+    sp<FakeWindowHandle> wallpaper1 =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper1", ADISPLAY_ID_DEFAULT);
+    wallpaper1->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> wallpaper2 =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper2", ADISPLAY_ID_DEFAULT);
+    wallpaper2->setIsWallpaper(true);
+    // Add the windows to the dispatcher
+    mDispatcher->setInputWindows(
+            {{ADISPLAY_ID_DEFAULT, {firstWindow, wallpaper1, secondWindow, wallpaper2}}});
+
+    // Send down to the first window
+    NotifyMotionArgs downMotionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&downMotionArgs);
+
+    // Only the first window should get the down event
+    firstWindow->consumeMotionDown();
+    secondWindow->assertNoEvents();
+    wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper2->assertNoEvents();
+
+    // Transfer touch focus to the second window
+    TransferFunction f = GetParam();
+    bool success = f(mDispatcher, firstWindow->getToken(), secondWindow->getToken());
+    ASSERT_TRUE(success);
+
+    // The first window gets cancel and the second gets down
+    firstWindow->consumeMotionCancel();
+    secondWindow->consumeMotionDown();
+    wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    // Send up event to the second window
+    NotifyMotionArgs upMotionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&upMotionArgs);
+    // The first  window gets no events and the second gets up
+    firstWindow->assertNoEvents();
+    secondWindow->consumeMotionUp();
+    wallpaper1->assertNoEvents();
+    wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+}
+
 // For the cases of single pointer touch and two pointers non-split touch, the api's
 // 'transferTouch' and 'transferTouchFocus' are equivalent in behaviour. They only differ
 // for the case where there are multiple pointers split across several windows.
@@ -6096,6 +6299,8 @@
     sp<FakeWindowHandle> mWindow;
     sp<FakeWindowHandle> mSecondWindow;
     sp<FakeWindowHandle> mDragWindow;
+    // Mouse would force no-split, set the id as non-zero to verify if drag state could track it.
+    static constexpr int32_t MOUSE_POINTER_ID = 1;
 
     void SetUp() override {
         InputDispatcherTest::SetUp();
@@ -6110,11 +6315,41 @@
         mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}});
     }
 
-    void injectDown() {
-        ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                   {50, 50}))
-                << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    void injectDown(int fromSource = AINPUT_SOURCE_TOUCHSCREEN) {
+        switch (fromSource) {
+            case AINPUT_SOURCE_TOUCHSCREEN:
+                ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+                          injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                                           ADISPLAY_ID_DEFAULT, {50, 50}))
+                        << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+                break;
+            case AINPUT_SOURCE_STYLUS:
+                ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+                          injectMotionEvent(
+                                  mDispatcher,
+                                  MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                     AINPUT_SOURCE_STYLUS)
+                                          .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
+                                          .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+                                                           .x(50)
+                                                           .y(50))
+                                          .build()));
+                break;
+            case AINPUT_SOURCE_MOUSE:
+                ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+                          injectMotionEvent(
+                                  mDispatcher,
+                                  MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                          .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                          .pointer(PointerBuilder(MOUSE_POINTER_ID,
+                                                                  AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                           .x(50)
+                                                           .y(50))
+                                          .build()));
+                break;
+            default:
+                FAIL() << "Source " << fromSource << " doesn't support drag and drop";
+        }
 
         // Window should receive motion event.
         mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -6123,9 +6358,9 @@
     // Start performing drag, we will create a drag window and transfer touch to it.
     // @param sendDown : if true, send a motion down on first window before perform drag and drop.
     // Returns true on success.
-    bool performDrag(bool sendDown = true) {
+    bool startDrag(bool sendDown = true, int fromSource = AINPUT_SOURCE_TOUCHSCREEN) {
         if (sendDown) {
-            injectDown();
+            injectDown(fromSource);
         }
 
         // The drag window covers the entire display
@@ -6143,36 +6378,10 @@
         }
         return transferred;
     }
-
-    // Start performing drag, we will create a drag window and transfer touch to it.
-    void performStylusDrag() {
-        ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  injectMotionEvent(mDispatcher,
-                                    MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
-                                                       AINPUT_SOURCE_STYLUS)
-                                            .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
-                                            .pointer(PointerBuilder(0,
-                                                                    AMOTION_EVENT_TOOL_TYPE_STYLUS)
-                                                             .x(50)
-                                                             .y(50))
-                                            .build()));
-        mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-
-        // The drag window covers the entire display
-        mDragWindow = new FakeWindowHandle(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT);
-        mDispatcher->setInputWindows(
-                {{ADISPLAY_ID_DEFAULT, {mDragWindow, mWindow, mSecondWindow}}});
-
-        // Transfer touch focus to the drag window
-        mDispatcher->transferTouchFocus(mWindow->getToken(), mDragWindow->getToken(),
-                                        true /* isDragDrop */);
-        mWindow->consumeMotionCancel();
-        mDragWindow->consumeMotionDown();
-    }
 };
 
 TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) {
-    performDrag();
+    startDrag();
 
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -6210,7 +6419,7 @@
 }
 
 TEST_F(InputDispatcherDragTests, DragAndDrop) {
-    performDrag();
+    startDrag();
 
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -6242,7 +6451,7 @@
 }
 
 TEST_F(InputDispatcherDragTests, StylusDragAndDrop) {
-    performStylusDrag();
+    startDrag(true, AINPUT_SOURCE_STYLUS);
 
     // Move on window and keep button pressed.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -6289,7 +6498,7 @@
 }
 
 TEST_F(InputDispatcherDragTests, DragAndDropOnInvalidWindow) {
-    performDrag();
+    startDrag();
 
     // Set second window invisible.
     mSecondWindow->setVisible(false);
@@ -6325,6 +6534,9 @@
 }
 
 TEST_F(InputDispatcherDragTests, NoDragAndDropWhenMultiFingers) {
+    // Ensure window could track pointerIds if it didn't support split touch.
+    mWindow->setPreventSplitting(true);
+
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {50, 50}))
@@ -6345,7 +6557,7 @@
     mWindow->consumeMotionPointerDown(1 /* pointerIndex */);
 
     // Should not perform drag and drop when window has multi fingers.
-    ASSERT_FALSE(performDrag(false));
+    ASSERT_FALSE(startDrag(false));
 }
 
 TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) {
@@ -6373,7 +6585,7 @@
     mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
 
     // Perform drag and drop from first window.
-    ASSERT_TRUE(performDrag(false));
+    ASSERT_TRUE(startDrag(false));
 
     // Move on window.
     const MotionEvent secondFingerMoveEvent =
@@ -6408,7 +6620,7 @@
 }
 
 TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) {
-    performDrag();
+    startDrag();
 
     // Update window of second display.
     sp<FakeWindowHandle> windowInSecondary =
@@ -6459,6 +6671,55 @@
     mSecondWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherDragTests, MouseDragAndDrop) {
+    startDrag(true, AINPUT_SOURCE_MOUSE);
+    // Move on window.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                        .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                        .pointer(PointerBuilder(MOUSE_POINTER_ID,
+                                                                AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(50)
+                                                         .y(50))
+                                        .build()))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeDragEvent(false, 50, 50);
+    mSecondWindow->assertNoEvents();
+
+    // Move to another window.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                        .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                        .pointer(PointerBuilder(MOUSE_POINTER_ID,
+                                                                AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(150)
+                                                         .y(50))
+                                        .build()))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeDragEvent(true, 150, 50);
+    mSecondWindow->consumeDragEvent(false, 50, 50);
+
+    // drop to another window.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
+                                        .buttonState(0)
+                                        .pointer(PointerBuilder(MOUSE_POINTER_ID,
+                                                                AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(150)
+                                                         .y(50))
+                                        .build()))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    mFakePolicy->assertDropTargetEquals(mSecondWindow->getToken());
+    mWindow->assertNoEvents();
+    mSecondWindow->assertNoEvents();
+}
+
 class InputDispatcherDropInputFeatureTest : public InputDispatcherTest {};
 
 TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) {
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index a26a0bc..8a97901 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -56,7 +56,9 @@
 
 // Arbitrary display properties.
 static constexpr int32_t DISPLAY_ID = 0;
+static const std::string DISPLAY_UNIQUE_ID = "local:1";
 static constexpr int32_t SECONDARY_DISPLAY_ID = DISPLAY_ID + 1;
+static const std::string SECONDARY_DISPLAY_UNIQUE_ID = "local:2";
 static constexpr int32_t DISPLAY_WIDTH = 480;
 static constexpr int32_t DISPLAY_HEIGHT = 800;
 static constexpr int32_t VIRTUAL_DISPLAY_ID = 1;
@@ -91,6 +93,31 @@
 // Error tolerance for floating point assertions.
 static const float EPSILON = 0.001f;
 
+using ::testing::AllOf;
+
+MATCHER_P(WithAction, action, "InputEvent with specified action") {
+    return arg.action == action;
+}
+
+MATCHER_P(WithSource, source, "InputEvent with specified source") {
+    return arg.source == source;
+}
+
+MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") {
+    return arg.displayId == displayId;
+}
+
+MATCHER_P2(WithCoords, x, y, "MotionEvent with specified action") {
+    return arg.pointerCoords[0].getX() == x && arg.pointerCoords[0].getY();
+}
+
+MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") {
+    const auto argToolType = arg.pointerProperties[0].toolType;
+    *result_listener << "expected tool type " << motionToolTypeToString(toolType) << ", but got "
+                     << motionToolTypeToString(argToolType);
+    return argToolType == toolType;
+}
+
 template<typename T>
 static inline T min(T a, T b) {
     return a < b ? a : b;
@@ -2137,6 +2164,8 @@
 
     ~FakePeripheralController() override {}
 
+    int32_t getEventHubId() const { return getDeviceContext().getEventHubId(); }
+
     void populateDeviceInfo(InputDeviceInfo* deviceInfo) override {}
 
     void dump(std::string& dump) override {}
@@ -2170,6 +2199,7 @@
     InputDeviceContext& mDeviceContext;
     inline int32_t getDeviceId() { return mDeviceContext.getId(); }
     inline InputDeviceContext& getDeviceContext() { return mDeviceContext; }
+    inline InputDeviceContext& getDeviceContext() const { return mDeviceContext; }
 };
 
 TEST_F(InputReaderTest, BatteryGetCapacity) {
@@ -2872,7 +2902,6 @@
 
     // Device should be disabled because it is associated with a specific display, but the
     // corresponding display is not found.
-    const std::string DISPLAY_UNIQUE_ID = "displayUniqueId";
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
     mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                        InputReaderConfiguration::CHANGE_DISPLAY_INFO);
@@ -2903,7 +2932,6 @@
     mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD);
     mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
 
-    const std::string DISPLAY_UNIQUE_ID = "displayUniqueId";
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
     mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
                                     DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
@@ -2913,6 +2941,21 @@
     ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId());
 }
 
+/**
+ * This test reproduces a crash caused by a dangling reference that remains after device is added
+ * and removed. The reference is accessed in InputDevice::dump(..);
+ */
+TEST_F(InputDeviceTest, DumpDoesNotCrash) {
+    constexpr int32_t TEST_EVENTHUB_ID = 10;
+    mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY);
+
+    InputDevice device(mReader->getContext(), 1 /*id*/, 2 /*generation*/, {} /*identifier*/);
+    device.addEventHubDevice(TEST_EVENTHUB_ID, true /*populateMappers*/);
+    device.removeEventHubDevice(TEST_EVENTHUB_ID);
+    std::string dumpStr, eventHubDevStr;
+    device.dump(dumpStr, eventHubDevStr);
+}
+
 // --- InputMapperTest ---
 
 class InputMapperTest : public testing::Test {
@@ -2938,6 +2981,8 @@
         mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
                                                             *mFakeListener);
         mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes);
+        // Consume the device reset notification generated when adding a new device.
+        mFakeListener->assertNotifyDeviceResetWasCalled();
     }
 
     void SetUp() override {
@@ -2962,6 +3007,8 @@
             mReader->loopOnce();
         }
         mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes);
+        // Loop the reader to flush the input listener queue.
+        mReader->loopOnce();
     }
 
     std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
@@ -2985,6 +3032,8 @@
         configureDevice(0);
         mDevice->reset(ARBITRARY_TIME);
         mapper.reset(ARBITRARY_TIME);
+        // Loop the reader to flush the input listener queue.
+        mReader->loopOnce();
         return mapper;
     }
 
@@ -3010,6 +3059,7 @@
         event.code = code;
         event.value = value;
         mapper.process(&event);
+        // Loop the reader to flush the input listener queue.
         mReader->loopOnce();
     }
 
@@ -4181,10 +4231,14 @@
                             int32_t rotatedX, int32_t rotatedY);
 
     void prepareDisplay(int32_t orientation) {
-        const std::string uniqueId = "local:0";
-        const ViewportType viewportType = ViewportType::INTERNAL;
-        setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                orientation, uniqueId, NO_PORT, viewportType);
+        setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation,
+                                     DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+    }
+
+    void prepareSecondaryDisplay() {
+        setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+                                     DISPLAY_ORIENTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT,
+                                     ViewportType::EXTERNAL);
     }
 
     static void assertCursorPointerCoords(const PointerCoords& coords, float x, float y,
@@ -4461,6 +4515,7 @@
 }
 
 TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldNotRotateMotions) {
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
     addConfigurationProperty("cursor.mode", "navigation");
     // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not
     // need to be rotated.
@@ -4479,11 +4534,13 @@
 }
 
 TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotions) {
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
     addConfigurationProperty("cursor.mode", "navigation");
     // Since InputReader works in the un-rotated coordinate space, only devices that are not
     // orientation-aware are affected by display rotation.
     CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
 
+    clearViewports();
     prepareDisplay(DISPLAY_ORIENTATION_0);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1,  1));
@@ -4494,6 +4551,7 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0, -1,  0));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1,  1));
 
+    clearViewports();
     prepareDisplay(DISPLAY_ORIENTATION_90);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1, -1,  0));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1, -1,  1));
@@ -4504,6 +4562,7 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  0, -1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1, -1));
 
+    clearViewports();
     prepareDisplay(DISPLAY_ORIENTATION_180);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0, -1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1, -1, -1));
@@ -4514,6 +4573,7 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  0,  1,  0));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1,  1, -1));
 
+    clearViewports();
     prepareDisplay(DISPLAY_ORIENTATION_270);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  1,  0));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1, -1));
@@ -4907,7 +4967,6 @@
     ASSERT_TRUE(mReader->getContext()->getGeneration() != generation);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
     ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
@@ -4969,33 +5028,118 @@
     ASSERT_EQ(20, args.pointerCoords[0].getY());
 }
 
-TEST_F(CursorInputMapperTest, Process_ShouldHandleDisplayId) {
+TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) {
+    addConfigurationProperty("cursor.mode", "pointer");
     CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
 
-    // Setup for second display.
-    constexpr int32_t SECOND_DISPLAY_ID = 1;
-    const std::string SECOND_DISPLAY_UNIQUE_ID = "local:1";
-    mFakePolicy->addDisplayViewport(SECOND_DISPLAY_ID, 800, 480, DISPLAY_ORIENTATION_0,
-                                    true /*isActive*/, SECOND_DISPLAY_UNIQUE_ID, NO_PORT,
-                                    ViewportType::EXTERNAL);
-    mFakePolicy->setDefaultPointerDisplayId(SECOND_DISPLAY_ID);
-    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    NotifyDeviceResetArgs resetArgs;
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
+    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
 
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-    mFakePointerController->setButtonState(0);
+    // Ensure the display is rotated.
+    prepareDisplay(DISPLAY_ORIENTATION_90);
 
     NotifyMotionArgs args;
+
+    // Verify that the coordinates are rotated.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
-            110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+    ASSERT_EQ(-20, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X));
+    ASSERT_EQ(10, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y));
+
+    // Enable Pointer Capture.
+    mFakePolicy->setPointerCapture(true);
+    configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+    NotifyPointerCaptureChangedArgs captureArgs;
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
+    ASSERT_TRUE(captureArgs.request.enable);
+
+    // Move and verify rotation is not applied.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+    ASSERT_EQ(10, args.pointerCoords[0].getX());
+    ASSERT_EQ(20, args.pointerCoords[0].getY());
+}
+
+TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) {
+    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+
+    // Set up the default display.
+    prepareDisplay(DISPLAY_ORIENTATION_90);
+
+    // Set up the secondary display as the display on which the pointer should be shown.
+    // The InputDevice is not associated with any display.
+    prepareSecondaryDisplay();
+    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+
+    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
+    mFakePointerController->setPosition(100, 200);
+    mFakePointerController->setButtonState(0);
+
+    // Ensure input events are generated for the secondary display.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+                  WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f))));
     ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f));
-    ASSERT_EQ(SECOND_DISPLAY_ID, args.displayId);
+}
+
+TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) {
+    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+
+    // Set up the default display.
+    prepareDisplay(DISPLAY_ORIENTATION_90);
+
+    // Set up the secondary display as the display on which the pointer should be shown,
+    // and associate the InputDevice with the secondary display.
+    prepareSecondaryDisplay();
+    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+
+    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
+    mFakePointerController->setPosition(100, 200);
+    mFakePointerController->setButtonState(0);
+
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+                  WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f))));
+    ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f));
+}
+
+TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) {
+    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+
+    // Set up the default display as the display on which the pointer should be shown.
+    prepareDisplay(DISPLAY_ORIENTATION_90);
+    mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+
+    // Associate the InputDevice with the secondary display.
+    prepareSecondaryDisplay();
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+
+    // The mapper should not generate any events because it is associated with a display that is
+    // different from the pointer display.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 }
 
 // --- TouchInputMapperTest ---
@@ -6648,6 +6792,171 @@
             toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
 }
 
+TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
+    prepareAxes(POSITION | PRESSURE);
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    NotifyMotionArgs motionArgs;
+
+    // Set the initial state for the touch pointer.
+    mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 100);
+    mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 200);
+    mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_PRESSURE, RAW_PRESSURE_MAX);
+    mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1);
+
+    // Reset the mapper. When the mapper is reset, we expect it to attempt to recreate the touch
+    // state by reading the current axis values.
+    mapper.reset(ARBITRARY_TIME);
+
+    // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use
+    // the recreated touch state to generate a down event.
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) {
+    std::shared_ptr<FakePointerController> fakePointerController =
+            std::make_shared<FakePointerController>();
+    fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
+    fakePointerController->setPosition(100, 200);
+    fakePointerController->setButtonState(0);
+    mFakePolicy->setPointerController(fakePointerController);
+
+    addConfigurationProperty("touch.deviceType", "pointer");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
+    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
+    prepareAxes(POSITION);
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+    // Start a stylus gesture.
+    processKey(mapper, BTN_TOOL_PEN, 1);
+    processDown(mapper, 100, 200);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    // TODO(b/257078296): Pointer mode generates extra event.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // Make the viewport inactive. This will put the device in disabled mode, and the ongoing stylus
+    // gesture should be disabled.
+    auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    viewport->isActive = false;
+    mFakePolicy->updateViewport(*viewport);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithAction(AMOTION_EVENT_ACTION_CANCEL),
+                  WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    // TODO(b/257078296): Pointer mode generates extra event.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithAction(AMOTION_EVENT_ACTION_CANCEL),
+                  WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest,
+       Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
+    prepareAxes(POSITION);
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    NotifyMotionArgs motionArgs;
+
+    // Down.
+    processDown(mapper, 100, 200);
+    processSync(mapper);
+
+    // We should receive a down event
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+
+    // Change display id
+    clearViewports();
+    prepareSecondaryDisplay(ViewportType::INTERNAL);
+
+    // We should receive a cancel event
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action);
+    // Then receive reset called
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest,
+       Process_WhenViewportActiveStatusChanged_TouchIsCanceledAndDeviceIsReset) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
+    prepareAxes(POSITION);
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+    NotifyMotionArgs motionArgs;
+
+    // Start a new gesture.
+    processDown(mapper, 100, 200);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+
+    // Make the viewport inactive. This will put the device in disabled mode.
+    auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    viewport->isActive = false;
+    mFakePolicy->updateViewport(*viewport);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+
+    // We should receive a cancel event for the ongoing gesture.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action);
+    // Then we should be notified that the device was reset.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+    // No events are generated while the viewport is inactive.
+    processMove(mapper, 101, 201);
+    processSync(mapper);
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // Start a new gesture while the viewport is still inactive.
+    processDown(mapper, 300, 400);
+    mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 300);
+    mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 400);
+    mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1);
+    processSync(mapper);
+
+    // Make the viewport active again. The device should resume processing events.
+    viewport->isActive = true;
+    mFakePolicy->updateViewport(*viewport);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+
+    // The device is reset because it changes back to direct mode, without generating any events.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // In the next sync, the touch state that was recreated when the device was reset is reported.
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+
+    // No more events.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled());
+}
+
 // --- TouchDisplayProjectionTest ---
 
 class TouchDisplayProjectionTest : public SingleTouchInputMapperTest {
@@ -8505,27 +8814,27 @@
     ASSERT_TRUE(mFakePolicy->updateViewport(displayViewport));
     configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
 
-    // Finger move
+    // The ongoing touch should be canceled immediately
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action);
+
+    // Finger move is ignored
     x += 10, y += 10;
     processPosition(mapper, x, y);
     processSync(mapper);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 
     // Reactivate display viewport
     displayViewport.isActive = true;
     ASSERT_TRUE(mFakePolicy->updateViewport(displayViewport));
     configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
 
-    // Finger move again
+    // Finger move again starts new gesture
     x += 10, y += 10;
     processPosition(mapper, x, y);
     processSync(mapper);
-
-    // Gesture is aborted, so events after display is activated won't be dispatched until there is
-    // no pointer on the touch device.
-    mFakeListener->assertNotifyMotionWasNotCalled();
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
 }
 
 TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) {
@@ -8683,6 +8992,10 @@
         // window's coordinate space.
         frames[0].rotate(getInverseRotation(orientation));
         ASSERT_EQ(frames, motionArgs.videoFrames);
+
+        // Release finger.
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     }
 }
 
@@ -9161,6 +9474,80 @@
     ASSERT_EQ(uint32_t(1), motionArgs.pointerCount);
 }
 
+TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION | ID | SLOT | PRESSURE);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+    NotifyMotionArgs motionArgs;
+
+    // First finger down.
+    processId(mapper, FIRST_TRACKING_ID);
+    processPosition(mapper, 100, 200);
+    processPressure(mapper, RAW_PRESSURE_MAX);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+
+    // Second finger down.
+    processSlot(mapper, SECOND_SLOT);
+    processId(mapper, SECOND_TRACKING_ID);
+    processPosition(mapper, 300, 400);
+    processPressure(mapper, RAW_PRESSURE_MAX);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(
+            mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
+
+    // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be
+    // preserved. Resetting should not generate any events.
+    mapper.reset(ARBITRARY_TIME);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use
+    // the existing touch state to generate a down event.
+    processPosition(mapper, 301, 302);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareAxes(POSITION | ID | SLOT | PRESSURE);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+    NotifyMotionArgs motionArgs;
+
+    // First finger touches down and releases.
+    processId(mapper, FIRST_TRACKING_ID);
+    processPosition(mapper, 100, 200);
+    processPressure(mapper, RAW_PRESSURE_MAX);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+    processId(mapper, INVALID_TRACKING_ID);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(
+            mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+
+    // Reset the mapper. When the mapper is reset, we expect it to restore the latest
+    // raw state where no pointers are down.
+    mapper.reset(ARBITRARY_TIME);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // Send an empty sync frame. Since there are no pointers, no events are generated.
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
 // --- MultiTouchInputMapperTest_ExternalDevice ---
 
 class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest {
diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp
index 6a26c63..57b382c 100644
--- a/services/inputflinger/tests/TestInputListener.cpp
+++ b/services/inputflinger/tests/TestInputListener.cpp
@@ -69,6 +69,13 @@
                                            "Expected notifyMotion() to have been called."));
 }
 
+void TestInputListener::assertNotifyMotionWasCalled(
+        const ::testing::Matcher<NotifyMotionArgs>& matcher) {
+    NotifyMotionArgs outEventArgs;
+    ASSERT_NO_FATAL_FAILURE(assertNotifyMotionWasCalled(&outEventArgs));
+    ASSERT_THAT(outEventArgs, matcher);
+}
+
 void TestInputListener::assertNotifyMotionWasNotCalled() {
     ASSERT_NO_FATAL_FAILURE(
             assertNotCalled<NotifyMotionArgs>("notifyMotion() should not be called."));
diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h
index 626cdfc..0bdfc6b 100644
--- a/services/inputflinger/tests/TestInputListener.h
+++ b/services/inputflinger/tests/TestInputListener.h
@@ -18,6 +18,7 @@
 #define _UI_TEST_INPUT_LISTENER_H
 
 #include <android-base/thread_annotations.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include "InputListener.h"
 
@@ -48,6 +49,8 @@
 
     void assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs = nullptr);
 
+    void assertNotifyMotionWasCalled(const ::testing::Matcher<NotifyMotionArgs>& matcher);
+
     void assertNotifyMotionWasNotCalled();
 
     void assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs = nullptr);
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
index 0062f42..29fa001 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -16,13 +16,17 @@
 
 #include "../UnwantedInteractionBlocker.h"
 #include <android-base/silent_death_test.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <gui/constants.h>
 #include <linux/input.h>
 #include <thread>
+#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
 
 #include "TestInputListener.h"
 
+using ::testing::AllOf;
+
 namespace android {
 
 constexpr int32_t DEVICE_ID = 3;
@@ -30,6 +34,8 @@
 constexpr int32_t Y_RESOLUTION = 11;
 constexpr int32_t MAJOR_RESOLUTION = 1;
 
+const nsecs_t RESAMPLE_PERIOD = ::ui::kResamplePeriod.InNanoseconds();
+
 constexpr int POINTER_0_DOWN =
         AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 constexpr int POINTER_1_DOWN =
@@ -47,6 +53,27 @@
 constexpr int UP = AMOTION_EVENT_ACTION_UP;
 constexpr int CANCEL = AMOTION_EVENT_ACTION_CANCEL;
 
+constexpr int32_t FLAG_CANCELED = AMOTION_EVENT_FLAG_CANCELED;
+
+MATCHER_P(WithAction, action, "MotionEvent with specified action") {
+    bool result = true;
+    if (action == CANCEL) {
+        result &= (arg.flags & FLAG_CANCELED) != 0;
+    }
+    result &= arg.action == action;
+    *result_listener << "expected to receive " << MotionEvent::actionToString(action)
+                     << " but received " << MotionEvent::actionToString(arg.action) << " instead.";
+    return result;
+}
+
+MATCHER_P(WithFlags, flags, "MotionEvent with specified flags") {
+    return arg.flags == flags;
+}
+
+static nsecs_t toNs(std::chrono::nanoseconds duration) {
+    return duration.count();
+}
+
 struct PointerData {
     float x;
     float y;
@@ -252,7 +279,7 @@
                                      /*newSuppressedPointerIds*/ {1});
     ASSERT_EQ(2u, result.size());
     assertArgs(result[0], POINTER_1_UP, {{0, {1, 2, 3}}, {1, {4, 5, 6}}, {2, {7, 8, 9}}});
-    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+    ASSERT_EQ(FLAG_CANCELED, result[0].flags);
     assertArgs(result[1], MOVE, {{0, {1, 2, 3}}, {2, {7, 8, 9}}});
 }
 
@@ -267,7 +294,7 @@
                                      /*newSuppressedPointerIds*/ {0});
     ASSERT_EQ(1u, result.size());
     assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}});
-    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+    ASSERT_EQ(FLAG_CANCELED, result[0].flags);
 }
 
 /**
@@ -282,7 +309,7 @@
                                      /*newSuppressedPointerIds*/ {1});
     ASSERT_EQ(1u, result.size());
     assertArgs(result[0], POINTER_1_UP, {{0, {1, 2, 3}}, {1, {4, 5, 6}}, {2, {7, 8, 9}}});
-    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+    ASSERT_EQ(FLAG_CANCELED, result[0].flags);
 }
 
 /**
@@ -297,7 +324,7 @@
                                      /*newSuppressedPointerIds*/ {0});
     ASSERT_EQ(1u, result.size());
     assertArgs(result[0], POINTER_0_UP, {{0, {1, 2, 3}}, {1, {4, 5, 6}}});
-    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+    ASSERT_EQ(FLAG_CANCELED, result[0].flags);
 }
 
 /**
@@ -312,7 +339,7 @@
                                      /*newSuppressedPointerIds*/ {0, 1});
     ASSERT_EQ(1u, result.size());
     assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}, {1, {4, 5, 6}}});
-    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+    ASSERT_EQ(FLAG_CANCELED, result[0].flags);
 }
 
 /**
@@ -328,7 +355,7 @@
                                      /*newSuppressedPointerIds*/ {0, 1});
     ASSERT_EQ(1u, result.size());
     assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}});
-    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+    ASSERT_EQ(FLAG_CANCELED, result[0].flags);
 }
 
 /**
@@ -384,7 +411,7 @@
     expected.reported_tool_type = ::ui::EventPointerType::kTouch;
     expected.stylus_button = false;
 
-    ASSERT_EQ(expected, touches[0]) << toString(touches[0]);
+    ASSERT_EQ(expected, touches[0]) << touches[0];
 }
 
 // --- UnwantedInteractionBlockerTest ---
@@ -569,6 +596,114 @@
     dumpThread.join();
 }
 
+/**
+ * Heuristic filter that's present in the palm rejection model blocks touches early if the size
+ * of the touch is large. This is an integration test that checks that this filter kicks in.
+ */
+TEST_F(UnwantedInteractionBlockerTest, HeuristicFilterWorks) {
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    // Small touch down
+    NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+    mBlocker->notifyMotion(&args1);
+    mTestListener.assertNotifyMotionWasCalled(WithAction(DOWN));
+
+    // Large touch oval on the next move
+    NotifyMotionArgs args2 =
+            generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}});
+    mBlocker->notifyMotion(&args2);
+    mTestListener.assertNotifyMotionWasCalled(WithAction(MOVE));
+
+    // Lift up the touch to force the model to decide on whether it's a palm
+    NotifyMotionArgs args3 =
+            generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
+    mBlocker->notifyMotion(&args3);
+    mTestListener.assertNotifyMotionWasCalled(WithAction(CANCEL));
+}
+
+/**
+ * Send a stylus event that would have triggered the heuristic palm detector if it were a touch
+ * event. However, since it's a stylus event, it should propagate without being canceled through
+ * the blocker.
+ * This is similar to `HeuristicFilterWorks` test, but for stylus tool.
+ */
+TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) {
+    InputDeviceInfo info = generateTestDeviceInfo();
+    info.addSource(AINPUT_SOURCE_STYLUS);
+    mBlocker->notifyInputDevicesChanged({info});
+    NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+    args1.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    mBlocker->notifyMotion(&args1);
+    mTestListener.assertNotifyMotionWasCalled(WithAction(DOWN));
+
+    // Move the stylus, setting large TOUCH_MAJOR/TOUCH_MINOR dimensions
+    NotifyMotionArgs args2 =
+            generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}});
+    args2.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    mBlocker->notifyMotion(&args2);
+    mTestListener.assertNotifyMotionWasCalled(WithAction(MOVE));
+
+    // Lift up the stylus. If it were a touch event, this would force the model to decide on whether
+    // it's a palm.
+    NotifyMotionArgs args3 =
+            generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
+    args3.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    mBlocker->notifyMotion(&args3);
+    mTestListener.assertNotifyMotionWasCalled(WithAction(UP));
+}
+
+/**
+ * Send a mixed touch and stylus event.
+ * The touch event goes first, and is a palm. The stylus event goes down after.
+ * Stylus event should continue to work even after touch is detected as a palm.
+ */
+TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) {
+    InputDeviceInfo info = generateTestDeviceInfo();
+    info.addSource(AINPUT_SOURCE_STYLUS);
+    mBlocker->notifyInputDevicesChanged({info});
+
+    // Touch down
+    NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+    mBlocker->notifyMotion(&args1);
+    mTestListener.assertNotifyMotionWasCalled(WithAction(DOWN));
+
+    // Stylus pointer down
+    NotifyMotionArgs args2 = generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, POINTER_1_DOWN,
+                                                {{1, 2, 3}, {10, 20, 30}});
+    args2.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    mBlocker->notifyMotion(&args2);
+    mTestListener.assertNotifyMotionWasCalled(WithAction(POINTER_1_DOWN));
+
+    // Large touch oval on the next finger move
+    NotifyMotionArgs args3 = generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, MOVE,
+                                                {{1, 2, 300}, {11, 21, 30}});
+    args3.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    mBlocker->notifyMotion(&args3);
+    mTestListener.assertNotifyMotionWasCalled(WithAction(MOVE));
+
+    // Lift up the finger pointer. It should be canceled due to the heuristic filter.
+    NotifyMotionArgs args4 = generateMotionArgs(0 /*downTime*/, 3 * RESAMPLE_PERIOD, POINTER_0_UP,
+                                                {{1, 2, 300}, {11, 21, 30}});
+    args4.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    mBlocker->notifyMotion(&args4);
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithAction(POINTER_0_UP), WithFlags(FLAG_CANCELED)));
+
+    NotifyMotionArgs args5 =
+            generateMotionArgs(0 /*downTime*/, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}});
+    args5.pointerProperties[0].id = args4.pointerProperties[1].id;
+    args5.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    mBlocker->notifyMotion(&args5);
+    mTestListener.assertNotifyMotionWasCalled(WithAction(MOVE));
+
+    // Lift up the stylus pointer
+    NotifyMotionArgs args6 =
+            generateMotionArgs(0 /*downTime*/, 5 * RESAMPLE_PERIOD, UP, {{4, 5, 200}});
+    args6.pointerProperties[0].id = args4.pointerProperties[1].id;
+    args6.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    mBlocker->notifyMotion(&args6);
+    mTestListener.assertNotifyMotionWasCalled(WithAction(UP));
+}
+
 using UnwantedInteractionBlockerTestDeathTest = UnwantedInteractionBlockerTest;
 
 /**
@@ -630,138 +765,138 @@
  */
 TEST_F(PalmRejectorTest, TwoPointersAreCanceled) {
     std::vector<NotifyMotionArgs> argsList;
-    constexpr nsecs_t downTime = 255955749837000;
+    const nsecs_t downTime = toNs(0ms);
 
     mPalmRejector->processMotion(
             generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955759313000, MOVE, {{1406.0, 650.0, 52.0}}));
+            generateMotionArgs(downTime, toNs(8ms), MOVE, {{1406.0, 650.0, 52.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955766361000, MOVE, {{1429.0, 672.0, 46.0}}));
+            generateMotionArgs(downTime, toNs(16ms), MOVE, {{1429.0, 672.0, 46.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955775989000, MOVE, {{1417.0, 685.0, 41.0}}));
+            generateMotionArgs(downTime, toNs(24ms), MOVE, {{1417.0, 685.0, 41.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955775989000, POINTER_1_DOWN,
+            generateMotionArgs(downTime, toNs(32ms), POINTER_1_DOWN,
                                {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955783039000, MOVE,
+            generateMotionArgs(downTime, toNs(40ms), MOVE,
                                {{1414.0, 702.0, 41.0}, {1059.0, 731.0, 12.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955792536000, MOVE,
+            generateMotionArgs(downTime, toNs(48ms), MOVE,
                                {{1415.0, 719.0, 44.0}, {1060.0, 760.0, 11.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955799474000, MOVE,
+            generateMotionArgs(downTime, toNs(56ms), MOVE,
                                {{1421.0, 733.0, 42.0}, {1065.0, 769.0, 13.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955809177000, MOVE,
+            generateMotionArgs(downTime, toNs(64ms), MOVE,
                                {{1426.0, 742.0, 43.0}, {1068.0, 771.0, 13.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955816131000, MOVE,
+            generateMotionArgs(downTime, toNs(72ms), MOVE,
                                {{1430.0, 748.0, 45.0}, {1069.0, 772.0, 13.0}}));
     argsList = mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955825907000, MOVE,
+            generateMotionArgs(downTime, toNs(80ms), MOVE,
                                {{1432.0, 750.0, 44.0}, {1069.0, 772.0, 13.0}}));
     ASSERT_EQ(1u, argsList.size());
     ASSERT_EQ(0 /* No FLAG_CANCELED */, argsList[0].flags);
     argsList = mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955832736000, MOVE,
+            generateMotionArgs(downTime, toNs(88ms), MOVE,
                                {{1433.0, 751.0, 44.0}, {1070.0, 771.0, 13.0}}));
     ASSERT_EQ(2u, argsList.size());
     ASSERT_EQ(POINTER_0_UP, argsList[0].action);
-    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, argsList[0].flags);
+    ASSERT_EQ(FLAG_CANCELED, argsList[0].flags);
     ASSERT_EQ(MOVE, argsList[1].action);
     ASSERT_EQ(1u, argsList[1].pointerCount);
     ASSERT_EQ(0, argsList[1].flags);
 
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955842432000, MOVE,
+            generateMotionArgs(downTime, toNs(96ms), MOVE,
                                {{1433.0, 751.0, 42.0}, {1071.0, 770.0, 13.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955849380000, MOVE,
+            generateMotionArgs(downTime, toNs(104ms), MOVE,
                                {{1433.0, 751.0, 45.0}, {1072.0, 769.0, 13.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955859046000, MOVE,
+            generateMotionArgs(downTime, toNs(112ms), MOVE,
                                {{1433.0, 751.0, 43.0}, {1072.0, 768.0, 13.0}}));
     argsList = mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955869823000, MOVE,
+            generateMotionArgs(downTime, toNs(120ms), MOVE,
                                {{1433.0, 751.0, 45.0}, {1072.0, 767.0, 13.0}}));
     ASSERT_EQ(1u, argsList.size());
     ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, argsList[0].action);
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955875641000, MOVE,
+            generateMotionArgs(downTime, toNs(128ms), MOVE,
                                {{1433.0, 751.0, 43.0}, {1072.0, 766.0, 13.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955882693000, MOVE,
+            generateMotionArgs(downTime, toNs(136ms), MOVE,
                                {{1433.0, 750.0, 44.0}, {1072.0, 765.0, 13.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955892324000, MOVE,
+            generateMotionArgs(downTime, toNs(144ms), MOVE,
                                {{1433.0, 750.0, 42.0}, {1072.0, 763.0, 14.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955899425000, MOVE,
+            generateMotionArgs(downTime, toNs(152ms), MOVE,
                                {{1434.0, 750.0, 44.0}, {1073.0, 761.0, 14.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955909400000, MOVE,
+            generateMotionArgs(downTime, toNs(160ms), MOVE,
                                {{1435.0, 750.0, 43.0}, {1073.0, 759.0, 15.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955915885000, MOVE,
+            generateMotionArgs(downTime, toNs(168ms), MOVE,
                                {{1436.0, 750.0, 45.0}, {1074.0, 757.0, 15.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955925607000, MOVE,
+            generateMotionArgs(downTime, toNs(176ms), MOVE,
                                {{1436.0, 750.0, 44.0}, {1074.0, 755.0, 15.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955932580000, MOVE,
+            generateMotionArgs(downTime, toNs(184ms), MOVE,
                                {{1436.0, 750.0, 45.0}, {1074.0, 753.0, 15.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955942231000, MOVE,
+            generateMotionArgs(downTime, toNs(192ms), MOVE,
                                {{1436.0, 749.0, 44.0}, {1074.0, 751.0, 15.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955949204000, MOVE,
+            generateMotionArgs(downTime, toNs(200ms), MOVE,
                                {{1435.0, 748.0, 45.0}, {1074.0, 749.0, 15.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955959103000, MOVE,
+            generateMotionArgs(downTime, toNs(208ms), MOVE,
                                {{1434.0, 746.0, 44.0}, {1074.0, 747.0, 14.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955965884000, MOVE,
+            generateMotionArgs(downTime, toNs(216ms), MOVE,
                                {{1433.0, 744.0, 44.0}, {1075.0, 745.0, 14.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955975649000, MOVE,
+            generateMotionArgs(downTime, toNs(224ms), MOVE,
                                {{1431.0, 741.0, 43.0}, {1075.0, 742.0, 13.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955982537000, MOVE,
+            generateMotionArgs(downTime, toNs(232ms), MOVE,
                                {{1428.0, 738.0, 43.0}, {1076.0, 739.0, 12.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955992284000, MOVE,
+            generateMotionArgs(downTime, toNs(240ms), MOVE,
                                {{1400.0, 726.0, 54.0}, {1076.0, 739.0, 13.0}}));
     argsList = mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955999348000, POINTER_1_UP,
+            generateMotionArgs(downTime, toNs(248ms), POINTER_1_UP,
                                {{1362.0, 716.0, 55.0}, {1076.0, 739.0, 13.0}}));
     ASSERT_TRUE(argsList.empty());
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255955999348000, MOVE, {{1362.0, 716.0, 55.0}}));
+            generateMotionArgs(downTime, toNs(256ms), MOVE, {{1362.0, 716.0, 55.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255956008885000, MOVE, {{1347.0, 707.0, 54.0}}));
+            generateMotionArgs(downTime, toNs(264ms), MOVE, {{1347.0, 707.0, 54.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255956015791000, MOVE, {{1340.0, 698.0, 54.0}}));
+            generateMotionArgs(downTime, toNs(272ms), MOVE, {{1340.0, 698.0, 54.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255956025804000, MOVE, {{1338.0, 694.0, 55.0}}));
+            generateMotionArgs(downTime, toNs(280ms), MOVE, {{1338.0, 694.0, 55.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255956032314000, MOVE, {{1336.0, 690.0, 53.0}}));
+            generateMotionArgs(downTime, toNs(288ms), MOVE, {{1336.0, 690.0, 53.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255956042329000, MOVE, {{1334.0, 685.0, 47.0}}));
+            generateMotionArgs(downTime, toNs(296ms), MOVE, {{1334.0, 685.0, 47.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255956048979000, MOVE, {{1333.0, 679.0, 46.0}}));
+            generateMotionArgs(downTime, toNs(304ms), MOVE, {{1333.0, 679.0, 46.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255956058813000, MOVE, {{1332.0, 672.0, 45.0}}));
+            generateMotionArgs(downTime, toNs(312ms), MOVE, {{1332.0, 672.0, 45.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255956065592000, MOVE, {{1333.0, 666.0, 40.0}}));
+            generateMotionArgs(downTime, toNs(320ms), MOVE, {{1333.0, 666.0, 40.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255956075276000, MOVE, {{1336.0, 661.0, 24.0}}));
+            generateMotionArgs(downTime, toNs(328ms), MOVE, {{1336.0, 661.0, 24.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255956082198000, MOVE, {{1338.0, 656.0, 16.0}}));
+            generateMotionArgs(downTime, toNs(336ms), MOVE, {{1338.0, 656.0, 16.0}}));
     mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255956092059000, MOVE, {{1341.0, 649.0, 1.0}}));
+            generateMotionArgs(downTime, toNs(344ms), MOVE, {{1341.0, 649.0, 1.0}}));
     argsList = mPalmRejector->processMotion(
-            generateMotionArgs(downTime, 255956098764000, UP, {{1341.0, 649.0, 1.0}}));
+            generateMotionArgs(downTime, toNs(352ms), UP, {{1341.0, 649.0, 1.0}}));
     ASSERT_TRUE(argsList.empty());
 }
 
@@ -845,7 +980,7 @@
     ASSERT_EQ(2u, argsList.size());
     // First event - cancel pointer 1
     ASSERT_EQ(POINTER_1_UP, argsList[0].action);
-    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, argsList[0].flags);
+    ASSERT_EQ(FLAG_CANCELED, argsList[0].flags);
     // Second event - send MOVE for the remaining pointer
     ASSERT_EQ(MOVE, argsList[1].action);
     ASSERT_EQ(0, argsList[1].flags);
@@ -886,7 +1021,7 @@
     // Cancel all
     ASSERT_EQ(CANCEL, argsList[0].action);
     ASSERT_EQ(2u, argsList[0].pointerCount);
-    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, argsList[0].flags);
+    ASSERT_EQ(FLAG_CANCELED, argsList[0].flags);
 
     // Future move events are ignored
     argsList = mPalmRejector->processMotion(
@@ -932,7 +1067,7 @@
                                {{1414.0, 702.0, 41.0}, {1059.0, 731.0, 12.0}}));
     ASSERT_EQ(1u, argsList.size());
     ASSERT_EQ(CANCEL, argsList[0].action) << MotionEvent::actionToString(argsList[0].action);
-    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, argsList[0].flags);
+    ASSERT_EQ(FLAG_CANCELED, argsList[0].flags);
 
     // Future move events should not go to the listener.
     argsList = mPalmRejector->processMotion(
@@ -966,7 +1101,7 @@
                                {{1417.0, 685.0, 41.0}, {1060, 700, 10.0}}));
     ASSERT_EQ(2u, argsList.size());
     ASSERT_EQ(POINTER_1_UP, argsList[0].action);
-    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, argsList[0].flags);
+    ASSERT_EQ(FLAG_CANCELED, argsList[0].flags);
 
     ASSERT_EQ(MOVE, argsList[1].action) << MotionEvent::actionToString(argsList[1].action);
     ASSERT_EQ(0, argsList[1].flags);
diff --git a/services/sensorservice/AidlSensorHalWrapper.cpp b/services/sensorservice/AidlSensorHalWrapper.cpp
index 4d1de96..f67c610 100644
--- a/services/sensorservice/AidlSensorHalWrapper.cpp
+++ b/services/sensorservice/AidlSensorHalWrapper.cpp
@@ -726,7 +726,7 @@
             .type = type,
             .format = format,
             .size = static_cast<int32_t>(memory->size),
-            .memoryHandle = makeToAidl(memory->handle),
+            .memoryHandle = dupToAidl(memory->handle),
     };
 
     return convertToStatus(mSensors->registerDirectChannel(mem, channelHandle));
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 948692b..e0a4f03 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -1328,6 +1328,7 @@
             mSensors.getUserDebugSensors() : mSensors.getUserSensors();
     Vector<Sensor> accessibleSensorList;
 
+    resetTargetSdkVersionCache(opPackageName);
     bool isCapped = isRateCappedBasedOnPermission(opPackageName);
     for (size_t i = 0; i < initialSensorList.size(); i++) {
         Sensor sensor = initialSensorList[i];
@@ -1367,6 +1368,7 @@
     if (requestedMode != NORMAL && requestedMode != DATA_INJECTION) {
         return nullptr;
     }
+    resetTargetSdkVersionCache(opPackageName);
 
     Mutex::Autolock _l(mLock);
     // To create a client in DATA_INJECTION mode to inject data, SensorService should already be
@@ -1402,6 +1404,7 @@
 sp<ISensorEventConnection> SensorService::createSensorDirectConnection(
         const String16& opPackageName, uint32_t size, int32_t type, int32_t format,
         const native_handle *resource) {
+    resetTargetSdkVersionCache(opPackageName);
     ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
 
     // No new direct connections are allowed when sensor privacy is enabled
@@ -1643,14 +1646,6 @@
         checkWakeLockStateLocked(&connLock);
     }
 
-    {
-        Mutex::Autolock packageLock(sPackageTargetVersionLock);
-        auto iter = sPackageTargetVersion.find(c->mOpPackageName);
-        if (iter != sPackageTargetVersion.end()) {
-            sPackageTargetVersion.erase(iter);
-        }
-    }
-
     SensorDevice& dev(SensorDevice::getInstance());
     dev.notifyConnectionDestroyed(c);
 }
@@ -2091,6 +2086,14 @@
     return targetSdkVersion;
 }
 
+void SensorService::resetTargetSdkVersionCache(const String16& opPackageName) {
+    Mutex::Autolock packageLock(sPackageTargetVersionLock);
+    auto iter = sPackageTargetVersion.find(opPackageName);
+    if (iter != sPackageTargetVersion.end()) {
+        sPackageTargetVersion.erase(iter);
+    }
+}
+
 void SensorService::checkWakeLockState() {
     ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
     checkWakeLockStateLocked(&connLock);
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index 234dc9c..4ba3c51 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -377,6 +377,7 @@
             const String16& opPackageName);
     static bool hasPermissionForSensor(const Sensor& sensor);
     static int getTargetSdkVersion(const String16& opPackageName);
+    static void resetTargetSdkVersionCache(const String16& opPackageName);
     // SensorService acquires a partial wakelock for delivering events from wake up sensors. This
     // method checks whether all the events from these wake up sensors have been delivered to the
     // corresponding applications, if yes the wakelock is released.
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
index 16cb41b..5e84be1 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
@@ -56,6 +56,9 @@
     // similar requests if needed.
     virtual void createClientCompositionCache(uint32_t cacheSize) = 0;
 
+    // Sends the brightness setting to HWC
+    virtual void applyDisplayBrightness(const bool applyImmediately) = 0;
+
 protected:
     ~Display() = default;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index db2fd1b..2203639 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -27,6 +27,7 @@
 #include <compositionengine/LayerFE.h>
 #include <renderengine/LayerSettings.h>
 #include <ui/Fence.h>
+#include <ui/FenceTime.h>
 #include <ui/GraphicTypes.h>
 #include <ui/LayerStack.h>
 #include <ui/Region.h>
@@ -311,6 +312,8 @@
             const Region& flashRegion,
             std::vector<LayerFE::LayerSettings>& clientCompositionLayers) = 0;
     virtual void setExpensiveRenderingExpected(bool enabled) = 0;
+    virtual void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) = 0;
+    virtual bool isPowerHintSessionEnabled() = 0;
     virtual void cacheClientCompositionRequests(uint32_t cacheSize) = 0;
     virtual bool canPredictCompositionStrategy(const CompositionRefreshArgs&) = 0;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index 61a0e6a..33a10a3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -72,6 +72,7 @@
             const compositionengine::DisplayColorProfileCreationArgs&) override;
     void createRenderSurface(const compositionengine::RenderSurfaceCreationArgs&) override;
     void createClientCompositionCache(uint32_t cacheSize) override;
+    void applyDisplayBrightness(const bool applyImmediately) override;
 
     // Internal helpers used by chooseCompositionStrategy()
     using ChangedTypes = android::HWComposer::DeviceRequestedChanges::ChangedTypes;
@@ -89,6 +90,8 @@
     std::unique_ptr<compositionengine::OutputLayer> createOutputLayer(const sp<LayerFE>&) const;
 
 private:
+    bool isPowerHintSessionEnabled() override;
+    void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
     DisplayId mId;
     bool mIsDisconnected = false;
     Hwc2::PowerAdvisor* mPowerAdvisor = nullptr;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 31c51e6..428c19f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -140,6 +140,8 @@
           std::vector<LayerFE*> &outLayerFEs) override;
     void appendRegionFlashRequests(const Region&, std::vector<LayerFE::LayerSettings>&) override;
     void setExpensiveRenderingExpected(bool enabled) override;
+    void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
+    bool isPowerHintSessionEnabled() override;
     void dumpBase(std::string&) const;
 
     // Implemented by the final implementation for the final state it uses.
@@ -150,6 +152,8 @@
     virtual const compositionengine::CompositionEngine& getCompositionEngine() const = 0;
     virtual void dumpState(std::string& out) const = 0;
 
+    bool mustRecompose() const;
+
 private:
     void dirtyEntireOutput();
     compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const;
@@ -168,6 +172,9 @@
     std::unique_ptr<ClientCompositionRequestCache> mClientCompositionRequestCache;
     std::unique_ptr<planner::Planner> mPlanner;
     std::unique_ptr<HwcAsyncWorker> mHwComposerAsyncWorker;
+
+    // Whether the content must be recomposed this frame.
+    bool mMustRecompose = false;
 };
 
 // This template factory function standardizes the implementation details of the
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index e65aa73..8e4e9af 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -115,7 +115,7 @@
 
     // Renders the cached set with the supplied output composition state.
     void render(renderengine::RenderEngine& re, TexturePool& texturePool,
-                const OutputCompositionState& outputState);
+                const OutputCompositionState& outputState, bool deviceHandlesColorTransform);
 
     void dump(std::string& result) const;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
index 92cc484..f934cb2 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -106,7 +106,8 @@
 
     // Renders the newest cached sets with the supplied output composition state
     void renderCachedSets(const OutputCompositionState& outputState,
-                          std::optional<std::chrono::steady_clock::time_point> renderDeadline);
+                          std::optional<std::chrono::steady_clock::time_point> renderDeadline,
+                          bool deviceHandlesColorTransform);
 
     void setTexturePoolEnabled(bool enabled) { mTexturePool.setEnabled(enabled); }
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
index b7ebca6..c968df7 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
@@ -65,7 +65,8 @@
     // Rendering a pending cached set is optional: if the renderDeadline is not far enough in the
     // future then the planner may opt to skip rendering the cached set.
     void renderCachedSets(const OutputCompositionState& outputState,
-                          std::optional<std::chrono::steady_clock::time_point> renderDeadline);
+                          std::optional<std::chrono::steady_clock::time_point> renderDeadline,
+                          bool deviceHandlesColorTransform);
 
     void setTexturePoolEnabled(bool enabled) { mFlattener.setTexturePoolEnabled(enabled); }
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
index 72e6f3b..7e99ec2 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
@@ -41,6 +41,7 @@
     MOCK_METHOD1(createDisplayColorProfile, void(const DisplayColorProfileCreationArgs&));
     MOCK_METHOD1(createRenderSurface, void(const RenderSurfaceCreationArgs&));
     MOCK_METHOD1(createClientCompositionCache, void(uint32_t));
+    MOCK_METHOD1(applyDisplayBrightness, void(const bool));
     MOCK_METHOD1(setPredictCompositionStrategy, void(bool));
 };
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index cb9fbad..2a04949 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -133,6 +133,8 @@
     MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
     MOCK_METHOD1(setPredictCompositionStrategy, void(bool));
     MOCK_METHOD1(setTreat170mAsSrgb, void(bool));
+    MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence));
+    MOCK_METHOD(bool, isPowerHintSessionEnabled, ());
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index b79b46b..163d9a3 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -203,6 +203,24 @@
     setReleasedLayers(std::move(releasedLayers));
 }
 
+void Display::applyDisplayBrightness(const bool applyImmediately) {
+    auto& hwc = getCompositionEngine().getHwComposer();
+    const auto halDisplayId = HalDisplayId::tryCast(*getDisplayId());
+    if (const auto physicalDisplayId = PhysicalDisplayId::tryCast(*halDisplayId);
+        physicalDisplayId && getState().displayBrightness) {
+        const status_t result =
+                hwc.setDisplayBrightness(*physicalDisplayId, *getState().displayBrightness,
+                                         getState().displayBrightnessNits,
+                                         Hwc2::Composer::DisplayBrightnessOptions{
+                                                 .applyImmediately = applyImmediately})
+                        .get();
+        ALOGE_IF(result != NO_ERROR, "setDisplayBrightness failed for %s: %d, (%s)",
+                 getName().c_str(), result, strerror(-result));
+    }
+    // Clear out the display brightness now that it's been communicated to composer.
+    editState().displayBrightness.reset();
+}
+
 void Display::beginFrame() {
     Output::beginFrame();
 
@@ -212,20 +230,7 @@
         return;
     }
 
-    auto& hwc = getCompositionEngine().getHwComposer();
-    if (const auto physicalDisplayId = PhysicalDisplayId::tryCast(*halDisplayId);
-        physicalDisplayId && getState().displayBrightness) {
-        const status_t result =
-                hwc.setDisplayBrightness(*physicalDisplayId, *getState().displayBrightness,
-                                         getState().displayBrightnessNits,
-                                         Hwc2::Composer::DisplayBrightnessOptions{
-                                                 .applyImmediately = false})
-                        .get();
-        ALOGE_IF(result != NO_ERROR, "setDisplayBrightness failed for %s: %d, (%s)",
-                 getName().c_str(), result, strerror(-result));
-    }
-    // Clear out the display brightness now that it's been communicated to composer.
-    editState().displayBrightness.reset();
+    applyDisplayBrightness(false);
 }
 
 bool Display::chooseCompositionStrategy(
@@ -243,11 +248,14 @@
         return false;
     }
 
+    const nsecs_t startTime = systemTime();
+
     // Get any composition changes requested by the HWC device, and apply them.
     std::optional<android::HWComposer::DeviceRequestedChanges> changes;
     auto& hwc = getCompositionEngine().getHwComposer();
+    const bool requiresClientComposition = anyLayersRequireClientComposition();
     if (status_t result =
-                hwc.getDeviceCompositionChanges(*halDisplayId, anyLayersRequireClientComposition(),
+                hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
                                                 getState().earliestPresentTime,
                                                 getState().previousPresentFence,
                                                 getState().expectedPresentTime, outChanges);
@@ -257,6 +265,11 @@
         return false;
     }
 
+    if (isPowerHintSessionEnabled()) {
+        mPowerAdvisor->setHwcValidateTiming(mId, startTime, systemTime());
+        mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition);
+    }
+
     return true;
 }
 
@@ -356,9 +369,24 @@
     }
 
     auto& hwc = getCompositionEngine().getHwComposer();
+
+    const nsecs_t startTime = systemTime();
+
+    if (isPowerHintSessionEnabled()) {
+        if (!getCompositionEngine().getHwComposer().getComposer()->isSupported(
+                    Hwc2::Composer::OptionalFeature::ExpectedPresentTime) &&
+            getState().previousPresentFence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) {
+            mPowerAdvisor->setHwcPresentDelayedTime(mId, getState().earliestPresentTime);
+        }
+    }
+
     hwc.presentAndGetReleaseFences(*halDisplayIdOpt, getState().earliestPresentTime,
                                    getState().previousPresentFence);
 
+    if (isPowerHintSessionEnabled()) {
+        mPowerAdvisor->setHwcPresentTiming(mId, startTime, systemTime());
+    }
+
     fences.presentFence = hwc.getPresentFence(*halDisplayIdOpt);
 
     // TODO(b/121291683): Change HWComposer call to return entire map
@@ -384,18 +412,33 @@
     }
 }
 
+bool Display::isPowerHintSessionEnabled() {
+    return mPowerAdvisor != nullptr && mPowerAdvisor->usePowerHintSession();
+}
+
+void Display::setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) {
+    mPowerAdvisor->setGpuFenceTime(mId, std::move(gpuFence));
+}
+
 void Display::finishFrame(const compositionengine::CompositionRefreshArgs& refreshArgs,
                           GpuCompositionResult&& result) {
     // We only need to actually compose the display if:
     // 1) It is being handled by hardware composer, which may need this to
     //    keep its virtual display state machine in sync, or
     // 2) There is work to be done (the dirty region isn't empty)
-    if (GpuVirtualDisplayId::tryCast(mId) && getDirtyRegion().isEmpty()) {
+    if (GpuVirtualDisplayId::tryCast(mId) && !mustRecompose()) {
         ALOGV("Skipping display composition");
         return;
     }
 
     impl::Output::finishFrame(refreshArgs, std::move(result));
+
+    if (isPowerHintSessionEnabled()) {
+        auto& hwc = getCompositionEngine().getHwComposer();
+        if (auto halDisplayId = HalDisplayId::tryCast(mId)) {
+            mPowerAdvisor->setSkippedValidate(mId, hwc.getValidateSkipped(*halDisplayId));
+        }
+    }
 }
 
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index f360504..d0c5803 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -586,8 +586,29 @@
     // Remove the transparent area from the visible region
     if (!layerFEState->isOpaque) {
         if (tr.preserveRects()) {
-            // transform the transparent region
-            transparentRegion = tr.transform(layerFEState->transparentRegionHint);
+            // Clip the transparent region to geomLayerBounds first
+            // The transparent region may be influenced by applications, for
+            // instance, by overriding ViewGroup#gatherTransparentRegion with a
+            // custom view. Once the layer stack -> display mapping is known, we
+            // must guard against very wrong inputs to prevent underflow or
+            // overflow errors. We do this here by constraining the transparent
+            // region to be within the pre-transform layer bounds, since the
+            // layer bounds are expected to play nicely with the full
+            // transform.
+            const Region clippedTransparentRegionHint =
+                    layerFEState->transparentRegionHint.intersect(
+                            Rect(layerFEState->geomLayerBounds));
+
+            if (clippedTransparentRegionHint.isEmpty()) {
+                if (!layerFEState->transparentRegionHint.isEmpty()) {
+                    ALOGD("Layer: %s had an out of bounds transparent region",
+                          layerFE->getDebugName());
+                    layerFEState->transparentRegionHint.dump("transparentRegionHint");
+                }
+                transparentRegion.clear();
+            } else {
+                transparentRegion = tr.transform(clippedTransparentRegionHint);
+            }
         } else {
             // transformation too complex, can't do the
             // transparent region optimization.
@@ -956,17 +977,17 @@
     //   frame, then nothing more until we get new layers.
     // - When a display is created with a private layer stack, we won't
     //   emit any black frames until a layer is added to the layer stack.
-    const bool mustRecompose = dirty && !(empty && wasEmpty);
+    mMustRecompose = dirty && !(empty && wasEmpty);
 
     const char flagPrefix[] = {'-', '+'};
     static_cast<void>(flagPrefix);
-    ALOGV_IF("%s: %s composition for %s (%cdirty %cempty %cwasEmpty)", __FUNCTION__,
-             mustRecompose ? "doing" : "skipping", getName().c_str(), flagPrefix[dirty],
-             flagPrefix[empty], flagPrefix[wasEmpty]);
+    ALOGV("%s: %s composition for %s (%cdirty %cempty %cwasEmpty)", __func__,
+          mMustRecompose ? "doing" : "skipping", getName().c_str(), flagPrefix[dirty],
+          flagPrefix[empty], flagPrefix[wasEmpty]);
 
-    mRenderSurface->beginFrame(mustRecompose);
+    mRenderSurface->beginFrame(mMustRecompose);
 
-    if (mustRecompose) {
+    if (mMustRecompose) {
         outputState.lastCompositionHadVisibleLayers = !empty;
     }
 }
@@ -1099,6 +1120,10 @@
         return;
     }
 
+    if (isPowerHintSessionEnabled()) {
+        // get fence end time to know when gpu is complete in display
+        setHintSessionGpuFence(std::make_unique<FenceTime>(new Fence(dup(optReadyFence->get()))));
+    }
     // swap buffers (presentation)
     mRenderSurface->queueBuffer(std::move(*optReadyFence));
 }
@@ -1404,6 +1429,14 @@
     // The base class does nothing with this call.
 }
 
+void Output::setHintSessionGpuFence(std::unique_ptr<FenceTime>&&) {
+    // The base class does nothing with this call.
+}
+
+bool Output::isPowerHintSessionEnabled() {
+    return false;
+}
+
 void Output::postFramebuffer() {
     ATRACE_CALL();
     ALOGV(__FUNCTION__);
@@ -1461,7 +1494,8 @@
 
 void Output::renderCachedSets(const CompositionRefreshArgs& refreshArgs) {
     if (mPlanner) {
-        mPlanner->renderCachedSets(getState(), refreshArgs.scheduledFrameTime);
+        mPlanner->renderCachedSets(getState(), refreshArgs.scheduledFrameTime,
+                                   getState().usesDeviceComposition || getSkipColorTransform());
     }
 }
 
@@ -1556,5 +1590,9 @@
     mRenderSurface->prepareFrame(state.usesClientComposition, state.usesDeviceComposition);
 }
 
+bool Output::mustRecompose() const {
+    return mMustRecompose;
+}
+
 } // namespace impl
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 641b806..d6f02ee 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -159,7 +159,8 @@
 }
 
 void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& texturePool,
-                       const OutputCompositionState& outputState) {
+                       const OutputCompositionState& outputState,
+                       bool deviceHandlesColorTransform) {
     ATRACE_CALL();
     const Rect& viewport = outputState.layerStackSpace.getContent();
     const ui::Dataspace& outputDataspace = outputState.dataspace;
@@ -170,6 +171,8 @@
             .physicalDisplay = outputState.framebufferSpace.getContent(),
             .clip = viewport,
             .outputDataspace = outputDataspace,
+            .colorTransform = outputState.colorTransformMatrix,
+            .deviceHandlesColorTransform = deviceHandlesColorTransform,
             .orientation = orientation,
             .targetLuminanceNits = outputState.displayBrightnessNits,
     };
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 1062b70..9175dd0 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -99,7 +99,8 @@
 
 void Flattener::renderCachedSets(
         const OutputCompositionState& outputState,
-        std::optional<std::chrono::steady_clock::time_point> renderDeadline) {
+        std::optional<std::chrono::steady_clock::time_point> renderDeadline,
+        bool deviceHandlesColorTransform) {
     ATRACE_CALL();
 
     if (!mNewCachedSet) {
@@ -136,7 +137,7 @@
         }
     }
 
-    mNewCachedSet->render(mRenderEngine, mTexturePool, outputState);
+    mNewCachedSet->render(mRenderEngine, mTexturePool, outputState, deviceHandlesColorTransform);
 }
 
 void Flattener::dumpLayers(std::string& result) const {
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
index c8413eb..54133d9 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -201,11 +201,11 @@
                             finalPlan);
 }
 
-void Planner::renderCachedSets(
-        const OutputCompositionState& outputState,
-        std::optional<std::chrono::steady_clock::time_point> renderDeadline) {
+void Planner::renderCachedSets(const OutputCompositionState& outputState,
+                               std::optional<std::chrono::steady_clock::time_point> renderDeadline,
+                               bool deviceHandlesColorTransform) {
     ATRACE_CALL();
-    mFlattener.renderCachedSets(outputState, renderDeadline);
+    mFlattener.renderCachedSets(outputState, renderDeadline, deviceHandlesColorTransform);
 }
 
 void Planner::dump(const Vector<String16>& args, std::string& result) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 0e5a7b6..5369642 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -169,6 +169,7 @@
         EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
         EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
         EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
+        EXPECT_CALL(mPowerAdvisor, usePowerHintSession()).WillRepeatedly(Return(false));
     }
 
     DisplayCreationArgs getDisplayCreationArgsForPhysicalDisplay() {
@@ -970,16 +971,40 @@
 
     // We expect no calls to queueBuffer if composition was skipped.
     EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(0);
+    EXPECT_CALL(*renderSurface, beginFrame(false));
 
     gpuDisplay->editState().isEnabled = true;
     gpuDisplay->editState().usesClientComposition = false;
     gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1));
     gpuDisplay->editState().dirtyRegion = Region::INVALID_REGION;
+    gpuDisplay->editState().lastCompositionHadVisibleLayers = true;
 
+    gpuDisplay->beginFrame();
     gpuDisplay->finishFrame({}, std::move(mResultWithoutBuffer));
 }
 
-TEST_F(DisplayFinishFrameTest, performsCompositionIfDirty) {
+TEST_F(DisplayFinishFrameTest, skipsCompositionIfEmpty) {
+    auto args = getDisplayCreationArgsForGpuVirtualDisplay();
+    std::shared_ptr<impl::Display> gpuDisplay = impl::createDisplay(mCompositionEngine, args);
+
+    mock::RenderSurface* renderSurface = new StrictMock<mock::RenderSurface>();
+    gpuDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface));
+
+    // We expect no calls to queueBuffer if composition was skipped.
+    EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(0);
+    EXPECT_CALL(*renderSurface, beginFrame(false));
+
+    gpuDisplay->editState().isEnabled = true;
+    gpuDisplay->editState().usesClientComposition = false;
+    gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1));
+    gpuDisplay->editState().dirtyRegion = Region(Rect(0, 0, 1, 1));
+    gpuDisplay->editState().lastCompositionHadVisibleLayers = false;
+
+    gpuDisplay->beginFrame();
+    gpuDisplay->finishFrame({}, std::move(mResultWithoutBuffer));
+}
+
+TEST_F(DisplayFinishFrameTest, performsCompositionIfDirtyAndNotEmpty) {
     auto args = getDisplayCreationArgsForGpuVirtualDisplay();
     std::shared_ptr<impl::Display> gpuDisplay = impl::createDisplay(mCompositionEngine, args);
 
@@ -988,11 +1013,15 @@
 
     // We expect a single call to queueBuffer when composition is not skipped.
     EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(1);
+    EXPECT_CALL(*renderSurface, beginFrame(true));
 
     gpuDisplay->editState().isEnabled = true;
     gpuDisplay->editState().usesClientComposition = false;
     gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1));
     gpuDisplay->editState().dirtyRegion = Region(Rect(0, 0, 1, 1));
+    gpuDisplay->editState().lastCompositionHadVisibleLayers = true;
+
+    gpuDisplay->beginFrame();
     gpuDisplay->finishFrame({}, std::move(mResultWithBuffer));
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 9b12b08..d7704a8 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -137,6 +137,7 @@
     MOCK_METHOD(bool, hasDisplayIdleTimerCapability, (PhysicalDisplayId), (const, override));
     MOCK_METHOD(Hwc2::AidlTransform, getPhysicalDisplayOrientation, (PhysicalDisplayId),
                 (const, override));
+    MOCK_METHOD(bool, getValidateSkipped, (HalDisplayId), (const, override));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
index 50adcfb..c8bd5e4 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
@@ -38,11 +38,33 @@
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
     MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override));
-    MOCK_METHOD(void, setTargetWorkDuration, (int64_t targetDurationNanos), (override));
-    MOCK_METHOD(void, sendActualWorkDuration, (int64_t actualDurationNanos, nsecs_t timestamp),
-                (override));
+    MOCK_METHOD(void, setTargetWorkDuration, (int64_t targetDuration), (override));
+    MOCK_METHOD(void, sendActualWorkDuration, (), (override));
+    MOCK_METHOD(void, sendPredictedWorkDuration, (), (override));
     MOCK_METHOD(void, enablePowerHint, (bool enabled), (override));
     MOCK_METHOD(bool, startPowerHintSession, (const std::vector<int32_t>& threadIds), (override));
+    MOCK_METHOD(void, setGpuFenceTime,
+                (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
+    MOCK_METHOD(void, setHwcValidateTiming,
+                (DisplayId displayId, nsecs_t valiateStartTime, nsecs_t validateEndTime),
+                (override));
+    MOCK_METHOD(void, setHwcPresentTiming,
+                (DisplayId displayId, nsecs_t presentStartTime, nsecs_t presentEndTime),
+                (override));
+    MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override));
+    MOCK_METHOD(void, setRequiresClientComposition,
+                (DisplayId displayId, bool requiresClientComposition), (override));
+    MOCK_METHOD(void, setExpectedPresentTime, (nsecs_t expectedPresentTime), (override));
+    MOCK_METHOD(void, setSfPresentTiming, (nsecs_t presentFenceTime, nsecs_t presentEndTime),
+                (override));
+    MOCK_METHOD(void, setHwcPresentDelayedTime,
+                (DisplayId displayId,
+                 std::chrono::steady_clock::time_point earliestFrameStartTime));
+    MOCK_METHOD(void, setFrameDelay, (nsecs_t frameDelayDuration), (override));
+    MOCK_METHOD(void, setCommitStart, (nsecs_t commitStartTime), (override));
+    MOCK_METHOD(void, setCompositeEnd, (nsecs_t compositeEndtime), (override));
+    MOCK_METHOD(void, setDisplays, (std::vector<DisplayId> & displayIds), (override));
+    MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (int64_t targetDuration), (override));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 063726b..cf12890 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -1505,6 +1505,8 @@
     static const Region kTransparentRegionHint;
     static const Region kTransparentRegionHintTwo;
     static const Region kTransparentRegionHintTwo90Rotation;
+    static const Region kTransparentRegionHintNegative;
+    static const Region kTransparentRegionHintNegativeIntersectsBounds;
 
     StrictMock<OutputPartialMock> mOutput;
     LayerFESet mGeomSnapshots;
@@ -1528,6 +1530,10 @@
         Region(Rect(25, 20, 50, 75));
 const Region OutputEnsureOutputLayerIfVisibleTest::kTransparentRegionHintTwo90Rotation =
         Region(Rect(125, 25, 180, 50));
+const Region OutputEnsureOutputLayerIfVisibleTest::kTransparentRegionHintNegative =
+        Region(Rect(INT32_MIN, INT32_MIN, INT32_MIN + 100, INT32_MIN + 200));
+const Region OutputEnsureOutputLayerIfVisibleTest::kTransparentRegionHintNegativeIntersectsBounds =
+        Region(Rect(INT32_MIN, INT32_MIN, 100, 100));
 
 TEST_F(OutputEnsureOutputLayerIfVisibleTest, performsGeomLatchBeforeCheckingIfLayerIncluded) {
     EXPECT_CALL(mOutput, includesLayer(sp<LayerFE>(mLayer.layerFE))).WillOnce(Return(false));
@@ -1997,6 +2003,41 @@
                 RegionEq(kTransparentRegionHintTwo90Rotation));
 }
 
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, transparentRegionExcludesOutputLayer) {
+    mLayer.layerFEState.isOpaque = false;
+    mLayer.layerFEState.contentDirty = true;
+    mLayer.layerFEState.geomLayerBounds = kFullBoundsNoRotation.bounds().toFloatRect();
+    mLayer.layerFEState.transparentRegionHint = kFullBoundsNoRotation;
+
+    EXPECT_CALL(mOutput, ensureOutputLayer(_, _)).Times(0);
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, transparentRegionIgnoredWhenOutsideBounds) {
+    mLayer.layerFEState.isOpaque = false;
+    mLayer.layerFEState.contentDirty = true;
+    mLayer.layerFEState.geomLayerBounds = kFullBoundsNoRotation.bounds().toFloatRect();
+    mLayer.layerFEState.transparentRegionHint = kTransparentRegionHintNegative;
+
+    EXPECT_CALL(mOutput, ensureOutputLayer(_, _)).Times(0);
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, transparentRegionClipsWhenOutsideBounds) {
+    mLayer.layerFEState.isOpaque = false;
+    mLayer.layerFEState.contentDirty = true;
+    mLayer.layerFEState.compositionType =
+            aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
+    mLayer.layerFEState.transparentRegionHint = kTransparentRegionHintNegativeIntersectsBounds;
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(std::nullopt), Eq(mLayer.layerFE)))
+            .WillOnce(Return(&mLayer.outputLayer));
+    ensureOutputLayerIfVisible();
+
+    // Check that the blocking region clips an out-of-bounds transparent region.
+    EXPECT_THAT(mLayer.outputLayerState.outputSpaceBlockingRegionHint,
+                RegionEq(kTransparentRegionHint));
+}
+
 /*
  * Output::present()
  */
@@ -3319,6 +3360,9 @@
         MOCK_METHOD2(appendRegionFlashRequests,
                      void(const Region&, std::vector<LayerFE::LayerSettings>&));
         MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
+        MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence),
+                    (override));
+        MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
     };
 
     OutputComposeSurfacesTest() {
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index 8a99e4e..0e9db36 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -376,7 +376,7 @@
             .WillOnce(Return(clientCompList2));
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
     mOutputState.isSecure = false;
-    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
     expectReadyBuffer(cachedSet);
 
     EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace());
@@ -429,7 +429,7 @@
             .WillOnce(Return(clientCompList2));
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
     mOutputState.isSecure = true;
-    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
     expectReadyBuffer(cachedSet);
 
     EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace());
@@ -477,7 +477,58 @@
             .WillOnce(Return(clientCompList2));
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
     mOutputState.isSecure = true;
-    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
+    expectReadyBuffer(cachedSet);
+
+    EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace());
+    EXPECT_EQ(Rect(kOutputSize.width, kOutputSize.height), cachedSet.getTextureBounds());
+
+    // Now check that appending a new cached set properly cleans up RenderEngine resources.
+    CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get();
+    cachedSet.append(CachedSet(layer3));
+}
+
+TEST_F(CachedSetTest, renderWhitePointNoColorTransform) {
+    // Skip the 0th layer to ensure that the bounding box of the layers is offset from (0, 0)
+    // This is a duplicate of the "renderWhitePoint" test, but setting "deviceHandlesColorTransform"
+    // to false, in the render call.
+
+    CachedSet::Layer& layer1 = *mTestLayers[1]->cachedSetLayer.get();
+    sp<mock::LayerFE> layerFE1 = mTestLayers[1]->layerFE;
+    CachedSet::Layer& layer2 = *mTestLayers[2]->cachedSetLayer.get();
+    sp<mock::LayerFE> layerFE2 = mTestLayers[2]->layerFE;
+
+    CachedSet cachedSet(layer1);
+    cachedSet.append(CachedSet(layer2));
+
+    std::vector<compositionengine::LayerFE::LayerSettings> clientCompList1;
+    clientCompList1.push_back({});
+
+    std::vector<compositionengine::LayerFE::LayerSettings> clientCompList2;
+    clientCompList2.push_back({});
+
+    mOutputState.displayBrightnessNits = 400.f;
+
+    const auto drawLayers =
+            [&](const renderengine::DisplaySettings& displaySettings,
+                const std::vector<renderengine::LayerSettings>&,
+                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+        EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits);
+        return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+    };
+
+    EXPECT_CALL(*layerFE1,
+                prepareClientCompositionList(ClientCompositionTargetSettingsWhitePointEq(
+                        mOutputState.displayBrightnessNits)))
+            .WillOnce(Return(clientCompList1));
+    EXPECT_CALL(*layerFE2,
+                prepareClientCompositionList(ClientCompositionTargetSettingsWhitePointEq(
+                        mOutputState.displayBrightnessNits)))
+            .WillOnce(Return(clientCompList2));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    mOutputState.isSecure = true;
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState, false);
     expectReadyBuffer(cachedSet);
 
     EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace());
@@ -527,7 +578,7 @@
     EXPECT_CALL(*layerFE1, prepareClientCompositionList(_)).WillOnce(Return(clientCompList1));
     EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2));
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
-    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
     expectReadyBuffer(cachedSet);
 
     EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace());
@@ -767,7 +818,7 @@
     };
 
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
-    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
 }
 
 TEST_F(CachedSetTest, addHolePunch_noBuffer) {
@@ -829,7 +880,7 @@
     };
 
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
-    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
 }
 
 TEST_F(CachedSetTest, append_removesHolePunch) {
@@ -969,7 +1020,7 @@
     };
 
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
-    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
 }
 
 } // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 50e3a28..96021ec 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -159,13 +159,13 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     // same geometry, update the internal layer stack
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 }
 
 void FlattenerTest::expectAllLayersFlattened(const std::vector<const LayerState*>& layers) {
@@ -177,7 +177,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     for (const auto layer : layers) {
         EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -187,7 +187,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     const auto buffer = layers[0]->getOutputLayer()->getState().overrideInfo.buffer;
     EXPECT_NE(nullptr, buffer);
@@ -222,7 +222,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 }
 
 TEST_F(FlattenerTest, flattenLayers_ActiveLayersWithLowFpsAreFlattened) {
@@ -284,7 +284,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -389,7 +389,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, overrideBuffer2);
@@ -428,7 +428,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, overrideBuffer2);
@@ -437,7 +437,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_NE(nullptr, overrideBuffer2);
@@ -452,7 +452,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_NE(nullptr, overrideBuffer2);
@@ -461,7 +461,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -505,7 +505,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, overrideBuffer2);
@@ -521,7 +521,7 @@
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
     mOutputState.framebufferSpace.setOrientation(ui::ROTATION_90);
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -534,7 +534,7 @@
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
     mOutputState.framebufferSpace.setOrientation(ui::ROTATION_180);
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -550,7 +550,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -562,7 +562,7 @@
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
     mOutputState.framebufferSpace.setOrientation(ui::ROTATION_270);
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -603,7 +603,7 @@
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
             .WillOnce(Return(ByMove(
                     futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     // We've rendered a CachedSet, but we haven't merged it in.
     EXPECT_EQ(nullptr, overrideBuffer1);
@@ -616,7 +616,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -669,7 +669,7 @@
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
             .WillOnce(Return(ByMove(
                     futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     // We've rendered a CachedSet, but we haven't merged it in.
     EXPECT_EQ(nullptr, overrideBuffer1);
@@ -682,7 +682,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -743,7 +743,7 @@
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
             .WillOnce(Return(ByMove(
                     futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     // We've rendered a CachedSet, but we haven't merged it in.
     EXPECT_EQ(nullptr, overrideBuffer0);
@@ -753,7 +753,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer0); // got overridden
     EXPECT_EQ(nullptr, overrideBuffer1); // did not
@@ -815,7 +815,7 @@
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
             .WillOnce(Return(ByMove(
                     futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     // We've rendered a CachedSet, but we haven't merged it in.
     EXPECT_EQ(nullptr, overrideBuffer0);
@@ -825,7 +825,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer0); // got overridden
     EXPECT_EQ(nullptr, overrideBuffer1); // did not
@@ -871,7 +871,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     for (const auto layer : layers) {
         EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -881,7 +881,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
     EXPECT_EQ(nullptr, overrideBuffer3);
@@ -917,7 +917,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     for (const auto layer : layers) {
         EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -928,7 +928,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
     for (const auto layer : layers) {
         EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
     }
@@ -971,7 +971,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     for (const auto layer : layers) {
         EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -981,7 +981,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, blurOverrideBuffer);
     EXPECT_NE(nullptr, overrideBuffer3);
@@ -1020,7 +1020,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     const auto& cachedSet = mFlattener->getNewCachedSetForTesting();
     ASSERT_NE(std::nullopt, cachedSet);
@@ -1034,7 +1034,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer2, overrideBuffer1);
     EXPECT_EQ(nullptr, blurOverrideBuffer);
@@ -1063,7 +1063,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, overrideBuffer2);
@@ -1071,12 +1071,12 @@
     // Simulate attempting to render prior to merging the new cached set with the layer stack.
     // Here we should not try to re-render.
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     // We provide the override buffer now that it's rendered
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer2, overrideBuffer1);
@@ -1120,7 +1120,8 @@
         EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
         mFlattener->renderCachedSets(mOutputState,
                                      std::chrono::steady_clock::now() -
-                                             (kCachedSetRenderDuration + 10ms));
+                                             (kCachedSetRenderDuration + 10ms),
+                                     true);
     }
 
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
@@ -1128,7 +1129,8 @@
                     futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     mFlattener->renderCachedSets(mOutputState,
                                  std::chrono::steady_clock::now() -
-                                         (kCachedSetRenderDuration + 10ms));
+                                         (kCachedSetRenderDuration + 10ms),
+                                 true);
 }
 
 TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) {
@@ -1162,7 +1164,7 @@
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
             .WillOnce(Return(ByMove(
                     futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     // We've rendered a CachedSet, but we haven't merged it in.
     EXPECT_EQ(nullptr, overrideBuffer1);
@@ -1175,7 +1177,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -1213,7 +1215,7 @@
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
             .WillOnce(Return(ByMove(
                     futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     // We've rendered a CachedSet, but we haven't merged it in.
     EXPECT_EQ(nullptr, overrideBuffer1);
@@ -1226,7 +1228,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -1264,7 +1266,7 @@
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
             .WillOnce(Return(ByMove(
                     futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     // We've rendered a CachedSet, but we haven't merged it in.
     EXPECT_EQ(nullptr, overrideBuffer1);
@@ -1277,7 +1279,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -1318,7 +1320,7 @@
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
             .WillOnce(Return(ByMove(
                     futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     // We've rendered a CachedSet, but we haven't merged it in.
     EXPECT_EQ(nullptr, overrideBuffer1);
@@ -1332,7 +1334,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, overrideBuffer2);
@@ -1371,7 +1373,7 @@
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
             .WillOnce(Return(ByMove(
                     futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     // We've rendered a CachedSet, but we haven't merged it in.
     EXPECT_EQ(nullptr, overrideBuffer1);
@@ -1384,7 +1386,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index a915b61..b49c95d 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -104,7 +104,7 @@
 
     mCompositionDisplay->getRenderSurface()->initialize();
 
-    setPowerMode(args.initialPowerMode);
+    if (args.initialPowerMode.has_value()) setPowerMode(args.initialPowerMode.value());
 
     // initialize the display orientation transform.
     setProjection(ui::ROTATION_0, Rect::INVALID_RECT, Rect::INVALID_RECT);
@@ -173,20 +173,31 @@
 }
 
 void DisplayDevice::setPowerMode(hal::PowerMode mode) {
+    if (mode == hal::PowerMode::OFF || mode == hal::PowerMode::ON) {
+        if (mStagedBrightness && mBrightness != mStagedBrightness) {
+            getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
+            mBrightness = *mStagedBrightness;
+        }
+        mStagedBrightness = std::nullopt;
+        getCompositionDisplay()->applyDisplayBrightness(true);
+    }
+
     mPowerMode = mode;
-    getCompositionDisplay()->setCompositionEnabled(mPowerMode != hal::PowerMode::OFF);
+
+    getCompositionDisplay()->setCompositionEnabled(mPowerMode.has_value() &&
+                                                   *mPowerMode != hal::PowerMode::OFF);
 }
 
 void DisplayDevice::enableLayerCaching(bool enable) {
     getCompositionDisplay()->setLayerCachingEnabled(enable);
 }
 
-hal::PowerMode DisplayDevice::getPowerMode() const {
+std::optional<hal::PowerMode> DisplayDevice::getPowerMode() const {
     return mPowerMode;
 }
 
 bool DisplayDevice::isPoweredOn() const {
-    return mPowerMode != hal::PowerMode::OFF;
+    return mPowerMode && *mPowerMode != hal::PowerMode::OFF;
 }
 
 void DisplayDevice::setActiveMode(DisplayModeId id) {
@@ -211,6 +222,7 @@
               to_string(getId()).c_str());
         return BAD_VALUE;
     }
+    mNumModeSwitchesInPolicy++;
     mUpcomingActiveMode = info;
     ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), info.mode->getFps().getIntValue());
     return mHwComposer.setActiveModeWithConstraints(getPhysicalId(), info.mode->getHwcId(),
@@ -324,8 +336,10 @@
 }
 
 void DisplayDevice::persistBrightness(bool needsComposite) {
-    if (needsComposite && mStagedBrightness && mBrightness != *mStagedBrightness) {
-        getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
+    if (mStagedBrightness && mBrightness != mStagedBrightness) {
+        if (needsComposite) {
+            getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
+        }
         mBrightness = *mStagedBrightness;
     }
     mStagedBrightness = std::nullopt;
@@ -372,7 +386,7 @@
     }
 
     result += "\n   powerMode="s;
-    result += to_string(mPowerMode);
+    result += mPowerMode.has_value() ? to_string(mPowerMode.value()) : "OFF(reset)";
     result += '\n';
 
     if (mRefreshRateConfigs) {
@@ -497,7 +511,7 @@
     }
 }
 
-bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) {
+bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info, bool force) {
     ATRACE_CALL();
 
     LOG_ALWAYS_FATAL_IF(!info.mode, "desired mode not provided");
@@ -515,7 +529,7 @@
     }
 
     // Check if we are already at the desired mode
-    if (getActiveMode()->getId() == info.mode->getId()) {
+    if (!force && getActiveMode()->getId() == info.mode->getId()) {
         return false;
     }
 
@@ -537,6 +551,27 @@
     mDesiredActiveModeChanged = false;
 }
 
+status_t DisplayDevice::setRefreshRatePolicy(
+        const std::optional<scheduler::RefreshRateConfigs::Policy>& policy, bool overridePolicy) {
+    const auto oldPolicy = mRefreshRateConfigs->getCurrentPolicy();
+    const status_t setPolicyResult = overridePolicy
+            ? mRefreshRateConfigs->setOverridePolicy(policy)
+            : mRefreshRateConfigs->setDisplayManagerPolicy(*policy);
+
+    if (setPolicyResult == OK) {
+        const int numModeChanges = mNumModeSwitchesInPolicy.exchange(0);
+
+        ALOGI("Display %s policy changed\n"
+              "Previous: {%s}\n"
+              "Current:  {%s}\n"
+              "%d mode changes were performed under the previous policy",
+              to_string(getId()).c_str(), oldPolicy.toString().c_str(),
+              policy ? policy->toString().c_str() : "null", numModeChanges);
+    }
+
+    return setPolicyResult;
+}
+
 std::atomic<int32_t> DisplayDeviceState::sNextSequenceId(1);
 
 }  // namespace android
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index d5d87b4..b91dece 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -181,7 +181,7 @@
     /* ------------------------------------------------------------------------
      * Display power mode management.
      */
-    hardware::graphics::composer::hal::PowerMode getPowerMode() const;
+    std::optional<hardware::graphics::composer::hal::PowerMode> getPowerMode() const;
     void setPowerMode(hardware::graphics::composer::hal::PowerMode mode);
     bool isPoweredOn() const;
 
@@ -204,7 +204,7 @@
         }
     };
 
-    bool setDesiredActiveMode(const ActiveModeInfo&) EXCLUDES(mActiveModeLock);
+    bool setDesiredActiveMode(const ActiveModeInfo&, bool force = false) EXCLUDES(mActiveModeLock);
     std::optional<ActiveModeInfo> getDesiredActiveMode() const EXCLUDES(mActiveModeLock);
     void clearDesiredActiveModeState() EXCLUDES(mActiveModeLock);
     ActiveModeInfo getUpcomingActiveMode() const REQUIRES(kMainThreadContext) {
@@ -249,6 +249,10 @@
     nsecs_t getVsyncPeriodFromHWC() const;
     nsecs_t getRefreshTimestamp() const;
 
+    status_t setRefreshRatePolicy(
+            const std::optional<scheduler::RefreshRateConfigs::Policy>& policy,
+            bool overridePolicy);
+
     // release HWC resources (if any) for removable displays
     void disconnect();
 
@@ -277,11 +281,11 @@
 
     static ui::Transform::RotationFlags sPrimaryDisplayRotationFlags;
 
-    hardware::graphics::composer::hal::PowerMode mPowerMode =
-            hardware::graphics::composer::hal::PowerMode::OFF;
+     // allow initial power mode as null.
+    std::optional<hardware::graphics::composer::hal::PowerMode> mPowerMode;
     DisplayModePtr mActiveMode;
-    std::optional<float> mStagedBrightness = std::nullopt;
-    float mBrightness = -1.f;
+    std::optional<float> mStagedBrightness;
+    std::optional<float> mBrightness;
     const DisplayModes mSupportedModes;
 
     std::atomic<nsecs_t> mLastHwVsync = 0;
@@ -303,6 +307,8 @@
     TracedOrdinal<bool> mDesiredActiveModeChanged
             GUARDED_BY(mActiveModeLock) = {"DesiredActiveModeChanged", false};
     ActiveModeInfo mUpcomingActiveMode GUARDED_BY(kMainThreadContext);
+
+    std::atomic_int mNumModeSwitchesInPolicy = 0;
 };
 
 struct DisplayDeviceState {
@@ -360,8 +366,7 @@
     HdrCapabilities hdrCapabilities;
     int32_t supportedPerFrameMetadata{0};
     std::unordered_map<ui::ColorMode, std::vector<ui::RenderIntent>> hwcColorModes;
-    hardware::graphics::composer::hal::PowerMode initialPowerMode{
-            hardware::graphics::composer::hal::PowerMode::ON};
+    std::optional<hardware::graphics::composer::hal::PowerMode> initialPowerMode;
     bool isPrimary{false};
     DisplayModes supportedModes;
     DisplayModeId activeModeId;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 0da8ece..a6aee1f 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -738,6 +738,13 @@
             });
 }
 
+bool HWComposer::getValidateSkipped(HalDisplayId displayId) const {
+    if (mDisplayData.count(displayId) == 0) {
+        return false;
+    }
+    return mDisplayData.at(displayId).validateWasSkipped;
+}
+
 status_t HWComposer::setBootDisplayMode(PhysicalDisplayId displayId,
                                         hal::HWConfigId displayModeId) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 4c0ecd8..92a8f30 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -199,6 +199,9 @@
             PhysicalDisplayId, float brightness, float brightnessNits,
             const Hwc2::Composer::DisplayBrightnessOptions&) = 0;
 
+    // Get whether the display skipped validation on the latest present
+    virtual bool getValidateSkipped(HalDisplayId displayId) const = 0;
+
     // Events handling ---------------------------------------------------------
 
     // Returns stable display ID (and display name on connection of new or previously disconnected
@@ -397,6 +400,8 @@
 
     status_t setActiveColorMode(PhysicalDisplayId, ui::ColorMode, ui::RenderIntent) override;
 
+    bool getValidateSkipped(HalDisplayId displayId) const override;
+
     // Composer 2.4
     ui::DisplayConnectionType getDisplayConnectionType(PhysicalDisplayId) const override;
     bool isVsyncPeriodSwitchSupported(PhysicalDisplayId) const override;
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index b5678b4..a0350b7 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -187,7 +187,7 @@
     if (!mSupportsPowerHint.has_value()) {
         std::lock_guard lock(mPowerHalMutex);
         HalWrapper* const halWrapper = getPowerHal();
-        mSupportsPowerHint = halWrapper->supportsPowerHintSession();
+        mSupportsPowerHint = halWrapper && halWrapper->supportsPowerHintSession();
     }
     return *mSupportsPowerHint;
 }
@@ -196,7 +196,7 @@
     return mPowerHintSessionRunning;
 }
 
-void PowerAdvisor::setTargetWorkDuration(int64_t targetDurationNanos) {
+void PowerAdvisor::setTargetWorkDuration(int64_t targetDuration) {
     if (!usePowerHintSession()) {
         ALOGV("Power hint session target duration cannot be set, skipping");
         return;
@@ -205,26 +205,45 @@
         std::lock_guard lock(mPowerHalMutex);
         HalWrapper* const halWrapper = getPowerHal();
         if (halWrapper != nullptr) {
-            halWrapper->setTargetWorkDuration(targetDurationNanos - kTargetSafetyMargin.count());
+            halWrapper->setTargetWorkDuration(targetDuration);
         }
     }
 }
 
-void PowerAdvisor::sendActualWorkDuration(int64_t actualDurationNanos, nsecs_t timeStampNanos) {
+void PowerAdvisor::sendActualWorkDuration() {
     if (!mBootFinished || !usePowerHintSession()) {
         ALOGV("Actual work duration power hint cannot be sent, skipping");
         return;
     }
-    {
+    const std::optional<nsecs_t> actualDuration = estimateWorkDuration(false);
+    if (actualDuration.has_value()) {
         std::lock_guard lock(mPowerHalMutex);
         HalWrapper* const halWrapper = getPowerHal();
         if (halWrapper != nullptr) {
-            halWrapper->sendActualWorkDuration(actualDurationNanos, timeStampNanos);
+            halWrapper->sendActualWorkDuration(*actualDuration + kTargetSafetyMargin.count(),
+                                               systemTime());
         }
     }
 }
 
-// needs to be set after the flag is known but before PowerAdvisor enters onBootFinished
+void PowerAdvisor::sendPredictedWorkDuration() {
+    if (!mBootFinished || !usePowerHintSession()) {
+        ALOGV("Actual work duration power hint cannot be sent, skipping");
+        return;
+    }
+
+    const std::optional<nsecs_t> predictedDuration = estimateWorkDuration(true);
+
+    if (predictedDuration.has_value()) {
+        std::lock_guard lock(mPowerHalMutex);
+        HalWrapper* const halWrapper = getPowerHal();
+        if (halWrapper != nullptr) {
+            halWrapper->sendActualWorkDuration(*predictedDuration + kTargetSafetyMargin.count(),
+                                               systemTime());
+        }
+    }
+}
+
 void PowerAdvisor::enablePowerHint(bool enabled) {
     mPowerHintEnabled = enabled;
 }
@@ -244,6 +263,302 @@
     return mPowerHintSessionRunning;
 }
 
+void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
+    DisplayTimingData& displayData = mDisplayTimingData[displayId];
+    if (displayData.gpuEndFenceTime) {
+        nsecs_t signalTime = displayData.gpuEndFenceTime->getSignalTime();
+        if (signalTime != Fence::SIGNAL_TIME_INVALID && signalTime != Fence::SIGNAL_TIME_PENDING) {
+            for (auto&& [_, otherDisplayData] : mDisplayTimingData) {
+                // If the previous display started before us but ended after we should have
+                // started, then it likely delayed our start time and we must compensate for that.
+                // Displays finishing earlier should have already made their way through this call
+                // and swapped their timing into "lastValid" from "latest", so we check that here.
+                if (!otherDisplayData.lastValidGpuStartTime.has_value()) continue;
+                if ((*otherDisplayData.lastValidGpuStartTime < *displayData.gpuStartTime) &&
+                    (*otherDisplayData.lastValidGpuEndTime > *displayData.gpuStartTime)) {
+                    displayData.lastValidGpuStartTime = *otherDisplayData.lastValidGpuEndTime;
+                    break;
+                }
+            }
+            displayData.lastValidGpuStartTime = displayData.gpuStartTime;
+            displayData.lastValidGpuEndTime = signalTime;
+        }
+    }
+    displayData.gpuEndFenceTime = std::move(fenceTime);
+    displayData.gpuStartTime = systemTime();
+}
+
+void PowerAdvisor::setHwcValidateTiming(DisplayId displayId, nsecs_t validateStartTime,
+                                        nsecs_t validateEndTime) {
+    DisplayTimingData& displayData = mDisplayTimingData[displayId];
+    displayData.hwcValidateStartTime = validateStartTime;
+    displayData.hwcValidateEndTime = validateEndTime;
+}
+
+void PowerAdvisor::setHwcPresentTiming(DisplayId displayId, nsecs_t presentStartTime,
+                                       nsecs_t presentEndTime) {
+    DisplayTimingData& displayData = mDisplayTimingData[displayId];
+    displayData.hwcPresentStartTime = presentStartTime;
+    displayData.hwcPresentEndTime = presentEndTime;
+}
+
+void PowerAdvisor::setSkippedValidate(DisplayId displayId, bool skipped) {
+    mDisplayTimingData[displayId].skippedValidate = skipped;
+}
+
+void PowerAdvisor::setRequiresClientComposition(DisplayId displayId,
+                                                bool requiresClientComposition) {
+    mDisplayTimingData[displayId].usedClientComposition = requiresClientComposition;
+}
+
+void PowerAdvisor::setExpectedPresentTime(nsecs_t expectedPresentTime) {
+    mExpectedPresentTimes.append(expectedPresentTime);
+}
+
+void PowerAdvisor::setSfPresentTiming(nsecs_t presentFenceTime, nsecs_t presentEndTime) {
+    mLastSfPresentEndTime = presentEndTime;
+    mLastPresentFenceTime = presentFenceTime;
+}
+
+void PowerAdvisor::setFrameDelay(nsecs_t frameDelayDuration) {
+    mFrameDelayDuration = frameDelayDuration;
+}
+
+void PowerAdvisor::setHwcPresentDelayedTime(
+        DisplayId displayId, std::chrono::steady_clock::time_point earliestFrameStartTime) {
+    mDisplayTimingData[displayId].hwcPresentDelayedTime =
+            (earliestFrameStartTime - std::chrono::steady_clock::now()).count() + systemTime();
+}
+
+void PowerAdvisor::setCommitStart(nsecs_t commitStartTime) {
+    mCommitStartTimes.append(commitStartTime);
+}
+
+void PowerAdvisor::setCompositeEnd(nsecs_t compositeEnd) {
+    mLastPostcompDuration = compositeEnd - mLastSfPresentEndTime;
+}
+
+void PowerAdvisor::setDisplays(std::vector<DisplayId>& displayIds) {
+    mDisplayIds = displayIds;
+}
+
+void PowerAdvisor::setTotalFrameTargetWorkDuration(nsecs_t targetDuration) {
+    mTotalFrameTargetDuration = targetDuration;
+}
+
+std::vector<DisplayId> PowerAdvisor::getOrderedDisplayIds(
+        std::optional<nsecs_t> DisplayTimingData::*sortBy) {
+    std::vector<DisplayId> sortedDisplays;
+    std::copy_if(mDisplayIds.begin(), mDisplayIds.end(), std::back_inserter(sortedDisplays),
+                 [&](DisplayId id) {
+                     return mDisplayTimingData.count(id) &&
+                             (mDisplayTimingData[id].*sortBy).has_value();
+                 });
+    std::sort(sortedDisplays.begin(), sortedDisplays.end(), [&](DisplayId idA, DisplayId idB) {
+        return *(mDisplayTimingData[idA].*sortBy) < *(mDisplayTimingData[idB].*sortBy);
+    });
+    return sortedDisplays;
+}
+
+std::optional<nsecs_t> PowerAdvisor::estimateWorkDuration(bool earlyHint) {
+    if (earlyHint && (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull())) {
+        return std::nullopt;
+    }
+
+    // Tracks when we finish presenting to hwc
+    nsecs_t estimatedEndTime = mCommitStartTimes[0];
+
+    // How long we spent this frame not doing anything, waiting for fences or vsync
+    nsecs_t idleDuration = 0;
+
+    // Most recent previous gpu end time in the current frame, probably from a prior display, used
+    // as the start time for the next gpu operation if it ran over time since it probably blocked
+    std::optional<nsecs_t> previousValidGpuEndTime;
+
+    // The currently estimated gpu end time for the frame,
+    // used to accumulate gpu time as we iterate over the active displays
+    std::optional<nsecs_t> estimatedGpuEndTime;
+
+    // If we're predicting at the start of the frame, we use last frame as our reference point
+    // If we're predicting at the end of the frame, we use the current frame as a reference point
+    nsecs_t referenceFrameStartTime = (earlyHint ? mCommitStartTimes[-1] : mCommitStartTimes[0]);
+
+    // When the prior frame should be presenting to the display
+    // If we're predicting at the start of the frame, we use last frame's expected present time
+    // If we're predicting at the end of the frame, the present fence time is already known
+    nsecs_t lastFramePresentTime = (earlyHint ? mExpectedPresentTimes[-1] : mLastPresentFenceTime);
+
+    // The timing info for the previously calculated display, if there was one
+    std::optional<DisplayTimeline> previousDisplayReferenceTiming;
+    std::vector<DisplayId>&& displayIds =
+            getOrderedDisplayIds(&DisplayTimingData::hwcPresentStartTime);
+    DisplayTimeline referenceTiming, estimatedTiming;
+
+    // Iterate over the displays that use hwc in the same order they are presented
+    for (DisplayId displayId : displayIds) {
+        if (mDisplayTimingData.count(displayId) == 0) {
+            continue;
+        }
+
+        auto& displayData = mDisplayTimingData.at(displayId);
+
+        // mLastPresentFenceTime should always be the time of the reference frame, since it will be
+        // the previous frame's present fence if called at the start, and current frame's if called
+        // at the end
+        referenceTiming = displayData.calculateDisplayTimeline(mLastPresentFenceTime);
+
+        // If this is the first display, include the duration before hwc present starts
+        if (!previousDisplayReferenceTiming.has_value()) {
+            estimatedEndTime += referenceTiming.hwcPresentStartTime - referenceFrameStartTime;
+        } else { // Otherwise add the time since last display's hwc present finished
+            estimatedEndTime += referenceTiming.hwcPresentStartTime -
+                    previousDisplayReferenceTiming->hwcPresentEndTime;
+        }
+
+        // Late hint can re-use reference timing here since it's estimating its own reference frame
+        estimatedTiming = earlyHint
+                ? referenceTiming.estimateTimelineFromReference(lastFramePresentTime,
+                                                                estimatedEndTime)
+                : referenceTiming;
+
+        // Update predicted present finish time with this display's present time
+        estimatedEndTime = estimatedTiming.hwcPresentEndTime;
+
+        // Track how long we spent waiting for the fence, can be excluded from the timing estimate
+        idleDuration += estimatedTiming.probablyWaitsForPresentFence
+                ? lastFramePresentTime - estimatedTiming.presentFenceWaitStartTime
+                : 0;
+
+        // Track how long we spent waiting to present, can be excluded from the timing estimate
+        idleDuration += earlyHint ? 0 : referenceTiming.hwcPresentDelayDuration;
+
+        // Estimate the reference frame's gpu timing
+        auto gpuTiming = displayData.estimateGpuTiming(previousValidGpuEndTime);
+        if (gpuTiming.has_value()) {
+            previousValidGpuEndTime = gpuTiming->startTime + gpuTiming->duration;
+
+            // Estimate the prediction frame's gpu end time from the reference frame
+            estimatedGpuEndTime =
+                    std::max(estimatedTiming.hwcPresentStartTime, estimatedGpuEndTime.value_or(0)) +
+                    gpuTiming->duration;
+        }
+        previousDisplayReferenceTiming = referenceTiming;
+    }
+    ATRACE_INT64("Idle duration", idleDuration);
+
+    nsecs_t estimatedFlingerEndTime = earlyHint ? estimatedEndTime : mLastSfPresentEndTime;
+
+    // Don't count time spent idly waiting in the estimate as we could do more work in that time
+    estimatedEndTime -= idleDuration;
+    estimatedFlingerEndTime -= idleDuration;
+
+    // We finish the frame when both present and the gpu are done, so wait for the later of the two
+    // Also add the frame delay duration since the target did not move while we were delayed
+    nsecs_t totalDuration = mFrameDelayDuration +
+            std::max(estimatedEndTime, estimatedGpuEndTime.value_or(0)) - mCommitStartTimes[0];
+
+    // We finish SurfaceFlinger when post-composition finishes, so add that in here
+    nsecs_t flingerDuration =
+            estimatedFlingerEndTime + mLastPostcompDuration - mCommitStartTimes[0];
+
+    // Combine the two timings into a single normalized one
+    nsecs_t combinedDuration = combineTimingEstimates(totalDuration, flingerDuration);
+
+    return std::make_optional(combinedDuration);
+}
+
+nsecs_t PowerAdvisor::combineTimingEstimates(nsecs_t totalDuration, nsecs_t flingerDuration) {
+    nsecs_t targetDuration;
+    {
+        std::lock_guard lock(mPowerHalMutex);
+        targetDuration = *getPowerHal()->getTargetWorkDuration();
+    }
+    if (!mTotalFrameTargetDuration.has_value()) return flingerDuration;
+
+    // Normalize total to the flinger target (vsync period) since that's how often we actually send
+    // hints
+    nsecs_t normalizedTotalDuration = (targetDuration * totalDuration) / *mTotalFrameTargetDuration;
+    return std::max(flingerDuration, normalizedTotalDuration);
+}
+
+PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimeline::estimateTimelineFromReference(
+        nsecs_t fenceTime, nsecs_t displayStartTime) {
+    DisplayTimeline estimated;
+    estimated.hwcPresentStartTime = displayStartTime;
+
+    // We don't predict waiting for vsync alignment yet
+    estimated.hwcPresentDelayDuration = 0;
+
+    // How long we expect to run before we start waiting for the fence
+    // For now just re-use last frame's post-present duration and assume it will not change much
+    // Excludes time spent waiting for vsync since that's not going to be consistent
+    estimated.presentFenceWaitStartTime = estimated.hwcPresentStartTime +
+            (presentFenceWaitStartTime - (hwcPresentStartTime + hwcPresentDelayDuration));
+    estimated.probablyWaitsForPresentFence = fenceTime > estimated.presentFenceWaitStartTime;
+    estimated.hwcPresentEndTime = postPresentFenceHwcPresentDuration +
+            (estimated.probablyWaitsForPresentFence ? fenceTime
+                                                    : estimated.presentFenceWaitStartTime);
+    return estimated;
+}
+
+PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimingData::calculateDisplayTimeline(
+        nsecs_t fenceTime) {
+    DisplayTimeline timeline;
+    // How long between calling hwc present and trying to wait on the fence
+    const nsecs_t fenceWaitStartDelay =
+            (skippedValidate ? kFenceWaitStartDelaySkippedValidate : kFenceWaitStartDelayValidated)
+                    .count();
+
+    // Did our reference frame wait for an appropriate vsync before calling into hwc
+    const bool waitedOnHwcPresentTime = hwcPresentDelayedTime.has_value() &&
+            *hwcPresentDelayedTime > *hwcPresentStartTime &&
+            *hwcPresentDelayedTime < *hwcPresentEndTime;
+
+    // Use validate start here if we skipped it because we did validate + present together
+    timeline.hwcPresentStartTime = skippedValidate ? *hwcValidateStartTime : *hwcPresentStartTime;
+
+    // Use validate end here if we skipped it because we did validate + present together
+    timeline.hwcPresentEndTime = skippedValidate ? *hwcValidateEndTime : *hwcPresentEndTime;
+
+    // How long hwc present was delayed waiting for the next appropriate vsync
+    timeline.hwcPresentDelayDuration =
+            (waitedOnHwcPresentTime ? *hwcPresentDelayedTime - *hwcPresentStartTime : 0);
+    // When we started waiting for the present fence after calling into hwc present
+    timeline.presentFenceWaitStartTime =
+            timeline.hwcPresentStartTime + timeline.hwcPresentDelayDuration + fenceWaitStartDelay;
+    timeline.probablyWaitsForPresentFence = fenceTime > timeline.presentFenceWaitStartTime &&
+            fenceTime < timeline.hwcPresentEndTime;
+
+    // How long we ran after we finished waiting for the fence but before hwc present finished
+    timeline.postPresentFenceHwcPresentDuration = timeline.hwcPresentEndTime -
+            (timeline.probablyWaitsForPresentFence ? fenceTime
+                                                   : timeline.presentFenceWaitStartTime);
+    return timeline;
+}
+
+std::optional<PowerAdvisor::GpuTimeline> PowerAdvisor::DisplayTimingData::estimateGpuTiming(
+        std::optional<nsecs_t> previousEnd) {
+    if (!(usedClientComposition && lastValidGpuStartTime.has_value() && gpuEndFenceTime)) {
+        return std::nullopt;
+    }
+    const nsecs_t latestGpuStartTime = std::max(previousEnd.value_or(0), *gpuStartTime);
+    const nsecs_t latestGpuEndTime = gpuEndFenceTime->getSignalTime();
+    nsecs_t gpuDuration = 0;
+    if (latestGpuEndTime != Fence::SIGNAL_TIME_INVALID &&
+        latestGpuEndTime != Fence::SIGNAL_TIME_PENDING) {
+        // If we know how long the most recent gpu duration was, use that
+        gpuDuration = latestGpuEndTime - latestGpuStartTime;
+    } else if (lastValidGpuEndTime.has_value()) {
+        // If we don't have the fence data, use the most recent information we do have
+        gpuDuration = *lastValidGpuEndTime - *lastValidGpuStartTime;
+        if (latestGpuEndTime == Fence::SIGNAL_TIME_PENDING) {
+            // If pending but went over the previous duration, use current time as the end
+            gpuDuration = std::max(gpuDuration, systemTime() - latestGpuStartTime);
+        }
+    }
+    return GpuTimeline{.duration = gpuDuration, .startTime = latestGpuStartTime};
+}
+
 class HidlPowerHalWrapper : public PowerAdvisor::HalWrapper {
 public:
     HidlPowerHalWrapper(sp<V1_3::IPower> powerHal) : mPowerHal(std::move(powerHal)) {}
@@ -325,6 +640,9 @@
     }
 
     mSupportsPowerHint = checkPowerHintSessionSupported();
+
+    // Currently set to 0 to disable rate limiter by default
+    mAllowedActualDeviation = base::GetIntProperty<nsecs_t>("debug.sf.allowed_actual_deviation", 0);
 }
 
 AidlPowerHalWrapper::~AidlPowerHalWrapper() {
@@ -332,7 +650,7 @@
         mPowerHintSession->close();
         mPowerHintSession = nullptr;
     }
-};
+}
 
 std::unique_ptr<PowerAdvisor::HalWrapper> AidlPowerHalWrapper::connect() {
     // This only waits if the service is actually declared
@@ -370,7 +688,7 @@
     return ret.isOk();
 }
 
-// only version 2+ of the aidl supports power hint sessions, hidl has no support
+// Only version 2+ of the aidl supports power hint sessions, hidl has no support
 bool AidlPowerHalWrapper::supportsPowerHintSession() {
     return mSupportsPowerHint;
 }
@@ -424,30 +742,14 @@
     return isPowerHintSessionRunning();
 }
 
-bool AidlPowerHalWrapper::shouldSetTargetDuration(int64_t targetDurationNanos) {
-    if (targetDurationNanos <= 0) {
-        return false;
-    }
-    // report if the change in target from our last submission to now exceeds the threshold
-    return abs(1.0 -
-               static_cast<double>(mLastTargetDurationSent) /
-                       static_cast<double>(targetDurationNanos)) >= kAllowedTargetDeviationPercent;
-}
-
-void AidlPowerHalWrapper::setTargetWorkDuration(int64_t targetDurationNanos) {
+void AidlPowerHalWrapper::setTargetWorkDuration(int64_t targetDuration) {
     ATRACE_CALL();
-    mTargetDuration = targetDurationNanos;
-    if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDurationNanos);
-    if (!sNormalizeTarget && isPowerHintSessionRunning() &&
-        shouldSetTargetDuration(targetDurationNanos)) {
-        if (mLastActualDurationSent.has_value()) {
-            // update the error term here since we are actually sending an update to powerhal
-            if (sTraceHintSessionData)
-                ATRACE_INT64("Target error term", targetDurationNanos - *mLastActualDurationSent);
-        }
-        ALOGV("Sending target time: %" PRId64 "ns", targetDurationNanos);
-        mLastTargetDurationSent = targetDurationNanos;
-        auto ret = mPowerHintSession->updateTargetWorkDuration(targetDurationNanos);
+    mTargetDuration = targetDuration;
+    if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration);
+    if (isPowerHintSessionRunning() && (targetDuration != mLastTargetDurationSent)) {
+        ALOGV("Sending target time: %" PRId64 "ns", targetDuration);
+        mLastTargetDurationSent = targetDuration;
+        auto ret = mPowerHintSession->updateTargetWorkDuration(targetDuration);
         if (!ret.isOk()) {
             ALOGW("Failed to set power hint target work duration with error: %s",
                   ret.exceptionMessage().c_str());
@@ -456,8 +758,8 @@
     }
 }
 
-bool AidlPowerHalWrapper::shouldReportActualDurationsNow() {
-    // report if we have never reported before or are approaching a stale session
+bool AidlPowerHalWrapper::shouldReportActualDurations() {
+    // Report if we have never reported before or are approaching a stale session
     if (!mLastActualDurationSent.has_value() ||
         (systemTime() - mLastActualReportTimestamp) > kStaleTimeout.count()) {
         return true;
@@ -466,65 +768,42 @@
     if (!mActualDuration.has_value()) {
         return false;
     }
-
-    // duration of most recent timing
-    const double mostRecentActualDuration = static_cast<double>(*mActualDuration);
-    // duration of the last timing actually reported to the powerhal
-    const double lastReportedActualDuration = static_cast<double>(*mLastActualDurationSent);
-
-    // report if the change in duration from then to now exceeds the threshold
-    return abs(1.0 - mostRecentActualDuration / lastReportedActualDuration) >=
-            kAllowedActualDeviationPercent;
+    // Report if the change in actual duration exceeds the threshold
+    return abs(*mActualDuration - *mLastActualDurationSent) > mAllowedActualDeviation;
 }
 
-void AidlPowerHalWrapper::sendActualWorkDuration(int64_t actualDurationNanos,
-                                                 nsecs_t timeStampNanos) {
+void AidlPowerHalWrapper::sendActualWorkDuration(int64_t actualDuration, nsecs_t timestamp) {
     ATRACE_CALL();
 
-    if (actualDurationNanos < 0 || !isPowerHintSessionRunning()) {
+    if (actualDuration < 0 || !isPowerHintSessionRunning()) {
         ALOGV("Failed to send actual work duration, skipping");
         return;
     }
-    nsecs_t reportedDuration = actualDurationNanos;
+    const nsecs_t reportedDuration = actualDuration;
 
-    // normalize the sent values to a pre-set target
-    if (sNormalizeTarget) {
-        reportedDuration += mLastTargetDurationSent - mTargetDuration;
-    } else {
-        // when target duration change is within deviation and not updated, adjust the actual
-        // duration proportionally based on the difference, e.g. if new target is 5ms longer than
-        // last reported but actual duration is the same as last target, we want to report a smaller
-        // actual work duration now to indicate that we are overshooting
-        if (mLastTargetDurationSent != kDefaultTarget.count() && mTargetDuration != 0) {
-            reportedDuration =
-                    static_cast<int64_t>(static_cast<long double>(mLastTargetDurationSent) /
-                                         mTargetDuration * actualDurationNanos);
-            mActualDuration = reportedDuration;
-        }
-    }
     mActualDuration = reportedDuration;
     WorkDuration duration;
     duration.durationNanos = reportedDuration;
-    duration.timeStampNanos = timeStampNanos;
+    duration.timeStampNanos = timestamp;
     mPowerHintQueue.push_back(duration);
 
     if (sTraceHintSessionData) {
-        ATRACE_INT64("Measured duration", actualDurationNanos);
-        ATRACE_INT64("Target error term", mTargetDuration - actualDurationNanos);
+        ATRACE_INT64("Measured duration", actualDuration);
+        ATRACE_INT64("Target error term", actualDuration - mTargetDuration);
 
         ATRACE_INT64("Reported duration", reportedDuration);
         ATRACE_INT64("Reported target", mLastTargetDurationSent);
-        ATRACE_INT64("Reported target error term", mLastTargetDurationSent - reportedDuration);
+        ATRACE_INT64("Reported target error term", reportedDuration - mLastTargetDurationSent);
     }
 
     ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64
           " with error: %" PRId64,
-          reportedDuration, mLastTargetDurationSent, mLastTargetDurationSent - reportedDuration);
+          reportedDuration, mLastTargetDurationSent, reportedDuration - mLastTargetDurationSent);
 
     // This rate limiter queues similar duration reports to the powerhal into
     // batches to avoid excessive binder calls. The criteria to send a given batch
     // are outlined in shouldReportActualDurationsNow()
-    if (shouldReportActualDurationsNow()) {
+    if (shouldReportActualDurations()) {
         ALOGV("Sending hint update batch");
         mLastActualReportTimestamp = systemTime();
         auto ret = mPowerHintSession->reportActualWorkDuration(mPowerHintQueue);
@@ -534,8 +813,8 @@
             mShouldReconnectHal = true;
         }
         mPowerHintQueue.clear();
-        // we save the non-normalized value here to detect % changes
-        mLastActualDurationSent = reportedDuration;
+        // We save the actual duration here for rate limiting
+        mLastActualDurationSent = actualDuration;
     }
 }
 
@@ -551,76 +830,73 @@
     return mTargetDuration;
 }
 
+void AidlPowerHalWrapper::setAllowedActualDeviation(nsecs_t allowedDeviation) {
+    mAllowedActualDeviation = allowedDeviation;
+}
+
 const bool AidlPowerHalWrapper::sTraceHintSessionData =
         base::GetBoolProperty(std::string("debug.sf.trace_hint_sessions"), false);
 
-const bool AidlPowerHalWrapper::sNormalizeTarget =
-        base::GetBoolProperty(std::string("debug.sf.normalize_hint_session_durations"), false);
-
 PowerAdvisor::HalWrapper* PowerAdvisor::getPowerHal() {
-    static std::unique_ptr<HalWrapper> sHalWrapper = nullptr;
-    static bool sHasHal = true;
-
-    if (!sHasHal) {
+    if (!mHasHal) {
         return nullptr;
     }
 
-    // grab old hint session values before we destroy any existing wrapper
+    // Grab old hint session values before we destroy any existing wrapper
     std::vector<int32_t> oldPowerHintSessionThreadIds;
     std::optional<int64_t> oldTargetWorkDuration;
 
-    if (sHalWrapper != nullptr) {
-        oldPowerHintSessionThreadIds = sHalWrapper->getPowerHintSessionThreadIds();
-        oldTargetWorkDuration = sHalWrapper->getTargetWorkDuration();
+    if (mHalWrapper != nullptr) {
+        oldPowerHintSessionThreadIds = mHalWrapper->getPowerHintSessionThreadIds();
+        oldTargetWorkDuration = mHalWrapper->getTargetWorkDuration();
     }
 
     // If we used to have a HAL, but it stopped responding, attempt to reconnect
     if (mReconnectPowerHal) {
-        sHalWrapper = nullptr;
+        mHalWrapper = nullptr;
         mReconnectPowerHal = false;
     }
 
-    if (sHalWrapper != nullptr) {
-        auto wrapper = sHalWrapper.get();
-        // if the wrapper is fine, return it, but if it indicates a reconnect, remake it
+    if (mHalWrapper != nullptr) {
+        auto wrapper = mHalWrapper.get();
+        // If the wrapper is fine, return it, but if it indicates a reconnect, remake it
         if (!wrapper->shouldReconnectHAL()) {
             return wrapper;
         }
         ALOGD("Reconnecting Power HAL");
-        sHalWrapper = nullptr;
+        mHalWrapper = nullptr;
     }
 
-    // at this point, we know for sure there is no running session
+    // At this point, we know for sure there is no running session
     mPowerHintSessionRunning = false;
 
     // First attempt to connect to the AIDL Power HAL
-    sHalWrapper = AidlPowerHalWrapper::connect();
+    mHalWrapper = AidlPowerHalWrapper::connect();
 
     // If that didn't succeed, attempt to connect to the HIDL Power HAL
-    if (sHalWrapper == nullptr) {
-        sHalWrapper = HidlPowerHalWrapper::connect();
+    if (mHalWrapper == nullptr) {
+        mHalWrapper = HidlPowerHalWrapper::connect();
     } else {
         ALOGD("Successfully connecting AIDL Power HAL");
-        // if AIDL, pass on any existing hint session values
-        // thread ids always safe to set
-        sHalWrapper->setPowerHintSessionThreadIds(oldPowerHintSessionThreadIds);
-        // only set duration and start if duration is defined
+        // If AIDL, pass on any existing hint session values
+        mHalWrapper->setPowerHintSessionThreadIds(oldPowerHintSessionThreadIds);
+        // Only set duration and start if duration is defined
         if (oldTargetWorkDuration.has_value()) {
-            sHalWrapper->setTargetWorkDuration(*oldTargetWorkDuration);
-            // only start if possible to run and both threadids and duration are defined
+            mHalWrapper->setTargetWorkDuration(*oldTargetWorkDuration);
+            // Only start if possible to run and both threadids and duration are defined
             if (usePowerHintSession() && !oldPowerHintSessionThreadIds.empty()) {
-                mPowerHintSessionRunning = sHalWrapper->startPowerHintSession();
+                mPowerHintSessionRunning = mHalWrapper->startPowerHintSession();
             }
         }
     }
 
     // If we make it to this point and still don't have a HAL, it's unlikely we
     // will, so stop trying
-    if (sHalWrapper == nullptr) {
-        sHasHal = false;
+    if (mHalWrapper == nullptr) {
+        mHasHal = false;
     }
 
-    return sHalWrapper.get();
+    return mHalWrapper.get();
 }
 
 } // namespace impl
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 7c10e19..6e25f78 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -18,11 +18,15 @@
 
 #include <atomic>
 #include <chrono>
+#include <unordered_map>
 #include <unordered_set>
 
+#include <ui/DisplayId.h>
+#include <ui/FenceTime.h>
 #include <utils/Mutex.h>
 
 #include <android/hardware/power/IPower.h>
+#include <compositionengine/impl/OutputCompositionState.h>
 #include <ui/DisplayIdentification.h>
 #include "../Scheduler/OneShotTimer.h"
 
@@ -44,13 +48,50 @@
     virtual void setExpensiveRenderingExpected(DisplayId displayId, bool expected) = 0;
     virtual bool isUsingExpensiveRendering() = 0;
     virtual void notifyDisplayUpdateImminent() = 0;
+    // Checks both if it supports and if it's enabled
     virtual bool usePowerHintSession() = 0;
     virtual bool supportsPowerHintSession() = 0;
     virtual bool isPowerHintSessionRunning() = 0;
-    virtual void setTargetWorkDuration(int64_t targetDurationNanos) = 0;
-    virtual void sendActualWorkDuration(int64_t actualDurationNanos, nsecs_t timestamp) = 0;
+    // Sends a power hint that updates to the target work duration for the frame
+    virtual void setTargetWorkDuration(nsecs_t targetDuration) = 0;
+    // Sends a power hint for the actual known work duration at the end of the frame
+    virtual void sendActualWorkDuration() = 0;
+    // Sends a power hint for the upcoming frame predicted from previous frame timing
+    virtual void sendPredictedWorkDuration() = 0;
+    // Sets whether the power hint session is enabled
     virtual void enablePowerHint(bool enabled) = 0;
+    // Initializes the power hint session
     virtual bool startPowerHintSession(const std::vector<int32_t>& threadIds) = 0;
+    // Provides PowerAdvisor with a copy of the gpu fence so it can determine the gpu end time
+    virtual void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) = 0;
+    // Reports the start and end times of a hwc validate call this frame for a given display
+    virtual void setHwcValidateTiming(DisplayId displayId, nsecs_t validateStartTime,
+                                      nsecs_t validateEndTime) = 0;
+    // Reports the start and end times of a hwc present call this frame for a given display
+    virtual void setHwcPresentTiming(DisplayId displayId, nsecs_t presentStartTime,
+                                     nsecs_t presentEndTime) = 0;
+    // Reports the expected time that the current frame will present to the display
+    virtual void setExpectedPresentTime(nsecs_t expectedPresentTime) = 0;
+    // Reports the most recent present fence time and end time once known
+    virtual void setSfPresentTiming(nsecs_t presentFenceTime, nsecs_t presentEndTime) = 0;
+    // Reports whether a display used client composition this frame
+    virtual void setRequiresClientComposition(DisplayId displayId,
+                                              bool requiresClientComposition) = 0;
+    // Reports whether a given display skipped validation this frame
+    virtual void setSkippedValidate(DisplayId displayId, bool skipped) = 0;
+    // Reports when a hwc present is delayed, and the time that it will resume
+    virtual void setHwcPresentDelayedTime(
+            DisplayId displayId, std::chrono::steady_clock::time_point earliestFrameStartTime) = 0;
+    // Reports the start delay for SurfaceFlinger this frame
+    virtual void setFrameDelay(nsecs_t frameDelayDuration) = 0;
+    // Reports the SurfaceFlinger commit start time this frame
+    virtual void setCommitStart(nsecs_t commitStartTime) = 0;
+    // Reports the SurfaceFlinger composite end time this frame
+    virtual void setCompositeEnd(nsecs_t compositeEndTime) = 0;
+    // Reports the list of the currently active displays
+    virtual void setDisplays(std::vector<DisplayId>& displayIds) = 0;
+    // Sets the target duration for the entire pipeline including the gpu
+    virtual void setTotalFrameTargetWorkDuration(nsecs_t targetDuration) = 0;
 };
 
 namespace impl {
@@ -70,12 +111,11 @@
         virtual void restartPowerHintSession() = 0;
         virtual void setPowerHintSessionThreadIds(const std::vector<int32_t>& threadIds) = 0;
         virtual bool startPowerHintSession() = 0;
-        virtual void setTargetWorkDuration(int64_t targetDurationNanos) = 0;
-        virtual void sendActualWorkDuration(int64_t actualDurationNanos,
-                                            nsecs_t timeStampNanos) = 0;
+        virtual void setTargetWorkDuration(nsecs_t targetDuration) = 0;
+        virtual void sendActualWorkDuration(nsecs_t actualDuration, nsecs_t timestamp) = 0;
         virtual bool shouldReconnectHAL() = 0;
         virtual std::vector<int32_t> getPowerHintSessionThreadIds() = 0;
-        virtual std::optional<int64_t> getTargetWorkDuration() = 0;
+        virtual std::optional<nsecs_t> getTargetWorkDuration() = 0;
     };
 
     PowerAdvisor(SurfaceFlinger& flinger);
@@ -89,26 +129,43 @@
     bool usePowerHintSession() override;
     bool supportsPowerHintSession() override;
     bool isPowerHintSessionRunning() override;
-    void setTargetWorkDuration(int64_t targetDurationNanos) override;
-    void sendActualWorkDuration(int64_t actualDurationNanos, nsecs_t timestamp) override;
+    void setTargetWorkDuration(nsecs_t targetDuration) override;
+    void sendActualWorkDuration() override;
+    void sendPredictedWorkDuration() override;
     void enablePowerHint(bool enabled) override;
     bool startPowerHintSession(const std::vector<int32_t>& threadIds) override;
+    void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime);
+    void setHwcValidateTiming(DisplayId displayId, nsecs_t valiateStartTime,
+                              nsecs_t validateEndTime) override;
+    void setHwcPresentTiming(DisplayId displayId, nsecs_t presentStartTime,
+                             nsecs_t presentEndTime) override;
+    void setSkippedValidate(DisplayId displayId, bool skipped) override;
+    void setRequiresClientComposition(DisplayId displayId, bool requiresClientComposition) override;
+    void setExpectedPresentTime(nsecs_t expectedPresentTime) override;
+    void setSfPresentTiming(nsecs_t presentFenceTime, nsecs_t presentEndTime) override;
+    void setHwcPresentDelayedTime(
+            DisplayId displayId,
+            std::chrono::steady_clock::time_point earliestFrameStartTime) override;
+
+    void setFrameDelay(nsecs_t frameDelayDuration) override;
+    void setCommitStart(nsecs_t commitStartTime) override;
+    void setCompositeEnd(nsecs_t compositeEndTime) override;
+    void setDisplays(std::vector<DisplayId>& displayIds) override;
+    void setTotalFrameTargetWorkDuration(nsecs_t targetDuration) override;
 
 private:
+    friend class PowerAdvisorTest;
+
+    // Tracks if powerhal exists
+    bool mHasHal = true;
+    // Holds the hal wrapper for getPowerHal
+    std::unique_ptr<HalWrapper> mHalWrapper GUARDED_BY(mPowerHalMutex) = nullptr;
+
     HalWrapper* getPowerHal() REQUIRES(mPowerHalMutex);
     bool mReconnectPowerHal GUARDED_BY(mPowerHalMutex) = false;
     std::mutex mPowerHalMutex;
 
     std::atomic_bool mBootFinished = false;
-    std::optional<bool> mPowerHintEnabled;
-    std::optional<bool> mSupportsPowerHint;
-    bool mPowerHintSessionRunning = false;
-
-    // An adjustable safety margin which moves the "target" earlier to allow flinger to
-    // go a bit over without dropping a frame, especially since we can't measure
-    // the exact time HWC finishes composition so "actual" durations are measured
-    // from the end of present() instead, which is a bit later.
-    static constexpr const std::chrono::nanoseconds kTargetSafetyMargin = 2ms;
 
     std::unordered_set<DisplayId> mExpensiveDisplays;
     bool mNotifiedExpensiveRendering = false;
@@ -117,6 +174,110 @@
     std::atomic_bool mSendUpdateImminent = true;
     std::atomic<nsecs_t> mLastScreenUpdatedTime = 0;
     std::optional<scheduler::OneShotTimer> mScreenUpdateTimer;
+
+    // Higher-level timing data used for estimation
+    struct DisplayTimeline {
+        // The start of hwc present, or the start of validate if it happened there instead
+        nsecs_t hwcPresentStartTime = -1;
+        // The end of hwc present or validate, whichever one actually presented
+        nsecs_t hwcPresentEndTime = -1;
+        // How long the actual hwc present was delayed after hwcPresentStartTime
+        nsecs_t hwcPresentDelayDuration = 0;
+        // When we think we started waiting for the present fence after calling into hwc present and
+        // after potentially waiting for the earliest present time
+        nsecs_t presentFenceWaitStartTime = -1;
+        // How long we ran after we finished waiting for the fence but before hwc present finished
+        nsecs_t postPresentFenceHwcPresentDuration = 0;
+        // Are we likely to have waited for the present fence during composition
+        bool probablyWaitsForPresentFence = false;
+        // Estimate one frame's timeline from that of a previous frame
+        DisplayTimeline estimateTimelineFromReference(nsecs_t fenceTime, nsecs_t displayStartTime);
+    };
+
+    struct GpuTimeline {
+        nsecs_t duration = 0;
+        nsecs_t startTime = -1;
+    };
+
+    // Power hint session data recorded from the pipeline
+    struct DisplayTimingData {
+        std::unique_ptr<FenceTime> gpuEndFenceTime;
+        std::optional<nsecs_t> gpuStartTime;
+        std::optional<nsecs_t> lastValidGpuEndTime;
+        std::optional<nsecs_t> lastValidGpuStartTime;
+        std::optional<nsecs_t> hwcPresentStartTime;
+        std::optional<nsecs_t> hwcPresentEndTime;
+        std::optional<nsecs_t> hwcValidateStartTime;
+        std::optional<nsecs_t> hwcValidateEndTime;
+        std::optional<nsecs_t> hwcPresentDelayedTime;
+        bool usedClientComposition = false;
+        bool skippedValidate = false;
+        // Calculate high-level timing milestones from more granular display timing data
+        DisplayTimeline calculateDisplayTimeline(nsecs_t fenceTime);
+        // Estimate the gpu duration for a given display from previous gpu timing data
+        std::optional<GpuTimeline> estimateGpuTiming(std::optional<nsecs_t> previousEnd);
+    };
+
+    template <class T, size_t N>
+    class RingBuffer {
+        std::array<T, N> elements = {};
+        size_t mIndex = 0;
+        size_t numElements = 0;
+
+    public:
+        void append(T item) {
+            mIndex = (mIndex + 1) % N;
+            numElements = std::min(N, numElements + 1);
+            elements[mIndex] = item;
+        }
+        bool isFull() const { return numElements == N; }
+        // Allows access like [0] == current, [-1] = previous, etc..
+        T& operator[](int offset) {
+            size_t positiveOffset =
+                    static_cast<size_t>((offset % static_cast<int>(N)) + static_cast<int>(N));
+            return elements[(mIndex + positiveOffset) % N];
+        }
+    };
+
+    // Filter and sort the display ids by a given property
+    std::vector<DisplayId> getOrderedDisplayIds(std::optional<nsecs_t> DisplayTimingData::*sortBy);
+    // Estimates a frame's total work duration including gpu time.
+    // Runs either at the beginning or end of a frame, using the most recent data available
+    std::optional<nsecs_t> estimateWorkDuration(bool earlyHint);
+    // There are two different targets and actual work durations we care about,
+    // this normalizes them together and takes the max of the two
+    nsecs_t combineTimingEstimates(nsecs_t totalDuration, nsecs_t flingerDuration);
+
+    std::unordered_map<DisplayId, DisplayTimingData> mDisplayTimingData;
+
+    // Current frame's delay
+    nsecs_t mFrameDelayDuration = 0;
+    // Last frame's post-composition duration
+    nsecs_t mLastPostcompDuration = 0;
+    // Buffer of recent commit start times
+    RingBuffer<nsecs_t, 2> mCommitStartTimes;
+    // Buffer of recent expected present times
+    RingBuffer<nsecs_t, 2> mExpectedPresentTimes;
+    // Most recent present fence time, set at the end of the frame once known
+    nsecs_t mLastPresentFenceTime = -1;
+    // Most recent present fence time, set at the end of the frame once known
+    nsecs_t mLastSfPresentEndTime = -1;
+    // Target for the entire pipeline including gpu
+    std::optional<nsecs_t> mTotalFrameTargetDuration;
+    // Updated list of display IDs
+    std::vector<DisplayId> mDisplayIds;
+
+    std::optional<bool> mPowerHintEnabled;
+    std::optional<bool> mSupportsPowerHint;
+    bool mPowerHintSessionRunning = false;
+
+    // An adjustable safety margin which pads the "actual" value sent to PowerHAL,
+    // encouraging more aggressive boosting to give SurfaceFlinger a larger margin for error
+    static constexpr const std::chrono::nanoseconds kTargetSafetyMargin = 1ms;
+
+    // How long we expect hwc to run after the present call until it waits for the fence
+    static constexpr const std::chrono::nanoseconds kFenceWaitStartDelayValidated = 150us;
+    static constexpr const std::chrono::nanoseconds kFenceWaitStartDelaySkippedValidate = 250us;
 };
 
 class AidlPowerHalWrapper : public PowerAdvisor::HalWrapper {
@@ -133,50 +294,50 @@
     void restartPowerHintSession() override;
     void setPowerHintSessionThreadIds(const std::vector<int32_t>& threadIds) override;
     bool startPowerHintSession() override;
-    void setTargetWorkDuration(int64_t targetDurationNanos) override;
-    void sendActualWorkDuration(int64_t actualDurationNanos, nsecs_t timeStampNanos) override;
+    void setTargetWorkDuration(nsecs_t targetDuration) override;
+    void sendActualWorkDuration(nsecs_t actualDuration, nsecs_t timestamp) override;
     bool shouldReconnectHAL() override;
     std::vector<int32_t> getPowerHintSessionThreadIds() override;
-    std::optional<int64_t> getTargetWorkDuration() override;
+    std::optional<nsecs_t> getTargetWorkDuration() override;
 
 private:
+    friend class AidlPowerHalWrapperTest;
+
     bool checkPowerHintSessionSupported();
     void closePowerHintSession();
-    bool shouldReportActualDurationsNow();
-    bool shouldSetTargetDuration(int64_t targetDurationNanos);
+    bool shouldReportActualDurations();
+
+    // Used for testing
+    void setAllowedActualDeviation(nsecs_t);
 
     const sp<hardware::power::IPower> mPowerHal = nullptr;
     bool mHasExpensiveRendering = false;
     bool mHasDisplayUpdateImminent = false;
     // Used to indicate an error state and need for reconstruction
     bool mShouldReconnectHal = false;
-    // This is not thread safe, but is currently protected by mPowerHalMutex so it needs no lock
+
+    // Power hint session data
+
+    // Concurrent access for this is protected by mPowerHalMutex
     sp<hardware::power::IPowerHintSession> mPowerHintSession = nullptr;
     // Queue of actual durations saved to report
     std::vector<hardware::power::WorkDuration> mPowerHintQueue;
-    // The latest un-normalized values we have received for target and actual
-    int64_t mTargetDuration = kDefaultTarget.count();
-    std::optional<int64_t> mActualDuration;
+    // The latest values we have received for target and actual
+    nsecs_t mTargetDuration = kDefaultTarget.count();
+    std::optional<nsecs_t> mActualDuration;
     // The list of thread ids, stored so we can restart the session from this class if needed
     std::vector<int32_t> mPowerHintThreadIds;
-    bool mSupportsPowerHint;
+    bool mSupportsPowerHint = false;
     // Keep track of the last messages sent for rate limiter change detection
-    std::optional<int64_t> mLastActualDurationSent;
-    // timestamp of the last report we sent, used to avoid stale sessions
-    int64_t mLastActualReportTimestamp = 0;
-    int64_t mLastTargetDurationSent = kDefaultTarget.count();
-    // Whether to normalize all the actual values as error terms relative to a constant target
-    // This saves a binder call by not setting the target, and should not affect the pid values
-    static const bool sNormalizeTarget;
+    std::optional<nsecs_t> mLastActualDurationSent;
+    // Timestamp of the last report we sent, used to avoid stale sessions
+    nsecs_t mLastActualReportTimestamp = 0;
+    nsecs_t mLastTargetDurationSent = kDefaultTarget.count();
+    // Max amount the error term can vary without causing an actual value report
+    nsecs_t mAllowedActualDeviation = -1;
     // Whether we should emit ATRACE_INT data for hint sessions
     static const bool sTraceHintSessionData;
-
-    // Max percent the actual duration can vary without causing a report (eg: 0.1 = 10%)
-    static constexpr double kAllowedActualDeviationPercent = 0.1;
-    // Max percent the target duration can vary without causing a report (eg: 0.1 = 10%)
-    static constexpr double kAllowedTargetDeviationPercent = 0.1;
-    // Target used for init and normalization, the actual value does not really matter
-    static constexpr const std::chrono::nanoseconds kDefaultTarget = 50ms;
+    static constexpr const std::chrono::nanoseconds kDefaultTarget = 16ms;
     // Amount of time after the last message was sent before the session goes stale
     // actually 100ms but we use 80 here to ideally avoid going stale
     static constexpr const std::chrono::nanoseconds kStaleTimeout = 80ms;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index be16942..aff94d1 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -81,6 +81,7 @@
 namespace android {
 namespace {
 constexpr int kDumpTableRowLength = 159;
+const ui::Transform kIdentityTransform;
 } // namespace
 
 using namespace ftl::flag_operators;
@@ -405,7 +406,7 @@
     const auto& drawingState{getDrawingState()};
     const auto alpha = static_cast<float>(getAlpha());
     const bool opaque = isOpaque(drawingState);
-    const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f;
+    const bool usesRoundedCorners = hasRoundedCorners();
 
     auto blendMode = Hwc2::IComposerClient::BlendMode::NONE;
     if (!opaque || alpha != 1.0f) {
@@ -481,7 +482,7 @@
     compositionState->hasProtectedContent = isProtected();
     compositionState->dimmingEnabled = isDimmingEnabled();
 
-    const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f;
+    const bool usesRoundedCorners = hasRoundedCorners();
 
     compositionState->isOpaque =
             isOpaque(drawingState) && !usesRoundedCorners && getAlpha() == 1.0_hf;
@@ -1914,27 +1915,22 @@
     const auto& parent = mDrawingParent.promote();
     if (parent != nullptr) {
         parentSettings = parent->getRoundedCornerState();
-        if (parentSettings.radius > 0) {
+        if (parentSettings.hasRoundedCorners()) {
             ui::Transform t = getActiveTransform(getDrawingState());
             t = t.inverse();
             parentSettings.cropRect = t.transform(parentSettings.cropRect);
-            // The rounded corners shader only accepts 1 corner radius for performance reasons,
-            // but a transform matrix can define horizontal and vertical scales.
-            // Let's take the average between both of them and pass into the shader, practically we
-            // never do this type of transformation on windows anyway.
-            auto scaleX = sqrtf(t[0][0] * t[0][0] + t[0][1] * t[0][1]);
-            auto scaleY = sqrtf(t[1][0] * t[1][0] + t[1][1] * t[1][1]);
-            parentSettings.radius *= (scaleX + scaleY) / 2.0f;
+            parentSettings.radius.x *= t.getScaleX();
+            parentSettings.radius.y *= t.getScaleY();
         }
     }
 
     // Get layer settings
     Rect layerCropRect = getCroppedBufferSize(getDrawingState());
-    const float radius = getDrawingState().cornerRadius;
+    const vec2 radius(getDrawingState().cornerRadius, getDrawingState().cornerRadius);
     RoundedCornerState layerSettings(layerCropRect.toFloatRect(), radius);
-    const bool layerSettingsValid = layerSettings.radius > 0 && layerCropRect.isValid();
+    const bool layerSettingsValid = layerSettings.hasRoundedCorners() && layerCropRect.isValid();
 
-    if (layerSettingsValid && parentSettings.radius > 0) {
+    if (layerSettingsValid && parentSettings.hasRoundedCorners()) {
         // If the parent and the layer have rounded corner settings, use the parent settings if the
         // parent crop is entirely inside the layer crop.
         // This has limitations and cause rendering artifacts. See b/200300845 for correct fix.
@@ -1948,7 +1944,7 @@
         }
     } else if (layerSettingsValid) {
         return layerSettings;
-    } else if (parentSettings.radius > 0) {
+    } else if (parentSettings.hasRoundedCorners()) {
         return parentSettings;
     }
     return {};
@@ -2069,7 +2065,8 @@
     layerInfo->set_effective_scaling_mode(getEffectiveScalingMode());
 
     layerInfo->set_requested_corner_radius(getDrawingState().cornerRadius);
-    layerInfo->set_corner_radius(getRoundedCornerState().radius);
+    layerInfo->set_corner_radius(
+            (getRoundedCornerState().radius.x + getRoundedCornerState().radius.y) / 2.0);
     layerInfo->set_background_blur_radius(getBackgroundBlurRadius());
     layerInfo->set_is_trusted_overlay(isTrustedOverlay());
     LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform());
@@ -2162,7 +2159,8 @@
     if ((traceFlags & LayerTracing::TRACE_INPUT) && needsInputInfo()) {
         WindowInfo info;
         if (useDrawing) {
-            info = fillInputInfo(ui::Transform(), /* displayIsSecure */ true);
+            info = fillInputInfo(
+                    InputDisplayArgs{.transform = &kIdentityTransform, .isSecure = true});
         } else {
             info = state.inputInfo;
         }
@@ -2369,7 +2367,7 @@
     }
 }
 
-WindowInfo Layer::fillInputInfo(const ui::Transform& displayTransform, bool displayIsSecure) {
+WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) {
     if (!hasInputInfo()) {
         mDrawingState.inputInfo.name = getName();
         mDrawingState.inputInfo.ownerUid = mOwnerUid;
@@ -2378,12 +2376,21 @@
         mDrawingState.inputInfo.displayId = getLayerStack().id;
     }
 
+    const ui::Transform& displayTransform =
+            displayArgs.transform != nullptr ? *displayArgs.transform : kIdentityTransform;
+
     WindowInfo info = mDrawingState.inputInfo;
     info.id = sequence;
     info.displayId = getLayerStack().id;
 
     fillInputFrameInfo(info, displayTransform);
 
+    if (displayArgs.transform == nullptr) {
+        // Do not let the window receive touches if it is not associated with a valid display
+        // transform. We still allow the window to receive keys and prevent ANRs.
+        info.inputConfig |= WindowInfo::InputConfig::NOT_TOUCHABLE;
+    }
+
     // For compatibility reasons we let layers which can receive input
     // receive input before they have actually submitted a buffer. Because
     // of this we use canReceiveInput instead of isVisible to check the
@@ -2401,7 +2408,7 @@
 
     // If the window will be blacked out on a display because the display does not have the secure
     // flag and the layer has the secure flag set, then drop input.
-    if (!displayIsSecure && isSecure()) {
+    if (!displayArgs.isSecure && isSecure()) {
         info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
     }
 
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 24abad9..200baf0 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -134,13 +134,14 @@
 
     struct RoundedCornerState {
         RoundedCornerState() = default;
-        RoundedCornerState(FloatRect cropRect, float radius)
+        RoundedCornerState(const FloatRect& cropRect, const vec2& radius)
               : cropRect(cropRect), radius(radius) {}
 
         // Rounded rectangle in local layer coordinate space.
         FloatRect cropRect = FloatRect();
         // Radius of the rounded rectangle.
-        float radius = 0.0f;
+        vec2 radius;
+        bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; }
     };
 
     using FrameRate = scheduler::LayerInfo::FrameRate;
@@ -596,7 +597,7 @@
     // corner crop does not intersect with its own rounded corner crop.
     virtual RoundedCornerState getRoundedCornerState() const;
 
-    bool hasRoundedCorners() const override { return getRoundedCornerState().radius > .0f; }
+    bool hasRoundedCorners() const override { return getRoundedCornerState().hasRoundedCorners(); }
 
     virtual PixelFormat getPixelFormat() const { return PIXEL_FORMAT_NONE; }
     /**
@@ -844,7 +845,11 @@
     bool getPremultipledAlpha() const;
     void setInputInfo(const gui::WindowInfo& info);
 
-    gui::WindowInfo fillInputInfo(const ui::Transform& displayTransform, bool displayIsSecure);
+    struct InputDisplayArgs {
+        const ui::Transform* transform = nullptr;
+        bool isSecure = false;
+    };
+    gui::WindowInfo fillInputInfo(const InputDisplayArgs& displayArgs);
 
     /**
      * Returns whether this layer has an explicitly set input-info.
diff --git a/services/surfaceflinger/Scheduler/DispSyncSource.cpp b/services/surfaceflinger/Scheduler/DispSyncSource.cpp
index 747032b..4af1f5c 100644
--- a/services/surfaceflinger/Scheduler/DispSyncSource.cpp
+++ b/services/surfaceflinger/Scheduler/DispSyncSource.cpp
@@ -188,10 +188,10 @@
 
 VSyncSource::VSyncData DispSyncSource::getLatestVSyncData() const {
     std::lock_guard lock(mVsyncMutex);
-    nsecs_t expectedPresentTime = mVSyncTracker.nextAnticipatedVSyncTimeFrom(
+    nsecs_t expectedPresentationTime = mVSyncTracker.nextAnticipatedVSyncTimeFrom(
             systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
-    nsecs_t deadline = expectedPresentTime - mWorkDuration.get().count() - mReadyDuration.count();
-    return {expectedPresentTime, deadline};
+    nsecs_t deadline = expectedPresentationTime - mReadyDuration.count();
+    return {expectedPresentationTime, deadline};
 }
 
 void DispSyncSource::dump(std::string& result) const {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index ca83496..a48c921 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -40,22 +40,26 @@
 
 struct RefreshRateScore {
     DisplayModeIterator modeIt;
-    float score;
+    float overallScore;
+    struct {
+        float modeBelowThreshold;
+        float modeAboveThreshold;
+    } fixedRateBelowThresholdLayersScore;
 };
 
 template <typename Iterator>
 const DisplayModePtr& getMaxScoreRefreshRate(Iterator begin, Iterator end) {
     const auto it =
             std::max_element(begin, end, [](RefreshRateScore max, RefreshRateScore current) {
-                const auto& [modeIt, score] = current;
+                const auto& [modeIt, overallScore, _] = current;
 
                 std::string name = to_string(modeIt->second->getFps());
-                ALOGV("%s scores %.2f", name.c_str(), score);
+                ALOGV("%s scores %.2f", name.c_str(), overallScore);
 
-                ATRACE_INT(name.c_str(), static_cast<int>(std::round(score * 100)));
+                ATRACE_INT(name.c_str(), static_cast<int>(std::round(overallScore * 100)));
 
                 constexpr float kEpsilon = 0.0001f;
-                return score > max.score * (1 + kEpsilon);
+                return overallScore > max.overallScore * (1 + kEpsilon);
             });
 
     return it->modeIt->second;
@@ -151,31 +155,6 @@
     return {quotient, remainder};
 }
 
-bool RefreshRateConfigs::isVoteAllowed(const LayerRequirement& layer, Fps refreshRate) const {
-    using namespace fps_approx_ops;
-
-    switch (layer.vote) {
-        case LayerVoteType::ExplicitExactOrMultiple:
-        case LayerVoteType::Heuristic:
-            if (mConfig.frameRateMultipleThreshold != 0 &&
-                refreshRate >= Fps::fromValue(mConfig.frameRateMultipleThreshold) &&
-                layer.desiredRefreshRate < Fps::fromValue(mConfig.frameRateMultipleThreshold / 2)) {
-                // Don't vote high refresh rates past the threshold for layers with a low desired
-                // refresh rate. For example, desired 24 fps with 120 Hz threshold means no vote for
-                // 120 Hz, but desired 60 fps should have a vote.
-                return false;
-            }
-            break;
-        case LayerVoteType::ExplicitDefault:
-        case LayerVoteType::ExplicitExact:
-        case LayerVoteType::Max:
-        case LayerVoteType::Min:
-        case LayerVoteType::NoVote:
-            break;
-    }
-    return true;
-}
-
 float RefreshRateConfigs::calculateNonExactMatchingLayerScoreLocked(const LayerRequirement& layer,
                                                                     Fps refreshRate) const {
     constexpr float kScoreForFractionalPairs = .8f;
@@ -240,10 +219,6 @@
 
 float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate,
                                                     bool isSeamlessSwitch) const {
-    if (!isVoteAllowed(layer, refreshRate)) {
-        return 0;
-    }
-
     // Slightly prefer seamless switches.
     constexpr float kSeamedSwitchPenalty = 0.95f;
     const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
@@ -300,6 +275,7 @@
 auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vector<LayerRequirement>& layers,
                                                   GlobalSignals signals) const
         -> std::pair<DisplayModePtr, GlobalSignals> {
+    using namespace fps_approx_ops;
     ATRACE_CALL();
     ALOGV("%s: %zu layers", __func__, layers.size());
 
@@ -409,7 +385,7 @@
 
         const auto weight = layer.weight;
 
-        for (auto& [modeIt, score] : scores) {
+        for (auto& [modeIt, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
             const auto& [id, mode] = *modeIt;
             const bool isSeamlessSwitch = mode->getGroup() == mActiveModeIt->second->getGroup();
 
@@ -451,18 +427,92 @@
                 continue;
             }
 
-            const auto layerScore =
+            const float layerScore =
                     calculateLayerScoreLocked(layer, mode->getFps(), isSeamlessSwitch);
-            ALOGV("%s gives %s score of %.4f", formatLayerInfo(layer, weight).c_str(),
-                  to_string(mode->getFps()).c_str(), layerScore);
+            const float weightedLayerScore = weight * layerScore;
 
-            score += weight * layerScore;
+            // Layer with fixed source has a special consideration which depends on the
+            // mConfig.frameRateMultipleThreshold. We don't want these layers to score
+            // refresh rates above the threshold, but we also don't want to favor the lower
+            // ones by having a greater number of layers scoring them. Instead, we calculate
+            // the score independently for these layers and later decide which
+            // refresh rates to add it. For example, desired 24 fps with 120 Hz threshold should not
+            // score 120 Hz, but desired 60 fps should contribute to the score.
+            const bool fixedSourceLayer = [](LayerVoteType vote) {
+                switch (vote) {
+                    case LayerVoteType::ExplicitExactOrMultiple:
+                    case LayerVoteType::Heuristic:
+                        return true;
+                    case LayerVoteType::NoVote:
+                    case LayerVoteType::Min:
+                    case LayerVoteType::Max:
+                    case LayerVoteType::ExplicitDefault:
+                    case LayerVoteType::ExplicitExact:
+                        return false;
+                }
+            }(layer.vote);
+            const bool layerBelowThreshold = mConfig.frameRateMultipleThreshold != 0 &&
+                    layer.desiredRefreshRate <
+                            Fps::fromValue(mConfig.frameRateMultipleThreshold / 2);
+            if (fixedSourceLayer && layerBelowThreshold) {
+                const bool modeAboveThreshold =
+                        mode->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
+                if (modeAboveThreshold) {
+                    ALOGV("%s gives %s fixed source (above threshold) score of %.4f",
+                          formatLayerInfo(layer, weight).c_str(), to_string(mode->getFps()).c_str(),
+                          layerScore);
+                    fixedRateBelowThresholdLayersScore.modeAboveThreshold += weightedLayerScore;
+                } else {
+                    ALOGV("%s gives %s fixed source (below threshold) score of %.4f",
+                          formatLayerInfo(layer, weight).c_str(), to_string(mode->getFps()).c_str(),
+                          layerScore);
+                    fixedRateBelowThresholdLayersScore.modeBelowThreshold += weightedLayerScore;
+                }
+            } else {
+                ALOGV("%s gives %s score of %.4f", formatLayerInfo(layer, weight).c_str(),
+                      to_string(mode->getFps()).c_str(), layerScore);
+                overallScore += weightedLayerScore;
+            }
         }
     }
 
-    // Now that we scored all the refresh rates we need to pick the one that got the highest score.
-    // In case of a tie we will pick the higher refresh rate if any of the layers wanted Max,
-    // or the lower otherwise.
+    // We want to find the best refresh rate without the fixed source layers,
+    // so we could know whether we should add the modeAboveThreshold scores or not.
+    // If the best refresh rate is already above the threshold, it means that
+    // some non-fixed source layers already scored it, so we can just add the score
+    // for all fixed source layers, even the ones that are above the threshold.
+    const bool maxScoreAboveThreshold = [&] {
+        if (mConfig.frameRateMultipleThreshold == 0 || scores.empty()) {
+            return false;
+        }
+
+        const auto maxScoreIt =
+                std::max_element(scores.begin(), scores.end(),
+                                 [](RefreshRateScore max, RefreshRateScore current) {
+                                     const auto& [modeIt, overallScore, _] = current;
+                                     return overallScore > max.overallScore;
+                                 });
+        ALOGV("%s is the best refresh rate without fixed source layers. It is %s the threshold for "
+              "refresh rate multiples",
+              to_string(maxScoreIt->modeIt->second->getFps()).c_str(),
+              maxScoreAboveThreshold ? "above" : "below");
+        return maxScoreIt->modeIt->second->getFps() >=
+                Fps::fromValue(mConfig.frameRateMultipleThreshold);
+    }();
+
+    // Now we can add the fixed rate layers score
+    for (auto& [modeIt, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
+        overallScore += fixedRateBelowThresholdLayersScore.modeBelowThreshold;
+        if (maxScoreAboveThreshold) {
+            overallScore += fixedRateBelowThresholdLayersScore.modeAboveThreshold;
+        }
+        ALOGV("%s adjusted overallScore is %.4f", to_string(modeIt->second->getFps()).c_str(),
+              overallScore);
+    }
+
+    // Now that we scored all the refresh rates we need to pick the one that got the highest
+    // overallScore. In case of a tie we will pick the higher refresh rate if any of the layers
+    // wanted Max, or the lower otherwise.
     const DisplayModePtr& bestRefreshRate = maxVoteLayers > 0
             ? getMaxScoreRefreshRate(scores.rbegin(), scores.rend())
             : getMaxScoreRefreshRate(scores.begin(), scores.end());
@@ -471,7 +521,7 @@
         // If we never scored any layers, then choose the rate from the primary
         // range instead of picking a random score from the app range.
         if (std::all_of(scores.begin(), scores.end(),
-                        [](RefreshRateScore score) { return score.score == 0; })) {
+                        [](RefreshRateScore score) { return score.overallScore == 0; })) {
             const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup);
             ALOGV("layers not scored - choose %s", to_string(max->getFps()).c_str());
             return {max, kNoSignals};
@@ -575,7 +625,7 @@
             continue;
         }
 
-        for (auto& [_, score] : scores) {
+        for (auto& [_, score, _1] : scores) {
             score = 0;
         }
 
@@ -587,7 +637,7 @@
             LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault &&
                                 layer->vote != LayerVoteType::ExplicitExactOrMultiple &&
                                 layer->vote != LayerVoteType::ExplicitExact);
-            for (auto& [modeIt, score] : scores) {
+            for (auto& [modeIt, score, _] : scores) {
                 constexpr bool isSeamlessSwitch = true;
                 const auto layerScore = calculateLayerScoreLocked(*layer, modeIt->second->getFps(),
                                                                   isSeamlessSwitch);
@@ -605,7 +655,7 @@
 
         // If we never scored any layers, we don't have a preferred frame rate
         if (std::all_of(scores.begin(), scores.end(),
-                        [](RefreshRateScore score) { return score.score == 0; })) {
+                        [](RefreshRateScore score) { return score.overallScore == 0; })) {
             continue;
         }
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 05a8692..a79002e 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -353,9 +353,6 @@
     const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
     bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock);
 
-    // Returns whether the layer is allowed to vote for the given refresh rate.
-    bool isVoteAllowed(const LayerRequirement&, Fps) const;
-
     // calculates a score for a layer. Used to determine the display refresh rate
     // and the frame rate override for certains applications.
     float calculateLayerScoreLocked(const LayerRequirement&, Fps refreshRate,
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 37f0fec..8b1a5d9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -25,6 +25,7 @@
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
 #include <configstore/Utils.h>
+#include <ftl/fake_guard.h>
 #include <gui/WindowInfo.h>
 #include <system/window.h>
 #include <ui/DisplayStatInfo.h>
@@ -94,9 +95,13 @@
 }
 
 void Scheduler::setRefreshRateConfigs(std::shared_ptr<RefreshRateConfigs> configs) {
+    // The current RefreshRateConfigs instance may outlive this call, so unbind its idle timer.
     {
-        // The current RefreshRateConfigs instance may outlive this call, so unbind its idle timer.
-        std::scoped_lock lock(mRefreshRateConfigsLock);
+        // mRefreshRateConfigsLock is not locked here to avoid the deadlock
+        // as the callback can attempt to acquire the lock before stopIdleTimer can finish
+        // the execution. It's safe to FakeGuard as main thread is the only thread that
+        // writes to the mRefreshRateConfigs.
+        ftl::FakeGuard guard(mRefreshRateConfigsLock);
         if (mRefreshRateConfigs) {
             mRefreshRateConfigs->stopIdleTimer();
             mRefreshRateConfigs->clearIdleTimerCallbacks();
@@ -554,11 +559,12 @@
     }
 }
 
-void Scheduler::setDisplayPowerState(bool normal) {
+void Scheduler::setDisplayPowerMode(hal::PowerMode powerMode) {
     {
         std::lock_guard<std::mutex> lock(mPolicyLock);
-        mPolicy.isDisplayPowerStateNormal = normal;
+        mPolicy.displayPowerMode = powerMode;
     }
+    mVsyncSchedule->getController().setDisplayPowerMode(powerMode);
 
     if (mDisplayPowerTimer) {
         mDisplayPowerTimer->reset();
@@ -706,7 +712,8 @@
     // If Display Power is not in normal operation we want to be in performance mode. When coming
     // back to normal mode, a grace period is given with DisplayPowerTimer.
     if (mDisplayPowerTimer &&
-        (!mPolicy.isDisplayPowerStateNormal || mPolicy.displayPowerTimer == TimerState::Reset)) {
+        (mPolicy.displayPowerMode != hal::PowerMode::ON ||
+         mPolicy.displayPowerTimer == TimerState::Reset)) {
         constexpr GlobalSignals kNoSignals;
         return {configs->getMaxRefreshRateByPolicy(), kNoSignals};
     }
@@ -720,9 +727,7 @@
 DisplayModePtr Scheduler::getPreferredDisplayMode() {
     std::lock_guard<std::mutex> lock(mPolicyLock);
     // Make sure the stored mode is up to date.
-    if (mPolicy.mode) {
-        mPolicy.mode = chooseDisplayMode().first;
-    }
+    mPolicy.mode = chooseDisplayMode().first;
     return mPolicy.mode;
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 0c72124..a8043bf 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -186,7 +186,7 @@
     // Indicates that touch interaction is taking place.
     void onTouchHint();
 
-    void setDisplayPowerState(bool normal);
+    void setDisplayPowerMode(hal::PowerMode powerMode);
 
     VSyncDispatch& getVsyncDispatch() { return mVsyncSchedule->getDispatch(); }
 
@@ -325,7 +325,7 @@
         TimerState idleTimer = TimerState::Reset;
         TouchState touch = TouchState::Inactive;
         TimerState displayPowerTimer = TimerState::Expired;
-        bool isDisplayPowerStateNormal = true;
+        hal::PowerMode displayPowerMode = hal::PowerMode::ON;
 
         // Chosen display mode.
         DisplayModePtr mode;
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index bdcab51..13cd304 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -146,6 +146,11 @@
         return false;
     }
 
+    if (mDisplayPowerMode == hal::PowerMode::DOZE ||
+        mDisplayPowerMode == hal::PowerMode::DOZE_SUSPEND) {
+        return true;
+    }
+
     if (!mLastHwVsync && !HwcVsyncPeriod) {
         return false;
     }
@@ -206,6 +211,11 @@
     return mMoreSamplesNeeded;
 }
 
+void VSyncReactor::setDisplayPowerMode(hal::PowerMode powerMode) {
+    std::scoped_lock lock(mMutex);
+    mDisplayPowerMode = powerMode;
+}
+
 void VSyncReactor::dump(std::string& result) const {
     std::lock_guard lock(mMutex);
     StringAppendF(&result, "VsyncReactor in use\n");
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.h b/services/surfaceflinger/Scheduler/VSyncReactor.h
index 6a1950a..4501487 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.h
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.h
@@ -49,6 +49,8 @@
     bool addHwVsyncTimestamp(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod,
                              bool* periodFlushed) final;
 
+    void setDisplayPowerMode(hal::PowerMode powerMode) final;
+
     void dump(std::string& result) const final;
 
 private:
@@ -73,6 +75,8 @@
     std::optional<nsecs_t> mPeriodTransitioningTo GUARDED_BY(mMutex);
     std::optional<nsecs_t> mLastHwVsync GUARDED_BY(mMutex);
 
+    hal::PowerMode mDisplayPowerMode GUARDED_BY(mMutex) = hal::PowerMode::ON;
+
     const bool mSupportKernelIdleTimer = false;
 };
 
diff --git a/services/surfaceflinger/Scheduler/VsyncController.h b/services/surfaceflinger/Scheduler/VsyncController.h
index 59f6537..726a420 100644
--- a/services/surfaceflinger/Scheduler/VsyncController.h
+++ b/services/surfaceflinger/Scheduler/VsyncController.h
@@ -18,7 +18,10 @@
 
 #include <cstddef>
 #include <memory>
+#include <mutex>
 
+#include <DisplayHardware/HWComposer.h>
+#include <DisplayHardware/Hal.h>
 #include <ui/FenceTime.h>
 #include <utils/Mutex.h>
 #include <utils/RefBase.h>
@@ -70,6 +73,13 @@
      */
     virtual void setIgnorePresentFences(bool ignore) = 0;
 
+    /*
+     * Sets the primary display power mode to the controller.
+     *
+     * \param [in] powerMode
+     */
+    virtual void setDisplayPowerMode(hal::PowerMode powerMode) = 0;
+
     virtual void dump(std::string& result) const = 0;
 
 protected:
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 8e5edca..494aa2c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -444,6 +444,11 @@
     }
 
     mIgnoreHdrCameraLayers = ignore_hdr_camera_layers(false);
+
+    // Power hint session mode, representing which hint(s) to send: early, late, or both)
+    mPowerHintSessionMode =
+            {.late = base::GetBoolProperty("debug.sf.send_late_power_session_hint"s, true),
+             .early = base::GetBoolProperty("debug.sf.send_early_power_session_hint"s, false)};
 }
 
 LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() {
@@ -1065,7 +1070,7 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info) {
+void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info, bool force) {
     ATRACE_CALL();
 
     if (!info.mode) {
@@ -1078,7 +1083,7 @@
         return;
     }
 
-    if (display->setDesiredActiveMode(info)) {
+    if (display->setDesiredActiveMode(info, force)) {
         scheduleComposite(FrameHint::kNone);
 
         // Start receiving vsync samples now, so that we can detect a period
@@ -1991,12 +1996,6 @@
 
 bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVsyncTime)
         FTL_FAKE_GUARD(kMainThreadContext) {
-    // we set this once at the beginning of commit to ensure consistency throughout the whole frame
-    mPowerHintSessionData.sessionEnabled = mPowerAdvisor->usePowerHintSession();
-    if (mPowerHintSessionData.sessionEnabled) {
-        mPowerHintSessionData.commitStart = systemTime();
-    }
-
     // calculate the expected present time once and use the cached
     // value throughout this frame to make sure all layers are
     // seeing this same value.
@@ -2010,10 +2009,6 @@
     const nsecs_t lastScheduledPresentTime = mScheduledPresentTime;
     mScheduledPresentTime = expectedVsyncTime;
 
-    if (mPowerHintSessionData.sessionEnabled) {
-        mPowerAdvisor->setTargetWorkDuration(mExpectedPresentTime -
-                                             mPowerHintSessionData.commitStart);
-    }
     const auto vsyncIn = [&] {
         if (!ATRACE_ENABLED()) return 0.f;
         return (mExpectedPresentTime - systemTime()) / 1e6f;
@@ -2089,6 +2084,32 @@
         }
     }
 
+    // Save this once per commit + composite to ensure consistency
+    // TODO (b/240619471): consider removing active display check once AOD is fixed
+    const auto activeDisplay =
+            FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(mActiveDisplayToken));
+    mPowerHintSessionEnabled = mPowerAdvisor->usePowerHintSession() && activeDisplay &&
+            activeDisplay->getPowerMode() == hal::PowerMode::ON;
+    if (mPowerHintSessionEnabled) {
+        const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get();
+        // get stable vsync period from display mode
+        const nsecs_t vsyncPeriod = display->getActiveMode()->getVsyncPeriod();
+        mPowerAdvisor->setCommitStart(frameTime);
+        mPowerAdvisor->setExpectedPresentTime(mExpectedPresentTime);
+        const nsecs_t idealSfWorkDuration =
+                mVsyncModulator->getVsyncConfig().sfWorkDuration.count();
+        // Frame delay is how long we should have minus how long we actually have
+        mPowerAdvisor->setFrameDelay(idealSfWorkDuration - (mExpectedPresentTime - frameTime));
+        mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration);
+        mPowerAdvisor->setTargetWorkDuration(vsyncPeriod);
+
+        // Send early hint here to make sure there's not another frame pending
+        if (mPowerHintSessionMode.early) {
+            // Send a rough prediction for this frame based on last frame's timing info
+            mPowerAdvisor->sendPredictedWorkDuration();
+        }
+    }
+
     if (mTracingEnabledChanged) {
         mLayerTracingEnabled = mLayerTracing.isEnabled();
         mTracingEnabledChanged = false;
@@ -2165,16 +2186,15 @@
         FTL_FAKE_GUARD(kMainThreadContext) {
     ATRACE_FORMAT("%s %" PRId64, __func__, vsyncId);
 
-    if (mPowerHintSessionData.sessionEnabled) {
-        mPowerHintSessionData.compositeStart = systemTime();
-    }
-
     compositionengine::CompositionRefreshArgs refreshArgs;
     const auto& displays = FTL_FAKE_GUARD(mStateLock, mDisplays);
     refreshArgs.outputs.reserve(displays.size());
+    std::vector<DisplayId> displayIds;
     for (const auto& [_, display] : displays) {
         refreshArgs.outputs.push_back(display->getCompositionDisplay());
+        displayIds.push_back(display->getId());
     }
+    mPowerAdvisor->setDisplays(displayIds);
     mDrawingState.traverseInZOrder([&refreshArgs](Layer* layer) {
         if (auto layerFE = layer->getCompositionEngineLayerFE())
             refreshArgs.layers.push_back(layerFE);
@@ -2222,12 +2242,17 @@
 
     mCompositionEngine->present(refreshArgs);
 
-    if (mPowerHintSessionData.sessionEnabled) {
-        mPowerHintSessionData.presentEnd = systemTime();
-    }
-
     mTimeStats->recordFrameDuration(frameTime, systemTime());
 
+    // Send a power hint hint after presentation is finished
+    if (mPowerHintSessionEnabled) {
+        mPowerAdvisor->setSfPresentTiming(mPreviousPresentFences[0].fenceTime->getSignalTime(),
+                                          systemTime());
+        if (mPowerHintSessionMode.late) {
+            mPowerAdvisor->sendActualWorkDuration();
+        }
+    }
+
     if (mScheduler->onPostComposition(presentTime)) {
         scheduleComposite(FrameHint::kNone);
     }
@@ -2272,11 +2297,8 @@
         scheduleCommit(FrameHint::kNone);
     }
 
-    // calculate total render time for performance hinting if adpf cpu hint is enabled,
-    if (mPowerHintSessionData.sessionEnabled) {
-        const nsecs_t flingerDuration =
-                (mPowerHintSessionData.presentEnd - mPowerHintSessionData.commitStart);
-        mPowerAdvisor->sendActualWorkDuration(flingerDuration, mPowerHintSessionData.presentEnd);
+    if (mPowerHintSessionEnabled) {
+        mPowerAdvisor->setCompositeEnd(systemTime());
     }
 }
 
@@ -2867,7 +2889,8 @@
     ALOGV("Display Orientation: %s", toCString(creationArgs.physicalOrientation));
 
     // virtual displays are always considered enabled
-    creationArgs.initialPowerMode = state.isVirtual() ? hal::PowerMode::ON : hal::PowerMode::OFF;
+    creationArgs.initialPowerMode =
+            state.isVirtual() ? std::make_optional(hal::PowerMode::ON) : std::nullopt;
 
     sp<DisplayDevice> display = getFactory().createDisplayDevice(creationArgs);
 
@@ -3310,11 +3333,11 @@
     mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
         if (!layer->needsInputInfo()) return;
 
-        // Do not create WindowInfos for windows on displays that cannot receive input.
-        if (const auto opt = displayInputInfos.get(layer->getLayerStack())) {
-            const auto& info = opt->get();
-            outWindowInfos.push_back(layer->fillInputInfo(info.transform, info.isSecure));
-        }
+        const auto opt = displayInputInfos.get(layer->getLayerStack(),
+                                               [](const auto& info) -> Layer::InputDisplayArgs {
+                                                   return {&info.transform, info.isSecure};
+                                               });
+        outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{})));
     });
 
     sNumWindowInfos = outWindowInfos.size();
@@ -3349,8 +3372,14 @@
     }
     ATRACE_CALL();
 
+    if (display->isInternal() && !isDisplayActiveLocked(display)) {
+        ALOGV("%s(%s): Inactive display", __func__, to_string(display->getId()).c_str());
+        return;
+    }
+
     if (!display->refreshRateConfigs().isModeAllowed(mode->getId())) {
-        ALOGV("Skipping disallowed mode %d", mode->getId().value());
+        ALOGV("%s(%s): Disallowed mode %d", __func__, to_string(display->getId()).c_str(),
+              mode->getId().value());
         return;
     }
 
@@ -4849,8 +4878,8 @@
     const auto displayId = display->getPhysicalId();
     ALOGD("Setting power mode %d on display %s", mode, to_string(displayId).c_str());
 
-    const hal::PowerMode currentMode = display->getPowerMode();
-    if (mode == currentMode) {
+    std::optional<hal::PowerMode> currentMode = display->getPowerMode();
+    if (currentMode.has_value() && mode == *currentMode) {
         return;
     }
 
@@ -4866,7 +4895,7 @@
         mInterceptor->savePowerModeUpdate(display->getSequenceId(), static_cast<int32_t>(mode));
     }
     const auto refreshRate = display->refreshRateConfigs().getActiveMode()->getFps();
-    if (currentMode == hal::PowerMode::OFF) {
+    if (!currentMode || *currentMode == hal::PowerMode::OFF) {
         // Turn on the display
         if (display->isInternal() && (!activeDisplay || !activeDisplay->isPoweredOn())) {
             onActiveDisplayChangedLocked(display);
@@ -4897,7 +4926,7 @@
         if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) {
             ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno));
         }
-        if (isDisplayActiveLocked(display) && currentMode != hal::PowerMode::DOZE_SUSPEND) {
+        if (isDisplayActiveLocked(display) && *currentMode != hal::PowerMode::DOZE_SUSPEND) {
             mScheduler->disableHardwareVsync(true);
             mScheduler->onScreenReleased(mAppConnectionHandle);
         }
@@ -4911,7 +4940,7 @@
     } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) {
         // Update display while dozing
         getHwComposer().setPowerMode(displayId, mode);
-        if (isDisplayActiveLocked(display) && currentMode == hal::PowerMode::DOZE_SUSPEND) {
+        if (isDisplayActiveLocked(display) && *currentMode == hal::PowerMode::DOZE_SUSPEND) {
             mScheduler->onScreenAcquired(mAppConnectionHandle);
             mScheduler->resyncToHardwareVsync(true, refreshRate);
         }
@@ -4930,7 +4959,7 @@
     if (isDisplayActiveLocked(display)) {
         mTimeStats->setPowerMode(mode);
         mRefreshRateStats->setPowerMode(mode);
-        mScheduler->setDisplayPowerState(mode == hal::PowerMode::ON);
+        mScheduler->setDisplayPowerMode(mode);
     }
 
     ALOGD("Finished setting power mode %d on display %s", mode, to_string(displayId).c_str());
@@ -4983,6 +5012,25 @@
 
         const auto flag = args.empty() ? ""s : std::string(String8(args[0]));
 
+        // Traversal of drawing state must happen on the main thread.
+        // Otherwise, SortedVector may have shared ownership during concurrent
+        // traversals, which can result in use-after-frees.
+        std::string compositionLayers;
+        mScheduler
+                ->schedule([&] {
+                    StringAppendF(&compositionLayers, "Composition layers\n");
+                    mDrawingState.traverseInZOrder([&](Layer* layer) {
+                        auto* compositionState = layer->getCompositionState();
+                        if (!compositionState || !compositionState->isVisible) return;
+
+                        android::base::StringAppendF(&compositionLayers, "* Layer %p (%s)\n", layer,
+                                                     layer->getDebugName() ? layer->getDebugName()
+                                                                           : "<unknown>");
+                        compositionState->dump(compositionLayers);
+                    });
+                })
+                .get();
+
         bool dumpLayers = true;
         {
             TimedLock lock(mStateLock, s2ns(1), __func__);
@@ -4995,7 +5043,7 @@
                 (it->second)(args, asProto, result);
                 dumpLayers = false;
             } else if (!asProto) {
-                dumpAllLocked(args, result);
+                dumpAllLocked(args, compositionLayers, result);
             }
         }
 
@@ -5294,7 +5342,8 @@
     result.append(future.get());
 }
 
-void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, std::string& result) const {
+void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& compositionLayers,
+                                   std::string& result) const {
     const bool colorize = !args.empty() && args[0] == String16("--color");
     Colorizer colorizer(colorize);
 
@@ -5345,18 +5394,7 @@
     StringAppendF(&result, "Visible layers (count = %zu)\n", mNumLayers.load());
     colorizer.reset(result);
 
-    {
-        StringAppendF(&result, "Composition layers\n");
-        mDrawingState.traverseInZOrder([&](Layer* layer) {
-            auto* compositionState = layer->getCompositionState();
-            if (!compositionState || !compositionState->isVisible) return;
-
-            android::base::StringAppendF(&result, "* Layer %p (%s)\n", layer,
-                                         layer->getDebugName() ? layer->getDebugName()
-                                                               : "<unknown>");
-            compositionState->dump(result);
-        });
-    }
+    result.append(compositionLayers);
 
     colorizer.bold(result);
     StringAppendF(&result, "Displays (%zu entries)\n", mDisplays.size());
@@ -6650,8 +6688,10 @@
         std::unique_ptr<RenderArea> renderArea = renderAreaFuture.get();
         if (!renderArea) {
             ALOGW("Skipping screen capture because of invalid render area.");
-            captureResults.result = NO_MEMORY;
-            captureListener->onScreenCaptureCompleted(captureResults);
+            if (captureListener) {
+                captureResults.result = NO_MEMORY;
+                captureListener->onScreenCaptureCompleted(captureResults);
+            }
             return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
         }
 
@@ -6901,9 +6941,7 @@
         return NO_ERROR;
     }
 
-    status_t setPolicyResult = overridePolicy
-            ? display->refreshRateConfigs().setOverridePolicy(policy)
-            : display->refreshRateConfigs().setDisplayManagerPolicy(*policy);
+    const status_t setPolicyResult = display->setRefreshRatePolicy(policy, overridePolicy);
     if (setPolicyResult < 0) {
         return BAD_VALUE;
     }
@@ -6911,9 +6949,19 @@
         return NO_ERROR;
     }
 
-    scheduler::RefreshRateConfigs::Policy currentPolicy =
-            display->refreshRateConfigs().getCurrentPolicy();
+    if (display->isInternal() && !isDisplayActiveLocked(display)) {
+        // The policy will be be applied when the display becomes active.
+        ALOGV("%s(%s): Inactive display", __func__, to_string(display->getId()).c_str());
+        return NO_ERROR;
+    }
 
+    return applyRefreshRateConfigsPolicy(display);
+}
+
+status_t SurfaceFlinger::applyRefreshRateConfigsPolicy(const sp<DisplayDevice>& display,
+                                                       bool force) {
+    const scheduler::RefreshRateConfigs::Policy currentPolicy =
+            display->refreshRateConfigs().getCurrentPolicy();
     ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str());
 
     // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might
@@ -6941,7 +6989,7 @@
     if (display->refreshRateConfigs().isModeAllowed(preferredDisplayMode->getId())) {
         ALOGV("switching to Scheduler preferred display mode %d",
               preferredDisplayMode->getId().value());
-        setDesiredActiveMode({preferredDisplayMode, DisplayModeEvent::Changed});
+        setDesiredActiveMode({preferredDisplayMode, DisplayModeEvent::Changed}, force);
     } else {
         LOG_ALWAYS_FATAL("Desired display mode not allowed: %d",
                          preferredDisplayMode->getId().value());
@@ -7270,14 +7318,23 @@
 void SurfaceFlinger::onActiveDisplayChangedLocked(const sp<DisplayDevice>& activeDisplay) {
     ATRACE_CALL();
 
+    // During boot, SF powers on the primary display, which is the first display to be active. In
+    // that case, there is no need to force setDesiredActiveMode, because DM is about to send its
+    // policy via setDesiredDisplayModeSpecs.
+    bool forceApplyPolicy = false;
+
     if (const auto display = getDisplayDeviceLocked(mActiveDisplayToken)) {
         display->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false);
+        forceApplyPolicy = true;
     }
 
     if (!activeDisplay) {
         ALOGE("%s: activeDisplay is null", __func__);
         return;
     }
+
+    ALOGI("Active display is %s", to_string(activeDisplay->getPhysicalId()).c_str());
+
     mActiveDisplayToken = activeDisplay->getDisplayToken();
     activeDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true);
     updateInternalDisplayVsyncLocked(activeDisplay);
@@ -7286,9 +7343,11 @@
     onActiveDisplaySizeChanged(activeDisplay);
     mActiveDisplayTransformHint = activeDisplay->getTransformHint();
 
-    // Update the kernel timer for the current active display, since the policy
-    // for this display might have changed when it was not the active display.
-    toggleKernelIdleTimer();
+    // The policy of the new active/leader display may have changed while it was inactive. In that
+    // case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In either
+    // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode,
+    // and the kernel idle timer of the newly active display must be toggled.
+    applyRefreshRateConfigsPolicy(activeDisplay, forceApplyPolicy);
 }
 
 status_t SurfaceFlinger::addWindowInfosListener(
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index dcf3be8..3a45229 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -712,7 +712,7 @@
     // Called on the main thread in response to initializeDisplays()
     void onInitializeDisplays() REQUIRES(mStateLock);
     // Sets the desired active mode bit. It obtains the lock, and sets mDesiredActiveMode.
-    void setDesiredActiveMode(const ActiveModeInfo& info) REQUIRES(mStateLock);
+    void setDesiredActiveMode(const ActiveModeInfo& info, bool force = false) REQUIRES(mStateLock);
     status_t setActiveModeFromBackdoor(const sp<IBinder>& displayToken, int id);
     // Sets the active mode and a new refresh rate in SF.
     void updateInternalStateWithChangedMode() REQUIRES(mStateLock);
@@ -735,6 +735,9 @@
             const std::optional<scheduler::RefreshRateConfigs::Policy>& policy, bool overridePolicy)
             EXCLUDES(mStateLock);
 
+    status_t applyRefreshRateConfigsPolicy(const sp<DisplayDevice>&, bool force = false)
+            REQUIRES(mStateLock);
+
     void commitTransactions() EXCLUDES(mStateLock);
     void commitTransactionsLocked(uint32_t transactionFlags) REQUIRES(mStateLock);
     void doCommitTransactions() REQUIRES(mStateLock);
@@ -1085,7 +1088,8 @@
     /*
      * Debugging & dumpsys
      */
-    void dumpAllLocked(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
+    void dumpAllLocked(const DumpArgs& args, const std::string& compositionLayers,
+                       std::string& result) const REQUIRES(mStateLock);
 
     void appendSfConfigString(std::string& result) const;
     void listLayersLocked(std::string& result) const;
@@ -1435,12 +1439,12 @@
         return mScheduler->getLayerFramerate(now, id);
     }
 
+    bool mPowerHintSessionEnabled;
+
     struct {
-        bool sessionEnabled = false;
-        nsecs_t commitStart;
-        nsecs_t compositeStart;
-        nsecs_t presentEnd;
-    } mPowerHintSessionData GUARDED_BY(kMainThreadContext);
+        bool late = false;
+        bool early = false;
+    } mPowerHintSessionMode;
 
     nsecs_t mAnimationTransactionTimeout = s2ns(5);
 
diff --git a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp
index 9ab35d7..53de4a6 100644
--- a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp
+++ b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp
@@ -52,6 +52,8 @@
     void verifyAndClearExpectations();
     void sendActualWorkDurationGroup(std::vector<WorkDuration> durations,
                                      std::chrono::nanoseconds sleepBeforeLastSend);
+    std::chrono::nanoseconds mAllowedDeviation;
+    std::chrono::nanoseconds mStaleTimeout;
 };
 
 void AidlPowerHalWrapperTest::SetUp() {
@@ -59,6 +61,9 @@
     mMockSession = new NiceMock<MockIPowerHintSession>();
     ON_CALL(*mMockHal.get(), getHintSessionPreferredRate(_)).WillByDefault(Return(Status::ok()));
     mWrapper = std::make_unique<AidlPowerHalWrapper>(mMockHal);
+    mWrapper->setAllowedActualDeviation(std::chrono::nanoseconds{10ms}.count());
+    mAllowedDeviation = std::chrono::nanoseconds{mWrapper->mAllowedActualDeviation};
+    mStaleTimeout = AidlPowerHalWrapper::kStaleTimeout;
 }
 
 void AidlPowerHalWrapperTest::verifyAndClearExpectations() {
@@ -76,6 +81,7 @@
         mWrapper->sendActualWorkDuration(duration.durationNanos, duration.timeStampNanos);
     }
 }
+
 WorkDuration toWorkDuration(std::chrono::nanoseconds durationNanos, int64_t timeStampNanos) {
     WorkDuration duration;
     duration.durationNanos = durationNanos.count();
@@ -83,6 +89,10 @@
     return duration;
 }
 
+WorkDuration toWorkDuration(std::pair<std::chrono::nanoseconds, nsecs_t> timePair) {
+    return toWorkDuration(timePair.first, timePair.second);
+}
+
 std::string printWorkDurations(const ::std::vector<WorkDuration>& durations) {
     std::ostringstream os;
     for (auto duration : durations) {
@@ -112,7 +122,7 @@
     EXPECT_FALSE(mWrapper->startPowerHintSession());
 }
 
-TEST_F(AidlPowerHalWrapperTest, restartNewPoserHintSessionWithNewThreadIds) {
+TEST_F(AidlPowerHalWrapperTest, restartNewPowerHintSessionWithNewThreadIds) {
     ASSERT_TRUE(mWrapper->supportsPowerHintSession());
 
     std::vector<int32_t> threadIds = {1, 2};
@@ -149,12 +159,8 @@
 
     std::chrono::nanoseconds base = 100ms;
     // test cases with target work duration and whether it should update hint against baseline 100ms
-    const std::vector<std::pair<std::chrono::nanoseconds, bool>> testCases = {{0ms, false},
-                                                                              {-1ms, false},
-                                                                              {200ms, true},
-                                                                              {2ms, true},
-                                                                              {91ms, false},
-                                                                              {109ms, false}};
+    const std::vector<std::pair<std::chrono::nanoseconds, bool>> testCases =
+            {{0ms, true}, {-1ms, true}, {200ms, true}, {2ms, true}, {100ms, false}, {109ms, true}};
 
     for (const auto& test : testCases) {
         // reset to 100ms baseline
@@ -200,21 +206,21 @@
     // 100ms
     const std::vector<std::pair<std::vector<std::pair<std::chrono::nanoseconds, nsecs_t>>, bool>>
             testCases = {{{{-1ms, 100}}, false},
-                         {{{91ms, 100}}, false},
-                         {{{109ms, 100}}, false},
+                         {{{100ms - (mAllowedDeviation / 2), 100}}, false},
+                         {{{100ms + (mAllowedDeviation / 2), 100}}, false},
+                         {{{100ms + (mAllowedDeviation + 1ms), 100}}, true},
+                         {{{100ms - (mAllowedDeviation + 1ms), 100}}, true},
                          {{{100ms, 100}, {200ms, 200}}, true},
                          {{{100ms, 500}, {100ms, 600}, {3ms, 600}}, true}};
 
     for (const auto& test : testCases) {
         // reset actual duration
-        sendActualWorkDurationGroup({base}, 80ms);
+        sendActualWorkDurationGroup({base}, mStaleTimeout);
 
         auto raw = test.first;
         std::vector<WorkDuration> durations(raw.size());
         std::transform(raw.begin(), raw.end(), durations.begin(),
-                       [](std::pair<std::chrono::nanoseconds, nsecs_t> d) {
-                           return toWorkDuration(d.first, d.second);
-                       });
+                       [](auto d) { return toWorkDuration(d); });
         EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(durations))
                 .Times(test.second ? 1 : 0);
         sendActualWorkDurationGroup(durations, 0ms);
@@ -222,40 +228,6 @@
     }
 }
 
-TEST_F(AidlPowerHalWrapperTest, sendAdjustedActualWorkDuration) {
-    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-
-    std::vector<int32_t> threadIds = {1, 2};
-    mWrapper->setPowerHintSessionThreadIds(threadIds);
-    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
-            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
-    ASSERT_TRUE(mWrapper->startPowerHintSession());
-    verifyAndClearExpectations();
-
-    std::chrono::nanoseconds lastTarget = 100ms;
-    EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(lastTarget.count())).Times(1);
-    mWrapper->setTargetWorkDuration(lastTarget.count());
-    std::chrono::nanoseconds newTarget = 105ms;
-    mWrapper->setTargetWorkDuration(newTarget.count());
-    EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(newTarget.count())).Times(0);
-    std::chrono::nanoseconds actual = 21ms;
-    // 100 / 105 * 21ms = 20ms
-    std::chrono::nanoseconds expectedActualSent = 20ms;
-    std::vector<WorkDuration> expectedDurations = {toWorkDuration(expectedActualSent, 1)};
-
-    EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(_))
-            .WillOnce(DoAll(
-                    [expectedDurations](const ::std::vector<WorkDuration>& durationsSent) {
-                        EXPECT_EQ(expectedDurations, durationsSent)
-                                << base::StringPrintf("actual sent: %s vs expected: %s",
-                                                      printWorkDurations(durationsSent).c_str(),
-                                                      printWorkDurations(expectedDurations)
-                                                              .c_str());
-                    },
-                    Return(Status::ok())));
-    mWrapper->sendActualWorkDuration(actual.count(), 1);
-}
-
 TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration_exceedsStaleTime) {
     ASSERT_TRUE(mWrapper->supportsPowerHintSession());
 
@@ -269,22 +241,23 @@
     auto base = toWorkDuration(100ms, 0);
     // test cases with actual work durations and whether it should update hint against baseline
     // 100ms
-    const std::vector<std::pair<std::vector<std::pair<std::chrono::nanoseconds, nsecs_t>>, bool>>
-            testCases = {{{{91ms, 100}}, true}, {{{109ms, 100}}, true}};
+    const std::vector<std::tuple<std::vector<std::pair<std::chrono::nanoseconds, nsecs_t>>,
+                                 std::chrono::nanoseconds, bool>>
+            testCases = {{{{100ms, 100}}, mStaleTimeout, true},
+                         {{{100ms + (mAllowedDeviation / 2), 100}}, mStaleTimeout, true},
+                         {{{100ms, 100}}, mStaleTimeout / 2, false}};
 
     for (const auto& test : testCases) {
         // reset actual duration
-        sendActualWorkDurationGroup({base}, 80ms);
+        sendActualWorkDurationGroup({base}, mStaleTimeout);
 
-        auto raw = test.first;
+        auto raw = std::get<0>(test);
         std::vector<WorkDuration> durations(raw.size());
         std::transform(raw.begin(), raw.end(), durations.begin(),
-                       [](std::pair<std::chrono::nanoseconds, nsecs_t> d) {
-                           return toWorkDuration(d.first, d.second);
-                       });
+                       [](auto d) { return toWorkDuration(d); });
         EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(durations))
-                .Times(test.second ? 1 : 0);
-        sendActualWorkDurationGroup(durations, 80ms);
+                .Times(std::get<2>(test) ? 1 : 0);
+        sendActualWorkDurationGroup(durations, std::get<1>(test));
         verifyAndClearExpectations();
     }
 }
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 7823363..004f31c 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -24,6 +24,7 @@
 filegroup {
     name: "libsurfaceflinger_mock_sources",
     srcs: [
+        "mock/DisplayHardware/MockAidlPowerHalWrapper.cpp",
         "mock/DisplayHardware/MockComposer.cpp",
         "mock/DisplayHardware/MockHWC2.cpp",
         "mock/DisplayHardware/MockIPower.cpp",
@@ -95,6 +96,7 @@
         "LayerTest.cpp",
         "LayerTestUtils.cpp",
         "MessageQueueTest.cpp",
+        "PowerAdvisorTest.cpp",
         "SurfaceFlinger_CreateDisplayTest.cpp",
         "SurfaceFlinger_DestroyDisplayTest.cpp",
         "SurfaceFlinger_DisplayModeSwitching.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index bbfedc7..fbc532e 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -664,7 +664,8 @@
                     EXPECT_EQ(false, layer.source.buffer.isY410BT2020);
                     EXPECT_EQ(true, layer.source.buffer.usePremultipliedAlpha);
                     EXPECT_EQ(false, layer.source.buffer.isOpaque);
-                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius);
+                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x);
+                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y);
                     EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
                     EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha);
                     return resultFuture;
@@ -714,7 +715,8 @@
                     EXPECT_EQ(half3(LayerProperties::COLOR[0], LayerProperties::COLOR[1],
                                     LayerProperties::COLOR[2]),
                               layer.source.solidColor);
-                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius);
+                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x);
+                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y);
                     EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
                     EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha);
                     return resultFuture;
@@ -792,7 +794,8 @@
                     const renderengine::LayerSettings layer = layerSettings.back();
                     EXPECT_THAT(layer.source.buffer.buffer, IsNull());
                     EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), layer.source.solidColor);
-                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius);
+                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x);
+                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y);
                     EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
                     EXPECT_EQ(1.0f, layer.alpha);
                     return resultFuture;
diff --git a/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp b/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
index ec27eda..67ace1a 100644
--- a/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
@@ -292,9 +292,10 @@
 
 TEST_F(DispSyncSourceTest, getLatestVsyncData) {
     const nsecs_t now = systemTime();
-    const nsecs_t vsyncInternalDuration = mWorkDuration.count() + mReadyDuration.count();
+    const nsecs_t expectedPresentationTime =
+            now + mWorkDuration.count() + mReadyDuration.count() + 1;
     EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_))
-            .WillOnce(Return(now + vsyncInternalDuration + 1));
+            .WillOnce(Return(expectedPresentationTime));
     {
         InSequence seq;
         EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
@@ -306,10 +307,8 @@
     EXPECT_TRUE(mDispSyncSource);
 
     const auto vsyncData = mDispSyncSource->getLatestVSyncData();
-    ASSERT_GT(vsyncData.deadlineTimestamp, now);
-    ASSERT_GT(vsyncData.expectedPresentationTime, vsyncData.deadlineTimestamp);
-    EXPECT_EQ(vsyncData.deadlineTimestamp,
-              vsyncData.expectedPresentationTime - vsyncInternalDuration);
+    ASSERT_EQ(vsyncData.expectedPresentationTime, expectedPresentationTime);
+    EXPECT_EQ(vsyncData.deadlineTimestamp, expectedPresentationTime - mReadyDuration.count());
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
index 225ad16..ac5e927 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
@@ -96,5 +96,23 @@
     EXPECT_EQ(std::nullopt, displayDevice->getCompositionDisplay()->getState().displayBrightness);
 }
 
+TEST_F(SetDisplayBrightnessTest, firstDisplayBrightnessWithComposite) {
+    ftl::FakeGuard guard(kMainThreadContext);
+    sp<DisplayDevice> displayDevice = getDisplayDevice();
+
+    EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+
+    constexpr float kDisplayBrightness = -1.0f;
+    displayDevice->stageBrightness(kDisplayBrightness);
+
+    EXPECT_EQ(-1.0f, displayDevice->getStagedBrightness());
+
+    displayDevice->persistBrightness(true);
+
+    EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+    EXPECT_EQ(kDisplayBrightness,
+              displayDevice->getCompositionDisplay()->getState().displayBrightness);
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index f04221c..5ddd1c8 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -120,51 +120,6 @@
     });
 }
 
-sp<DisplayDevice> DisplayTransactionTest::injectDefaultInternalDisplay(
-        std::function<void(FakeDisplayDeviceInjector&)> injectExtra) {
-    constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(255u);
-    constexpr int DEFAULT_DISPLAY_WIDTH = 1080;
-    constexpr int DEFAULT_DISPLAY_HEIGHT = 1920;
-    constexpr HWDisplayId DEFAULT_DISPLAY_HWC_DISPLAY_ID = 0;
-
-    // The DisplayDevice is required to have a framebuffer (behind the
-    // ANativeWindow interface) which uses the actual hardware display
-    // size.
-    EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_WIDTH), Return(0)));
-    EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_HEIGHT, _))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_HEIGHT), Return(0)));
-    EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_FORMAT));
-    EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_CONNECT));
-    EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64));
-    EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT)).Times(AnyNumber());
-
-    auto compositionDisplay =
-            compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
-                                                   compositionengine::DisplayCreationArgsBuilder()
-                                                           .setId(DEFAULT_DISPLAY_ID)
-                                                           .setPixels({DEFAULT_DISPLAY_WIDTH,
-                                                                       DEFAULT_DISPLAY_HEIGHT})
-                                                           .setPowerAdvisor(&mPowerAdvisor)
-                                                           .build());
-
-    constexpr bool kIsPrimary = true;
-    auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
-                                              ui::DisplayConnectionType::Internal,
-                                              DEFAULT_DISPLAY_HWC_DISPLAY_ID, kIsPrimary);
-
-    injector.setNativeWindow(mNativeWindow);
-    if (injectExtra) {
-        injectExtra(injector);
-    }
-
-    auto displayDevice = injector.inject();
-
-    Mock::VerifyAndClear(mNativeWindow.get());
-
-    return displayDevice;
-}
-
 bool DisplayTransactionTest::hasPhysicalHwcDisplay(HWDisplayId hwcDisplayId) const {
     return mFlinger.hwcPhysicalDisplayIdMap().count(hwcDisplayId) == 1;
 }
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index f5235ce..60f773f 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -42,6 +42,7 @@
 #include <renderengine/mock/RenderEngine.h>
 #include <ui/DebugUtils.h>
 
+#include "FakeDisplayInjector.h"
 #include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockComposer.h"
@@ -90,7 +91,9 @@
     void injectFakeBufferQueueFactory();
     void injectFakeNativeWindowSurfaceFactory();
     sp<DisplayDevice> injectDefaultInternalDisplay(
-            std::function<void(TestableSurfaceFlinger::FakeDisplayDeviceInjector&)>);
+            std::function<void(TestableSurfaceFlinger::FakeDisplayDeviceInjector&)> injectExtra) {
+        return mFakeDisplayInjector.injectInternalDisplay(injectExtra);
+    }
 
     // --------------------------------------------------------------------
     // Postcondition helpers
@@ -115,6 +118,8 @@
     sp<GraphicBuffer> mBuffer = new GraphicBuffer();
     Hwc2::mock::PowerAdvisor mPowerAdvisor;
 
+    FakeDisplayInjector mFakeDisplayInjector{mFlinger, mPowerAdvisor, mNativeWindow};
+
     // These mocks are created by the test, but are destroyed by SurfaceFlinger
     // by virtue of being stored into a std::unique_ptr. However we still need
     // to keep a reference to them for use in setting up call expectations.
diff --git a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
new file mode 100644
index 0000000..6e4bf2b
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockPowerAdvisor.h"
+#include "mock/system/window/MockNativeWindow.h"
+
+namespace android {
+
+using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+using android::hardware::graphics::composer::hal::HWDisplayId;
+using android::Hwc2::mock::PowerAdvisor;
+
+struct FakeDisplayInjectorArgs {
+    PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(255u);
+    HWDisplayId hwcDisplayId = 0;
+    bool isPrimary = true;
+};
+
+class FakeDisplayInjector {
+public:
+    FakeDisplayInjector(TestableSurfaceFlinger& flinger, Hwc2::mock::PowerAdvisor& powerAdvisor,
+                        sp<mock::NativeWindow> nativeWindow)
+          : mFlinger(flinger), mPowerAdvisor(powerAdvisor), mNativeWindow(nativeWindow) {}
+
+    sp<DisplayDevice> injectInternalDisplay(
+            const std::function<void(FakeDisplayDeviceInjector&)>& injectExtra,
+            FakeDisplayInjectorArgs args = {}) {
+        using testing::_;
+        using testing::AnyNumber;
+        using testing::DoAll;
+        using testing::Mock;
+        using testing::Return;
+        using testing::SetArgPointee;
+
+        constexpr ui::Size kResolution = {1080, 1920};
+
+        // The DisplayDevice is required to have a framebuffer (behind the
+        // ANativeWindow interface) which uses the actual hardware display
+        // size.
+        EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(kResolution.getWidth()), Return(0)));
+        EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_HEIGHT, _))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(kResolution.getHeight()), Return(0)));
+        EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_FORMAT));
+        EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_CONNECT));
+        EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64));
+        EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT)).Times(AnyNumber());
+
+        auto compositionDisplay = compositionengine::impl::
+                createDisplay(mFlinger.getCompositionEngine(),
+                              compositionengine::DisplayCreationArgsBuilder()
+                                      .setId(args.displayId)
+                                      .setPixels(kResolution)
+                                      .setPowerAdvisor(&mPowerAdvisor)
+                                      .build());
+
+        auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
+                                                  ui::DisplayConnectionType::Internal,
+                                                  args.hwcDisplayId, args.isPrimary);
+
+        injector.setNativeWindow(mNativeWindow);
+        if (injectExtra) {
+            injectExtra(injector);
+        }
+
+        auto displayDevice = injector.inject();
+
+        Mock::VerifyAndClear(mNativeWindow.get());
+
+        return displayDevice;
+    }
+
+    TestableSurfaceFlinger& mFlinger;
+    Hwc2::mock::PowerAdvisor& mPowerAdvisor;
+    sp<mock::NativeWindow> mNativeWindow;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
new file mode 100644
index 0000000..8711a42
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "PowerAdvisorTest"
+
+#include <DisplayHardware/PowerAdvisor.h>
+#include <compositionengine/Display.h>
+#include <ftl/fake_guard.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <ui/DisplayId.h>
+#include <chrono>
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockAidlPowerHalWrapper.h"
+
+using namespace android;
+using namespace android::Hwc2::mock;
+using namespace android::hardware::power;
+using namespace std::chrono_literals;
+using namespace testing;
+
+namespace android::Hwc2::impl {
+
+class PowerAdvisorTest : public testing::Test {
+public:
+    void SetUp() override;
+    void startPowerHintSession();
+    void fakeBasicFrameTiming(nsecs_t startTime, nsecs_t vsyncPeriod);
+    void setExpectedTiming(nsecs_t startTime, nsecs_t vsyncPeriod);
+    nsecs_t getFenceWaitDelayDuration(bool skipValidate);
+
+protected:
+    TestableSurfaceFlinger mFlinger;
+    std::unique_ptr<PowerAdvisor> mPowerAdvisor;
+    NiceMock<MockAidlPowerHalWrapper>* mMockAidlWrapper;
+    nsecs_t kErrorMargin = std::chrono::nanoseconds(1ms).count();
+};
+
+void PowerAdvisorTest::SetUp() FTL_FAKE_GUARD(mPowerAdvisor->mPowerHalMutex) {
+    std::unique_ptr<MockAidlPowerHalWrapper> mockAidlWrapper =
+            std::make_unique<NiceMock<MockAidlPowerHalWrapper>>();
+    mPowerAdvisor = std::make_unique<PowerAdvisor>(*mFlinger.flinger());
+    ON_CALL(*mockAidlWrapper.get(), supportsPowerHintSession()).WillByDefault(Return(true));
+    ON_CALL(*mockAidlWrapper.get(), startPowerHintSession()).WillByDefault(Return(true));
+    mPowerAdvisor->mHalWrapper = std::move(mockAidlWrapper);
+    mMockAidlWrapper =
+            reinterpret_cast<NiceMock<MockAidlPowerHalWrapper>*>(mPowerAdvisor->mHalWrapper.get());
+}
+
+void PowerAdvisorTest::startPowerHintSession() {
+    const std::vector<int32_t> threadIds = {1, 2, 3};
+    mPowerAdvisor->enablePowerHint(true);
+    mPowerAdvisor->startPowerHintSession(threadIds);
+}
+
+void PowerAdvisorTest::setExpectedTiming(nsecs_t totalFrameTarget, nsecs_t expectedPresentTime) {
+    mPowerAdvisor->setTotalFrameTargetWorkDuration(totalFrameTarget);
+    mPowerAdvisor->setExpectedPresentTime(expectedPresentTime);
+}
+
+void PowerAdvisorTest::fakeBasicFrameTiming(nsecs_t startTime, nsecs_t vsyncPeriod) {
+    mPowerAdvisor->setCommitStart(startTime);
+    mPowerAdvisor->setFrameDelay(0);
+    mPowerAdvisor->setTargetWorkDuration(vsyncPeriod);
+}
+
+nsecs_t PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) {
+    return (skipValidate ? PowerAdvisor::kFenceWaitStartDelaySkippedValidate
+                         : PowerAdvisor::kFenceWaitStartDelayValidated)
+            .count();
+}
+
+namespace {
+
+TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) {
+    mPowerAdvisor->onBootFinished();
+    startPowerHintSession();
+
+    std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u)};
+
+    // 60hz
+    const nsecs_t vsyncPeriod = std::chrono::nanoseconds(1s).count() / 60;
+    const nsecs_t presentDuration = std::chrono::nanoseconds(5ms).count();
+    const nsecs_t postCompDuration = std::chrono::nanoseconds(1ms).count();
+
+    nsecs_t startTime = 100;
+
+    // advisor only starts on frame 2 so do an initial no-op frame
+    fakeBasicFrameTiming(startTime, vsyncPeriod);
+    setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+    mPowerAdvisor->setDisplays(displayIds);
+    mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+    mPowerAdvisor->setCompositeEnd(startTime + presentDuration + postCompDuration);
+
+    // increment the frame
+    startTime += vsyncPeriod;
+
+    const nsecs_t expectedDuration = kErrorMargin + presentDuration + postCompDuration;
+    EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1);
+
+    fakeBasicFrameTiming(startTime, vsyncPeriod);
+    setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+    mPowerAdvisor->setDisplays(displayIds);
+    mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1000000, startTime + 1500000);
+    mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2000000, startTime + 2500000);
+    mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+    mPowerAdvisor->sendActualWorkDuration();
+}
+
+TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) {
+    mPowerAdvisor->onBootFinished();
+    startPowerHintSession();
+
+    std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u)};
+
+    // 60hz
+    const nsecs_t vsyncPeriod = std::chrono::nanoseconds(1s).count() / 60;
+    const nsecs_t presentDuration = std::chrono::nanoseconds(5ms).count();
+    const nsecs_t postCompDuration = std::chrono::nanoseconds(1ms).count();
+    const nsecs_t hwcBlockedDuration = std::chrono::nanoseconds(500us).count();
+
+    nsecs_t startTime = 100;
+
+    // advisor only starts on frame 2 so do an initial no-op frame
+    fakeBasicFrameTiming(startTime, vsyncPeriod);
+    setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+    mPowerAdvisor->setDisplays(displayIds);
+    mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+    mPowerAdvisor->setCompositeEnd(startTime + presentDuration + postCompDuration);
+
+    // increment the frame
+    startTime += vsyncPeriod;
+
+    const nsecs_t expectedDuration = kErrorMargin + presentDuration +
+            getFenceWaitDelayDuration(false) - hwcBlockedDuration + postCompDuration;
+    EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1);
+
+    fakeBasicFrameTiming(startTime, vsyncPeriod);
+    setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+    mPowerAdvisor->setDisplays(displayIds);
+    mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1000000, startTime + 1500000);
+    mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2000000, startTime + 3000000);
+    // now report the fence as having fired during the display HWC time
+    mPowerAdvisor->setSfPresentTiming(startTime + 2000000 + hwcBlockedDuration,
+                                      startTime + presentDuration);
+    mPowerAdvisor->sendActualWorkDuration();
+}
+
+TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) {
+    mPowerAdvisor->onBootFinished();
+    startPowerHintSession();
+
+    std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u), GpuVirtualDisplayId(0),
+                                      GpuVirtualDisplayId(1)};
+
+    // 60hz
+    const nsecs_t vsyncPeriod = std::chrono::nanoseconds(1s).count() / 60;
+    // make present duration much later than the hwc display by itself will account for
+    const nsecs_t presentDuration = std::chrono::nanoseconds(10ms).count();
+    const nsecs_t postCompDuration = std::chrono::nanoseconds(1ms).count();
+
+    nsecs_t startTime = 100;
+
+    // advisor only starts on frame 2 so do an initial no-op frame
+    fakeBasicFrameTiming(startTime, vsyncPeriod);
+    setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+    mPowerAdvisor->setDisplays(displayIds);
+    mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+    mPowerAdvisor->setCompositeEnd(startTime + presentDuration + postCompDuration);
+
+    // increment the frame
+    startTime += vsyncPeriod;
+
+    const nsecs_t expectedDuration = kErrorMargin + presentDuration + postCompDuration;
+    EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1);
+
+    fakeBasicFrameTiming(startTime, vsyncPeriod);
+    setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+    mPowerAdvisor->setDisplays(displayIds);
+
+    // don't report timing for the gpu displays since they don't use hwc
+    mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1000000, startTime + 1500000);
+    mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2000000, startTime + 2500000);
+    mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+    mPowerAdvisor->sendActualWorkDuration();
+}
+
+} // namespace
+} // namespace android::Hwc2::impl
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index fcde532..188fd58 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -564,9 +564,10 @@
     TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60,
                                        {.frameRateMultipleThreshold = 120});
 
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
+    auto& lr3 = layers[2];
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -639,6 +640,48 @@
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
     EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::Max;
+    lr2.name = "Max";
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 120_Hz;
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.name = "120Hz ExplicitDefault";
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 120_Hz;
+    lr2.vote = LayerVoteType::ExplicitExact;
+    lr2.name = "120Hz ExplicitExact";
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
+
+    lr1.desiredRefreshRate = 10_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "30Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 120_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.name = "120Hz ExplicitExact";
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
+
+    lr1.desiredRefreshRate = 30_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "30Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.name = "30Hz ExplicitExactOrMultiple";
+    lr3.vote = LayerVoteType::Heuristic;
+    lr3.desiredRefreshRate = 120_Hz;
+    lr3.name = "120Hz Heuristic";
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60) {
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index aab2795..93c809e 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -159,8 +159,8 @@
     mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
 
-    constexpr bool kPowerStateNormal = true;
-    mScheduler->setDisplayPowerState(kPowerStateNormal);
+    constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON;
+    mScheduler->setDisplayPowerMode(kPowerModeOn);
 
     constexpr uint32_t kDisplayArea = 999'999;
     mScheduler->onActiveDisplayAreaChanged(kDisplayArea);
@@ -226,8 +226,8 @@
 
     mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
 
-    constexpr bool kPowerStateNormal = true;
-    mScheduler->setDisplayPowerState(kPowerStateNormal);
+    constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON;
+    mScheduler->setDisplayPowerMode(kPowerModeOn);
 
     constexpr uint32_t kDisplayArea = 999'999;
     mScheduler->onActiveDisplayAreaChanged(kDisplayArea);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 5872a47..b58add8 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-#include "mock/MockEventThread.h"
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
 #include "DisplayTransactionTestHelpers.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
 
 #include <scheduler/Fps.h>
 
@@ -42,14 +42,9 @@
 
         mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED);
 
-        {
-            DisplayModes modes = makeModes(kMode60, kMode90, kMode120, kMode90_4K);
-            auto configs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60);
-
-            mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
-                               .setDisplayModes(std::move(modes), kModeId60, std::move(configs))
-                               .inject();
-        }
+        mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
+                           .setDisplayModes(kModes, kModeId60)
+                           .inject();
 
         setupScheduler(mDisplay->holdRefreshRateConfigs());
 
@@ -77,6 +72,8 @@
     static constexpr ui::Size kResolution4K{3840, 2160};
     static inline const DisplayModePtr kMode90_4K =
             createDisplayMode(kModeId90_4K, 90_Hz, 3, kResolution4K);
+
+    static inline const DisplayModes kModes = makeModes(kMode60, kMode90, kMode120, kMode90_4K);
 };
 
 void DisplayModeSwitchingTest::setupScheduler(
@@ -265,5 +262,112 @@
     ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90_4K);
 }
 
+TEST_F(DisplayModeSwitchingTest, multiDisplay) {
+    constexpr HWDisplayId kInnerDisplayHwcId = PrimaryDisplayVariant::HWC_DISPLAY_ID;
+    constexpr HWDisplayId kOuterDisplayHwcId = kInnerDisplayHwcId + 1;
+
+    constexpr PhysicalDisplayId kOuterDisplayId = PhysicalDisplayId::fromPort(254u);
+
+    constexpr bool kIsPrimary = false;
+    TestableSurfaceFlinger::FakeHwcDisplayInjector(kOuterDisplayId, hal::DisplayType::PHYSICAL,
+                                                   kIsPrimary)
+            .setHwcDisplayId(kOuterDisplayHwcId)
+            .inject(&mFlinger, mComposer);
+
+    const auto outerDisplay = mFakeDisplayInjector.injectInternalDisplay(
+            [&](FakeDisplayDeviceInjector& injector) {
+                injector.setDisplayModes(mock::cloneForDisplay(kOuterDisplayId, kModes),
+                                         kModeId120);
+            },
+            {.displayId = kOuterDisplayId,
+             .hwcDisplayId = kOuterDisplayHwcId,
+             .isPrimary = kIsPrimary});
+
+    const auto& innerDisplay = mDisplay;
+
+    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
+    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+
+    EXPECT_EQ(innerDisplay->getActiveMode()->getId(), kModeId60);
+    EXPECT_EQ(outerDisplay->getActiveMode()->getId(), kModeId120);
+
+    mFlinger.onActiveDisplayChanged(innerDisplay);
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
+                                                  kModeId90.value(), false, 0.f, 120.f, 0.f,
+                                                  120.f));
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
+                                                  kModeId60.value(), false, 0.f, 120.f, 0.f,
+                                                  120.f));
+
+    // Transition on the inner display.
+    ASSERT_TRUE(innerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(innerDisplay->getDesiredActiveMode()->mode->getId(), kModeId90);
+
+    // No transition on the outer display.
+    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+
+    const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
+    EXPECT_CALL(*mComposer,
+                setActiveConfigWithConstraints(kInnerDisplayHwcId,
+                                               hal::HWConfigId(kModeId90.value()), _, _))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+    mFlinger.commit();
+
+    // Transition on the inner display.
+    ASSERT_TRUE(innerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(innerDisplay->getDesiredActiveMode()->mode->getId(), kModeId90);
+
+    // No transition on the outer display.
+    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+
+    mFlinger.commit();
+
+    // Transition on the inner display.
+    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(innerDisplay->getActiveMode()->getId(), kModeId90);
+
+    // No transition on the outer display.
+    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(outerDisplay->getActiveMode()->getId(), kModeId120);
+
+    mFlinger.onActiveDisplayChanged(outerDisplay);
+
+    // No transition on the inner display.
+    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
+
+    // Transition on the outer display.
+    ASSERT_TRUE(outerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(outerDisplay->getDesiredActiveMode()->mode->getId(), kModeId60);
+
+    EXPECT_CALL(*mComposer,
+                setActiveConfigWithConstraints(kOuterDisplayHwcId,
+                                               hal::HWConfigId(kModeId60.value()), _, _))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+    mFlinger.commit();
+
+    // No transition on the inner display.
+    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
+
+    // Transition on the outer display.
+    ASSERT_TRUE(outerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(outerDisplay->getDesiredActiveMode()->mode->getId(), kModeId60);
+
+    mFlinger.commit();
+
+    // No transition on the inner display.
+    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(innerDisplay->getActiveMode()->getId(), kModeId90);
+
+    // Transition on the outer display.
+    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(outerDisplay->getActiveMode()->getId(), kModeId60);
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
index 8de9e4b..2c9888d 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -74,6 +74,7 @@
     mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
     mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
     mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
+    mFlinger.setPowerHintSessionMode(true, true);
     mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
     static constexpr bool kIsPrimary = true;
     FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
@@ -96,6 +97,7 @@
                     .setNativeWindow(mNativeWindow)
                     .setPowerMode(hal::PowerMode::ON)
                     .inject();
+    mFlinger.mutableActiveDisplayToken() = mDisplay->getDisplayToken();
 }
 
 void SurfaceFlingerPowerHintTest::setupScheduler() {
@@ -142,10 +144,30 @@
                 std::this_thread::sleep_for(mockHwcRunTime);
                 return hardware::graphics::composer::V2_1::Error::NONE;
             });
-    EXPECT_CALL(*mPowerAdvisor,
-                sendActualWorkDuration(Gt(mockHwcRunTime.count()),
-                                       Gt(now + mockHwcRunTime.count())))
+    EXPECT_CALL(*mPowerAdvisor, sendActualWorkDuration()).Times(1);
+    static constexpr bool kVsyncId = 123; // arbitrary
+    mFlinger.commitAndComposite(now, kVsyncId, now + mockVsyncPeriod.count());
+}
+
+TEST_F(SurfaceFlingerPowerHintTest, inactiveOnDisplayDoze) {
+    ON_CALL(*mPowerAdvisor, usePowerHintSession()).WillByDefault(Return(true));
+
+    mDisplay->setPowerMode(hal::PowerMode::DOZE);
+
+    const std::chrono::nanoseconds mockVsyncPeriod = 15ms;
+    EXPECT_CALL(*mPowerAdvisor, setTargetWorkDuration(_)).Times(0);
+
+    const nsecs_t now = systemTime();
+    const std::chrono::nanoseconds mockHwcRunTime = 20ms;
+    EXPECT_CALL(*mDisplaySurface,
+                prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc))
             .Times(1);
+    EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
+            .WillOnce([mockHwcRunTime] {
+                std::this_thread::sleep_for(mockHwcRunTime);
+                return hardware::graphics::composer::V2_1::Error::NONE;
+            });
+    EXPECT_CALL(*mPowerAdvisor, sendActualWorkDuration()).Times(0);
     static constexpr bool kVsyncId = 123; // arbitrary
     mFlinger.commitAndComposite(now, kVsyncId, now + mockVsyncPeriod.count());
 }
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index 583cf5f..b560025 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -323,6 +323,9 @@
 public:
     template <typename Case>
     void transitionDisplayCommon();
+
+    template <bool kBoot>
+    sp<DisplayDevice> activeDisplayTest();
 };
 
 template <PowerMode PowerMode>
@@ -499,5 +502,87 @@
     transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOnToUnknownVariant>>();
 }
 
+template <bool kBoot>
+sp<DisplayDevice> SetPowerModeInternalTest::activeDisplayTest() {
+    using Case = SimplePrimaryDisplayCase;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // Inject a primary display.
+    Case::Display::injectHwcDisplay(this);
+    auto injector = Case::Display::makeFakeExistingDisplayInjector(this);
+    injector.setPowerMode(kBoot ? std::nullopt : std::make_optional(PowerMode::OFF));
+
+    const auto display = injector.inject();
+    EXPECT_EQ(display->getDisplayToken(), mFlinger.mutableActiveDisplayToken());
+
+    using PowerCase = PrimaryDisplayPowerCase<TransitionOffToOnVariant>;
+    TransitionOffToOnVariant::template setupCallExpectations<PowerCase>(this);
+
+    constexpr size_t kTimes = kBoot ? 1 : 0;
+    EXPECT_CALL(*mRenderEngine, onActiveDisplaySizeChanged(display->getSize())).Times(kTimes);
+    EXPECT_CALL(*mEventThread, onModeChanged(display->getActiveMode())).Times(kTimes);
+
+    if constexpr (kBoot) {
+        mFlinger.mutableActiveDisplayToken() = nullptr;
+    }
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    mFlinger.setPowerModeInternal(display, PowerMode::ON);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The primary display should be the active display.
+    EXPECT_EQ(display->getDisplayToken(), mFlinger.mutableActiveDisplayToken());
+
+    Mock::VerifyAndClearExpectations(mComposer);
+    Mock::VerifyAndClearExpectations(mRenderEngine);
+    Mock::VerifyAndClearExpectations(mEventThread);
+    Mock::VerifyAndClearExpectations(mVsyncController);
+    Mock::VerifyAndClearExpectations(mVSyncTracker);
+    Mock::VerifyAndClearExpectations(mFlinger.scheduler());
+    Mock::VerifyAndClearExpectations(&mFlinger.mockSchedulerCallback());
+
+    return display;
+}
+
+TEST_F(SetPowerModeInternalTest, activeDisplayBoot) {
+    constexpr bool kBoot = true;
+    activeDisplayTest<kBoot>();
+}
+
+TEST_F(SetPowerModeInternalTest, activeDisplaySingle) {
+    constexpr bool kBoot = false;
+    activeDisplayTest<kBoot>();
+}
+
+TEST_F(SetPowerModeInternalTest, activeDisplayDual) {
+    constexpr bool kBoot = false;
+    const auto innerDisplay = activeDisplayTest<kBoot>();
+
+    // Inject a powered-off outer display.
+    const auto outerDisplay = mFakeDisplayInjector.injectInternalDisplay(
+            [&](FakeDisplayDeviceInjector& injector) { injector.setPowerMode(PowerMode::OFF); },
+            {.displayId = PhysicalDisplayId::fromPort(254u),
+             .hwcDisplayId = 1,
+             .isPrimary = false});
+
+    EXPECT_EQ(innerDisplay->getDisplayToken(), mFlinger.mutableActiveDisplayToken());
+
+    mFlinger.setPowerModeInternal(innerDisplay, PowerMode::OFF);
+    mFlinger.setPowerModeInternal(outerDisplay, PowerMode::ON);
+
+    EXPECT_EQ(outerDisplay->getDisplayToken(), mFlinger.mutableActiveDisplayToken());
+
+    mFlinger.setPowerModeInternal(outerDisplay, PowerMode::OFF);
+    mFlinger.setPowerModeInternal(innerDisplay, PowerMode::ON);
+
+    EXPECT_EQ(innerDisplay->getDisplayToken(), mFlinger.mutableActiveDisplayToken());
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index f1a69fb..3cffec1 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -330,6 +330,10 @@
         layer->mDrawingParent = drawingParent;
     }
 
+    void setPowerHintSessionMode(bool early, bool late) {
+        mFlinger->mPowerHintSessionMode = {.late = late, .early = early};
+    }
+
     /* ------------------------------------------------------------------------
      * Forwarding for functions being tested
      */
@@ -741,6 +745,7 @@
                 mHwcDisplayId(hwcDisplayId) {
             mCreationArgs.connectionType = connectionType;
             mCreationArgs.isPrimary = isPrimary;
+            mCreationArgs.initialPowerMode = hal::PowerMode::ON;
         }
 
         sp<IBinder> token() const { return mDisplayToken; }
@@ -793,7 +798,7 @@
             return *this;
         }
 
-        auto& setPowerMode(hal::PowerMode mode) {
+        auto& setPowerMode(std::optional<hal::PowerMode> mode) {
             mCreationArgs.initialPowerMode = mode;
             return *this;
         }
@@ -862,6 +867,10 @@
                                   .deviceProductInfo = {},
                                   .supportedModes = modes,
                                   .activeMode = activeMode->get()};
+
+                if (mCreationArgs.isPrimary) {
+                    mFlinger.mutableActiveDisplayToken() = mDisplayToken;
+                }
             }
 
             state.isSecure = mCreationArgs.isSecure;
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index 4eb9055..30a3f9a 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -349,6 +349,23 @@
     }
 }
 
+TEST_F(VSyncReactorTest, addHwVsyncTimestampDozePreempt) {
+    bool periodFlushed = false;
+    nsecs_t const newPeriod = 4000;
+
+    mReactor.startPeriodTransition(newPeriod);
+
+    auto time = 0;
+    // If the power mode is not DOZE or DOZE_SUSPEND, it is still collecting timestamps.
+    EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time, std::nullopt, &periodFlushed));
+    EXPECT_FALSE(periodFlushed);
+
+    // Set power mode to DOZE to trigger period flushing.
+    mReactor.setDisplayPowerMode(hal::PowerMode::DOZE);
+    EXPECT_FALSE(mReactor.addHwVsyncTimestamp(time, std::nullopt, &periodFlushed));
+    EXPECT_TRUE(periodFlushed);
+}
+
 TEST_F(VSyncReactorTest, addPresentFenceWhileAwaitingPeriodConfirmationRequestsHwVsync) {
     auto time = 0;
     bool periodFlushed = false;
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp
new file mode 100644
index 0000000..5049b1d
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockAidlPowerHalWrapper.h"
+#include "MockIPower.h"
+
+namespace android::Hwc2::mock {
+
+MockAidlPowerHalWrapper::MockAidlPowerHalWrapper()
+      : AidlPowerHalWrapper(sp<testing::NiceMock<MockIPower>>::make()){};
+MockAidlPowerHalWrapper::~MockAidlPowerHalWrapper() = default;
+
+} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h
new file mode 100644
index 0000000..657ced3
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "DisplayHardware/PowerAdvisor.h"
+
+namespace android {
+namespace hardware {
+namespace power {
+class IPower;
+}
+} // namespace hardware
+} // namespace android
+
+namespace android::Hwc2::mock {
+
+class MockAidlPowerHalWrapper : public Hwc2::impl::AidlPowerHalWrapper {
+public:
+    MockAidlPowerHalWrapper();
+    ~MockAidlPowerHalWrapper() override;
+    MOCK_METHOD(bool, setExpensiveRendering, (bool enabled), (override));
+    MOCK_METHOD(bool, notifyDisplayUpdateImminent, (), (override));
+    MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
+    MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override));
+    MOCK_METHOD(void, restartPowerHintSession, (), (override));
+    MOCK_METHOD(void, setPowerHintSessionThreadIds, (const std::vector<int32_t>& threadIds),
+                (override));
+    MOCK_METHOD(bool, startPowerHintSession, (), (override));
+    MOCK_METHOD(void, setTargetWorkDuration, (nsecs_t targetDuration), (override));
+    MOCK_METHOD(void, sendActualWorkDuration, (nsecs_t actualDuration, nsecs_t timestamp),
+                (override));
+    MOCK_METHOD(bool, shouldReconnectHAL, (), (override));
+};
+
+} // namespace android::Hwc2::mock
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
index a83ecbc..6809580 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
@@ -33,4 +33,24 @@
             .build();
 }
 
+inline DisplayModePtr cloneForDisplay(PhysicalDisplayId displayId, const DisplayModePtr& modePtr) {
+    return DisplayMode::Builder(modePtr->getHwcId())
+            .setId(modePtr->getId())
+            .setPhysicalDisplayId(displayId)
+            .setVsyncPeriod(modePtr->getVsyncPeriod())
+            .setGroup(modePtr->getGroup())
+            .setResolution(modePtr->getResolution())
+            .build();
+}
+
+inline DisplayModes cloneForDisplay(PhysicalDisplayId displayId, const DisplayModes& modes) {
+    DisplayModes clones;
+
+    for (const auto& [id, modePtr] : modes) {
+        clones.try_emplace(id, cloneForDisplay(displayId, modePtr));
+    }
+
+    return clones;
+}
+
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
index c598cbc..aede250 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
@@ -36,11 +36,33 @@
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
     MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override));
-    MOCK_METHOD(void, setTargetWorkDuration, (int64_t targetDurationNanos), (override));
-    MOCK_METHOD(void, sendActualWorkDuration, (int64_t actualDurationNanos, nsecs_t timestamp),
-                (override));
+    MOCK_METHOD(void, setTargetWorkDuration, (int64_t targetDuration), (override));
+    MOCK_METHOD(void, sendActualWorkDuration, (), (override));
+    MOCK_METHOD(void, sendPredictedWorkDuration, (), (override));
     MOCK_METHOD(void, enablePowerHint, (bool enabled), (override));
     MOCK_METHOD(bool, startPowerHintSession, (const std::vector<int32_t>& threadIds), (override));
+    MOCK_METHOD(void, setGpuFenceTime,
+                (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
+    MOCK_METHOD(void, setHwcValidateTiming,
+                (DisplayId displayId, nsecs_t valiateStartTime, nsecs_t validateEndTime),
+                (override));
+    MOCK_METHOD(void, setHwcPresentTiming,
+                (DisplayId displayId, nsecs_t presentStartTime, nsecs_t presentEndTime),
+                (override));
+    MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override));
+    MOCK_METHOD(void, setRequiresClientComposition,
+                (DisplayId displayId, bool requiresClientComposition), (override));
+    MOCK_METHOD(void, setExpectedPresentTime, (nsecs_t expectedPresentTime), (override));
+    MOCK_METHOD(void, setSfPresentTiming, (nsecs_t presentFenceTime, nsecs_t presentEndTime),
+                (override));
+    MOCK_METHOD(void, setHwcPresentDelayedTime,
+                (DisplayId displayId,
+                 std::chrono::steady_clock::time_point earliestFrameStartTime));
+    MOCK_METHOD(void, setFrameDelay, (nsecs_t frameDelayDuration), (override));
+    MOCK_METHOD(void, setCommitStart, (nsecs_t commitStartTime), (override));
+    MOCK_METHOD(void, setCompositeEnd, (nsecs_t compositeEndtime), (override));
+    MOCK_METHOD(void, setDisplays, (std::vector<DisplayId> & displayIds), (override));
+    MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (int64_t targetDuration), (override));
 };
 
 } // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
index 314f681..4ef91da 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
@@ -31,6 +31,7 @@
     MOCK_METHOD3(addHwVsyncTimestamp, bool(nsecs_t, std::optional<nsecs_t>, bool*));
     MOCK_METHOD1(startPeriodTransition, void(nsecs_t));
     MOCK_METHOD1(setIgnorePresentFences, void(bool));
+    MOCK_METHOD(void, setDisplayPowerMode, (hal::PowerMode), (override));
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };