Merge "SF: Remove "-fvisibility=hidden" flag from sf build"
diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
index 0712c0a..28e5ee2 100644
--- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
@@ -240,11 +240,11 @@
     EXPECT_GE(st.st_size, 1000000 /* 1MB */);
 }
 
-TEST_F(ZippedBugreportGenerationTest, TakesBetween30And300Seconds) {
-    EXPECT_GE(duration, 30s) << "Expected completion in more than 30s. Actual time "
-                             << duration.count() << " s.";
+TEST_F(ZippedBugreportGenerationTest, TakesBetween20And300Seconds) {
+    EXPECT_GE(duration, 20s) << "Expected completion in more than 20s. Actual time "
+                             << duration.count() << " ms.";
     EXPECT_LE(duration, 300s) << "Expected completion in less than 300s. Actual time "
-                              << duration.count() << " s.";
+                              << duration.count() << " ms.";
 }
 
 /**
diff --git a/cmds/dumpsys/tests/dumpsys_test.cpp b/cmds/dumpsys/tests/dumpsys_test.cpp
index 677d6c7..49c1318 100644
--- a/cmds/dumpsys/tests/dumpsys_test.cpp
+++ b/cmds/dumpsys/tests/dumpsys_test.cpp
@@ -61,6 +61,10 @@
     MOCK_METHOD1(getDeclaredInstances, Vector<String16>(const String16&));
     MOCK_METHOD1(updatableViaApex, std::optional<String16>(const String16&));
     MOCK_METHOD1(getConnectionInfo, std::optional<ConnectionInfo>(const String16&));
+    MOCK_METHOD2(registerForNotifications, status_t(const String16&,
+                                             const sp<LocalRegistrationCallback>&));
+    MOCK_METHOD2(unregisterForNotifications, status_t(const String16&,
+                                             const sp<LocalRegistrationCallback>&));
   protected:
     MOCK_METHOD0(onAsBinder, IBinder*());
 };
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index b6f42ad..c796da6 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -244,7 +244,7 @@
 // The location is the profile name for primary apks or the dex path for secondary dex files.
 bool clear_primary_current_profiles(const std::string& package_name, const std::string& location) {
     bool success = true;
-    // For secondary dex files, we don't really need the user but we use it for sanity checks.
+    // For secondary dex files, we don't really need the user but we use it for validity checks.
     std::vector<userid_t> users = get_known_users(/*volume_uuid*/ nullptr);
     for (auto user : users) {
         success &= clear_current_profile(package_name, location, user, /*is_secondary_dex*/false);
@@ -468,7 +468,7 @@
     *reference_profile_fd = open_reference_profile(uid, package_name, location,
             /*read_write*/ true, is_secondary_dex);
 
-    // For secondary dex files, we don't really need the user but we use it for sanity checks.
+    // For secondary dex files, we don't really need the user but we use it for validity checks.
     // Note: the user owning the dex file should be the current user.
     std::vector<userid_t> users;
     if (is_secondary_dex){
diff --git a/cmds/installd/utils.cpp b/cmds/installd/utils.cpp
index c4ecd07..0f8a732 100644
--- a/cmds/installd/utils.cpp
+++ b/cmds/installd/utils.cpp
@@ -829,7 +829,7 @@
  * to top level directories (i.e. have "..").
  */
 static int validate_path(const std::string& dir, const std::string& path, int maxSubdirs) {
-    // Argument sanity checking
+    // Argument check
     if (dir.find('/') != 0 || dir.rfind('/') != dir.size() - 1
             || dir.find("..") != std::string::npos) {
         LOG(ERROR) << "Invalid directory " << dir;
diff --git a/cmds/service/service.cpp b/cmds/service/service.cpp
index fe417a3..d5ca725 100644
--- a/cmds/service/service.cpp
+++ b/cmds/service/service.cpp
@@ -21,6 +21,7 @@
 #include <cutils/ashmem.h>
 
 #include <getopt.h>
+#include <libgen.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
diff --git a/headers/Android.bp b/headers/Android.bp
index 7481a23..cb18837 100644
--- a/headers/Android.bp
+++ b/headers/Android.bp
@@ -28,6 +28,11 @@
         "libstagefright_foundation_headers",
     ],
     min_sdk_version: "29",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media",
+        "com.android.media.swcodec",
+    ],
 
     host_supported: true,
     target: {
diff --git a/include/android/bitmap.h b/include/android/bitmap.h
index 6704a1d..35f87f9 100644
--- a/include/android/bitmap.h
+++ b/include/android/bitmap.h
@@ -68,6 +68,8 @@
     ANDROID_BITMAP_FORMAT_A_8       = 8,
     /** Each component is stored as a half float. **/
     ANDROID_BITMAP_FORMAT_RGBA_F16  = 9,
+    /** Red: 10 bits, Green: 10 bits, Blue: 10 bits, Alpha: 2 bits. **/
+    ANDROID_BITMAP_FORMAT_RGBA_1010102 = 10,
 };
 
 /** Bitmap alpha format */
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
new file mode 100644
index 0000000..5fa47f6
--- /dev/null
+++ b/include/android/performance_hint.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_NATIVE_PERFORMANCE_HINT_H
+#define ANDROID_NATIVE_PERFORMANCE_HINT_H
+
+#include <sys/cdefs.h>
+
+/******************************************************************
+ *
+ * IMPORTANT NOTICE:
+ *
+ *   This file is part of Android's set of stable system headers
+ *   exposed by the Android NDK (Native Development Kit).
+ *
+ *   Third-party source AND binary code relies on the definitions
+ *   here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
+ *
+ *   - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
+ *   - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
+ *   - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
+ *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
+ */
+
+#include <android/api-level.h>
+#include <stdint.h>
+
+__BEGIN_DECLS
+
+struct APerformanceHintManager;
+struct APerformanceHintSession;
+
+/**
+ * An opaque type representing a handle to a performance hint manager.
+ * It must be released after use.
+ *
+ * <p>To use:<ul>
+ *    <li>Obtain the performance hint manager instance by calling
+ *        {@link APerformanceHint_getManager} function.</li>
+ *    <li>Create an {@link APerformanceHintSession} with
+ *        {@link APerformanceHint_createSession}.</li>
+ *    <li>Get the preferred update rate in nanoseconds with
+ *        {@link APerformanceHint_getPreferredUpdateRateNanos}.</li>
+ */
+typedef struct APerformanceHintManager APerformanceHintManager;
+
+/**
+ * An opaque type representing a handle to a performance hint session.
+ * A session can only be acquired from a {@link APerformanceHintManager}
+ * with {@link APerformanceHint_getPreferredUpdateRateNanos}. It must be
+ * freed with {@link APerformanceHint_closeSession} after use.
+ *
+ * A Session represents a group of threads with an inter-related workload such that hints for
+ * their performance should be considered as a unit. The threads in a given session should be
+ * long-life and not created or destroyed dynamically.
+ *
+ * <p>Each session is expected to have a periodic workload with a target duration for each
+ * cycle. The cycle duration is likely greater than the target work duration to allow other
+ * parts of the pipeline to run within the available budget. For example, a renderer thread may
+ * work at 60hz in order to produce frames at the display's frame but have a target work
+ * duration of only 6ms.</p>
+ *
+ * <p>After each cycle of work, the client is expected to use
+ * {@link APerformanceHint_reportActualWorkDuration} to report the actual time taken to
+ * complete.</p>
+ *
+ * <p>To use:<ul>
+ *    <li>Update a sessions target duration for each cycle of work
+ *        with  {@link APerformanceHint_updateTargetWorkDuration}.</li>
+ *    <li>Report the actual duration for the last cycle of work with
+ *        {@link APerformanceHint_reportActualWorkDuration}.</li>
+ *    <li>Release the session instance with
+ *        {@link APerformanceHint_closeSession}.</li></ul></p>
+ */
+typedef struct APerformanceHintSession APerformanceHintSession;
+
+/**
+  * Acquire an instance of the performance hint manager.
+  *
+  * @return manager instance on success, nullptr on failure.
+  */
+APerformanceHintManager* APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Creates a session for the given set of threads and sets their initial target work
+ * duration.
+ * @param manager The performance hint manager instance.
+ * @param threadIds The list of threads to be associated with this session. They must be part of
+ *     this app's thread group.
+ * @param size the size of threadIds.
+ * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new session.
+ *     This must be positive.
+ * @return manager instance on success, nullptr on failure.
+ */
+APerformanceHintSession* APerformanceHint_createSession(
+        APerformanceHintManager* manager,
+        const int32_t* threadIds, size_t size,
+        int64_t initialTargetWorkDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Get preferred update rate information for this device.
+ *
+ * @param manager The performance hint manager instance.
+ * @return the preferred update rate supported by device software.
+ */
+int64_t APerformanceHint_getPreferredUpdateRateNanos(
+        APerformanceHintManager* manager) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Updates this session's target duration for each cycle of work.
+ *
+ * @param session The performance hint session instance to update.
+ * @param targetDurationNanos the new desired duration in nanoseconds. This must be positive.
+ * @return 0 on success
+ *         EINVAL if targetDurationNanos is not positive.
+ *         EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_updateTargetWorkDuration(
+        APerformanceHintSession* session,
+        int64_t targetDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Reports the actual duration for the last cycle of work.
+ *
+ * <p>The system will attempt to adjust the core placement of the threads within the thread
+ * group and/or the frequency of the core on which they are run to bring the actual duration
+ * close to the target duration.</p>
+ *
+ * @param session The performance hint session instance to update.
+ * @param actualDurationNanos how long the thread group took to complete its last task in
+ *     nanoseconds. This must be positive.
+ * @return 0 on success
+ *         EINVAL if actualDurationNanos is not positive.
+ *         EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_reportActualWorkDuration(
+        APerformanceHintSession* session,
+        int64_t actualDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Release the performance hint manager pointer acquired via
+ * {@link APerformanceHint_createSession}.
+ *
+ * @param session The performance hint session instance to release.
+ */
+void APerformanceHint_closeSession(
+        APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__);
+
+__END_DECLS
+
+#endif // ANDROID_NATIVE_PERFORMANCE_HINT_H
diff --git a/include/input/Input.h b/include/input/Input.h
index f4147a0..e421dee 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -164,7 +164,7 @@
  * (We want at least 10 but some touch controllers obstensibly configured for 10 pointers
  * will occasionally emit 11.  There is not much harm making this constant bigger.)
  */
-#define MAX_POINTERS 16
+static constexpr size_t MAX_POINTERS = 16;
 
 /*
  * Maximum number of samples supported per motion event.
@@ -534,7 +534,7 @@
 
     inline int32_t getActionMasked() const { return getActionMasked(mAction); }
 
-    static int32_t getActionIndex(int32_t action) {
+    static uint8_t getActionIndex(int32_t action) {
         return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
                 AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
     }
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index edcb615..5f9a37d 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -500,24 +500,6 @@
     status_t sendTimeline(int32_t inputEventId,
                           std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
 
-    /* Returns true if there is a deferred event waiting.
-     *
-     * Should be called after calling consume() to determine whether the consumer
-     * has a deferred event to be processed.  Deferred events are somewhat special in
-     * that they have already been removed from the input channel.  If the input channel
-     * becomes empty, the client may need to do extra work to ensure that it processes
-     * the deferred event despite the fact that the input channel's file descriptor
-     * is not readable.
-     *
-     * One option is simply to call consume() in a loop until it returns WOULD_BLOCK.
-     * This guarantees that all deferred events will be processed.
-     *
-     * Alternately, the caller can call hasDeferredEvent() to determine whether there is
-     * a deferred event waiting and then ensure that its event loop wakes up at least
-     * one more time to consume the deferred event.
-     */
-    bool hasDeferredEvent() const;
-
     /* Returns true if there is a pending batch.
      *
      * Should be called after calling consume() with consumeBatches == false to determine
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index 5832bf4..f27f5f1 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -17,124 +17,8 @@
 #ifndef ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
 #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
 
-#include <stdint.h>
-
 __BEGIN_DECLS
 
-struct APerformanceHintManager;
-struct APerformanceHintSession;
-
-/**
- * An opaque type representing a handle to a performance hint manager.
- * It must be released after use.
- *
- * <p>To use:<ul>
- *    <li>Obtain the performance hint manager instance by calling
- *        {@link APerformanceHint_getManager} function.</li>
- *    <li>Create an {@link APerformanceHintSession} with
- *        {@link APerformanceHint_createSession}.</li>
- *    <li>Get the preferred update rate in nanoseconds with
- *        {@link APerformanceHint_getPreferredUpdateRateNanos}.</li>
- */
-typedef struct APerformanceHintManager APerformanceHintManager;
-
-/**
- * An opaque type representing a handle to a performance hint session.
- * A session can only be acquired from a {@link APerformanceHintManager}
- * with {@link APerformanceHint_getPreferredUpdateRateNanos}. It must be
- * freed with {@link APerformanceHint_closeSession} after use.
- *
- * A Session represents a group of threads with an inter-related workload such that hints for
- * their performance should be considered as a unit. The threads in a given session should be
- * long-life and not created or destroyed dynamically.
- *
- * <p>Each session is expected to have a periodic workload with a target duration for each
- * cycle. The cycle duration is likely greater than the target work duration to allow other
- * parts of the pipeline to run within the available budget. For example, a renderer thread may
- * work at 60hz in order to produce frames at the display's frame but have a target work
- * duration of only 6ms.</p>
- *
- * <p>After each cycle of work, the client is expected to use
- * {@link APerformanceHint_reportActualWorkDuration} to report the actual time taken to
- * complete.</p>
- *
- * <p>To use:<ul>
- *    <li>Update a sessions target duration for each cycle of work
- *        with  {@link APerformanceHint_updateTargetWorkDuration}.</li>
- *    <li>Report the actual duration for the last cycle of work with
- *        {@link APerformanceHint_reportActualWorkDuration}.</li>
- *    <li>Release the session instance with
- *        {@link APerformanceHint_closeSession}.</li></ul></p>
- */
-typedef struct APerformanceHintSession APerformanceHintSession;
-
-/**
-  * Acquire an instance of the performance hint manager.
-  *
-  * @return manager instance on success, nullptr on failure.
-  */
-APerformanceHintManager* APerformanceHint_getManager();
-
-/**
- * Creates a session for the given set of threads and sets their initial target work
- * duration.
- * @param manager The performance hint manager instance.
- * @param threadIds The list of threads to be associated with this session. They must be part of
- *     this app's thread group.
- * @param size the size of threadIds.
- * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new session.
- *     This must be positive.
- * @return manager instance on success, nullptr on failure.
- */
-APerformanceHintSession* APerformanceHint_createSession(APerformanceHintManager* manager,
-                                                        const int32_t* threadIds, size_t size,
-                                                        int64_t initialTargetWorkDurationNanos);
-
-/**
- * Get preferred update rate information for this device.
- *
- * @param manager The performance hint manager instance.
- * @return the preferred update rate supported by device software.
- */
-int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* manager);
-
-/**
- * Updates this session's target duration for each cycle of work.
- *
- * @param session The performance hint session instance to update.
- * @param targetDurationNanos the new desired duration in nanoseconds. This must be positive.
- * @return 0 on success
- *         EINVAL if targetDurationNanos is not positive.
- *         EPIPE if communication with the system service has failed.
- */
-int APerformanceHint_updateTargetWorkDuration(APerformanceHintSession* session,
-                                               int64_t targetDurationNanos);
-
-/**
- * Reports the actual duration for the last cycle of work.
- *
- * <p>The system will attempt to adjust the core placement of the threads within the thread
- * group and/or the frequency of the core on which they are run to bring the actual duration
- * close to the target duration.</p>
- *
- * @param session The performance hint session instance to update.
- * @param actualDurationNanos how long the thread group took to complete its last task in
- *     nanoseconds. This must be positive.
- * @return 0 on success
- *         EINVAL if actualDurationNanos is not positive.
- *         EPIPE if communication with the system service has failed.
- */
-int APerformanceHint_reportActualWorkDuration(APerformanceHintSession* session,
-                                               int64_t actualDurationNanos);
-
-/**
- * Release the performance hint manager pointer acquired via
- * {@link APerformanceHint_createSession}.
- *
- * @param session The performance hint session instance to release.
- */
-void APerformanceHint_closeSession(APerformanceHintSession* session);
-
 /**
  * For testing only.
  */
diff --git a/libs/arect/Android.bp b/libs/arect/Android.bp
index bb40f51..41b3460 100644
--- a/libs/arect/Android.bp
+++ b/libs/arect/Android.bp
@@ -57,4 +57,11 @@
         },
     },
     min_sdk_version: "29",
+    // static link, so it won't straddle a module boundary at runtime.
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media",
+        "com.android.media.swcodec",
+    ],
+
 }
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 81e61da..ea2f8d2 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -43,6 +43,8 @@
 
 namespace android {
 
+using AidlRegistrationCallback = IServiceManager::LocalRegistrationCallback;
+
 using AidlServiceManager = android::os::IServiceManager;
 using android::binder::Status;
 
@@ -79,7 +81,24 @@
     Vector<String16> getDeclaredInstances(const String16& interface) override;
     std::optional<String16> updatableViaApex(const String16& name) override;
     std::optional<IServiceManager::ConnectionInfo> getConnectionInfo(const String16& name) override;
+    class RegistrationWaiter : public android::os::BnServiceCallback {
+    public:
+        explicit RegistrationWaiter(const sp<AidlRegistrationCallback>& callback)
+              : mImpl(callback) {}
+        Status onRegistration(const std::string& name, const sp<IBinder>& binder) override {
+            mImpl->onServiceRegistration(String16(name.c_str()), binder);
+            return Status::ok();
+        }
 
+    private:
+        sp<AidlRegistrationCallback> mImpl;
+    };
+
+    status_t registerForNotifications(const String16& service,
+                                      const sp<AidlRegistrationCallback>& cb) override;
+
+    status_t unregisterForNotifications(const String16& service,
+                                        const sp<AidlRegistrationCallback>& cb) override;
     // for legacy ABI
     const String16& getInterfaceDescriptor() const override {
         return mTheRealServiceManager->getInterfaceDescriptor();
@@ -90,6 +109,17 @@
 
 protected:
     sp<AidlServiceManager> mTheRealServiceManager;
+    // AidlRegistrationCallback -> services that its been registered for
+    // notifications.
+    using LocalRegistrationAndWaiter =
+            std::pair<sp<LocalRegistrationCallback>, sp<RegistrationWaiter>>;
+    using ServiceCallbackMap = std::map<std::string, std::vector<LocalRegistrationAndWaiter>>;
+    ServiceCallbackMap mNameToRegistrationCallback;
+    std::mutex mNameToRegistrationLock;
+
+    void removeRegistrationCallbackLocked(const sp<AidlRegistrationCallback>& cb,
+                                          ServiceCallbackMap::iterator* it,
+                                          sp<RegistrationWaiter>* waiter);
 
     // Directly get the service in a way that, for lazy services, requests the service to be started
     // if it is not currently started. This way, calls directly to ServiceManagerShim::getService
@@ -442,6 +472,77 @@
             : std::nullopt;
 }
 
+status_t ServiceManagerShim::registerForNotifications(const String16& name,
+                                                      const sp<AidlRegistrationCallback>& cb) {
+    if (cb == nullptr) {
+        ALOGE("%s: null cb passed", __FUNCTION__);
+        return BAD_VALUE;
+    }
+    std::string nameStr = String8(name).c_str();
+    sp<RegistrationWaiter> registrationWaiter = sp<RegistrationWaiter>::make(cb);
+    std::lock_guard<std::mutex> lock(mNameToRegistrationLock);
+    if (Status status =
+                mTheRealServiceManager->registerForNotifications(nameStr, registrationWaiter);
+        !status.isOk()) {
+        ALOGW("Failed to registerForNotifications for %s: %s", nameStr.c_str(),
+              status.toString8().c_str());
+        return UNKNOWN_ERROR;
+    }
+    mNameToRegistrationCallback[nameStr].push_back(std::make_pair(cb, registrationWaiter));
+    return OK;
+}
+
+void ServiceManagerShim::removeRegistrationCallbackLocked(const sp<AidlRegistrationCallback>& cb,
+                                                          ServiceCallbackMap::iterator* it,
+                                                          sp<RegistrationWaiter>* waiter) {
+    std::vector<LocalRegistrationAndWaiter>& localRegistrationAndWaiters = (*it)->second;
+    for (auto lit = localRegistrationAndWaiters.begin();
+         lit != localRegistrationAndWaiters.end();) {
+        if (lit->first == cb) {
+            if (waiter) {
+                *waiter = lit->second;
+            }
+            lit = localRegistrationAndWaiters.erase(lit);
+        } else {
+            ++lit;
+        }
+    }
+
+    if (localRegistrationAndWaiters.empty()) {
+        mNameToRegistrationCallback.erase(*it);
+    }
+}
+
+status_t ServiceManagerShim::unregisterForNotifications(const String16& name,
+                                                        const sp<AidlRegistrationCallback>& cb) {
+    if (cb == nullptr) {
+        ALOGE("%s: null cb passed", __FUNCTION__);
+        return BAD_VALUE;
+    }
+    std::string nameStr = String8(name).c_str();
+    std::lock_guard<std::mutex> lock(mNameToRegistrationLock);
+    auto it = mNameToRegistrationCallback.find(nameStr);
+    sp<RegistrationWaiter> registrationWaiter;
+    if (it != mNameToRegistrationCallback.end()) {
+        removeRegistrationCallbackLocked(cb, &it, &registrationWaiter);
+    } else {
+        ALOGE("%s no callback registered for notifications on %s", __FUNCTION__, nameStr.c_str());
+        return BAD_VALUE;
+    }
+    if (registrationWaiter == nullptr) {
+        ALOGE("%s Callback passed wasn't used to register for notifications", __FUNCTION__);
+        return BAD_VALUE;
+    }
+    if (Status status = mTheRealServiceManager->unregisterForNotifications(String8(name).c_str(),
+                                                                           registrationWaiter);
+        !status.isOk()) {
+        ALOGW("Failed to get service manager to unregisterForNotifications for %s: %s",
+              String8(name).c_str(), status.toString8().c_str());
+        return UNKNOWN_ERROR;
+    }
+    return OK;
+}
+
 #ifndef __ANDROID__
 // ServiceManagerShim for host. Implements the old libbinder android::IServiceManager API.
 // The internal implementation of the AIDL interface android::os::IServiceManager calls into
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index 6286c9c..4ddbce7 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -310,9 +310,9 @@
 }
 
 status_t RpcState::rpcSend(const sp<RpcSession::RpcConnection>& connection,
-                           const sp<RpcSession>& session, const char* what, iovec* iovs,
-                           size_t niovs, const std::function<status_t()>& altPoll) {
-    for (size_t i = 0; i < niovs; i++) {
+                           const sp<RpcSession>& session, const char* what, iovec* iovs, int niovs,
+                           const std::function<status_t()>& altPoll) {
+    for (int i = 0; i < niovs; i++) {
         LOG_RPC_DETAIL("Sending %s on RpcTransport %p: %s", what, connection->rpcTransport.get(),
                        android::base::HexString(iovs[i].iov_base, iovs[i].iov_len).c_str());
     }
@@ -321,7 +321,7 @@
                 connection->rpcTransport->interruptableWriteFully(session->mShutdownTrigger.get(),
                                                                   iovs, niovs, altPoll);
         status != OK) {
-        LOG_RPC_DETAIL("Failed to write %s (%zu iovs) on RpcTransport %p, error: %s", what, niovs,
+        LOG_RPC_DETAIL("Failed to write %s (%d iovs) on RpcTransport %p, error: %s", what, niovs,
                        connection->rpcTransport.get(), statusToString(status).c_str());
         (void)session->shutdownAndWait(false);
         return status;
@@ -331,19 +331,18 @@
 }
 
 status_t RpcState::rpcRec(const sp<RpcSession::RpcConnection>& connection,
-                          const sp<RpcSession>& session, const char* what, iovec* iovs,
-                          size_t niovs) {
+                          const sp<RpcSession>& session, const char* what, iovec* iovs, int niovs) {
     if (status_t status =
                 connection->rpcTransport->interruptableReadFully(session->mShutdownTrigger.get(),
                                                                  iovs, niovs, {});
         status != OK) {
-        LOG_RPC_DETAIL("Failed to read %s (%zu iovs) on RpcTransport %p, error: %s", what, niovs,
+        LOG_RPC_DETAIL("Failed to read %s (%d iovs) on RpcTransport %p, error: %s", what, niovs,
                        connection->rpcTransport.get(), statusToString(status).c_str());
         (void)session->shutdownAndWait(false);
         return status;
     }
 
-    for (size_t i = 0; i < niovs; i++) {
+    for (int i = 0; i < niovs; i++) {
         LOG_RPC_DETAIL("Received %s on RpcTransport %p: %s", what, connection->rpcTransport.get(),
                        android::base::HexString(iovs[i].iov_base, iovs[i].iov_len).c_str());
     }
diff --git a/libs/binder/RpcState.h b/libs/binder/RpcState.h
index 5cad394..f4a0894 100644
--- a/libs/binder/RpcState.h
+++ b/libs/binder/RpcState.h
@@ -180,11 +180,10 @@
 
     [[nodiscard]] status_t rpcSend(const sp<RpcSession::RpcConnection>& connection,
                                    const sp<RpcSession>& session, const char* what, iovec* iovs,
-                                   size_t niovs,
-                                   const std::function<status_t()>& altPoll = nullptr);
+                                   int niovs, const std::function<status_t()>& altPoll = nullptr);
     [[nodiscard]] status_t rpcRec(const sp<RpcSession::RpcConnection>& connection,
                                   const sp<RpcSession>& session, const char* what, iovec* iovs,
-                                  size_t niovs);
+                                  int niovs);
 
     [[nodiscard]] status_t waitForReply(const sp<RpcSession::RpcConnection>& connection,
                                         const sp<RpcSession>& session, Parcel* reply);
diff --git a/libs/binder/RpcTransportRaw.cpp b/libs/binder/RpcTransportRaw.cpp
index 2182e18..636e5d0 100644
--- a/libs/binder/RpcTransportRaw.cpp
+++ b/libs/binder/RpcTransportRaw.cpp
@@ -44,11 +44,15 @@
     }
 
     template <typename SendOrReceive>
-    status_t interruptableReadOrWrite(FdTrigger* fdTrigger, iovec* iovs, size_t niovs,
+    status_t interruptableReadOrWrite(FdTrigger* fdTrigger, iovec* iovs, int niovs,
                                       SendOrReceive sendOrReceiveFun, const char* funName,
                                       int16_t event, const std::function<status_t()>& altPoll) {
         MAYBE_WAIT_IN_FLAKE_MODE;
 
+        if (niovs < 0) {
+            return BAD_VALUE;
+        }
+
         // Since we didn't poll, we need to manually check to see if it was triggered. Otherwise, we
         // may never know we should be shutting down.
         if (fdTrigger->isTriggered()) {
@@ -74,7 +78,9 @@
         while (true) {
             msghdr msg{
                     .msg_iov = iovs,
-                    .msg_iovlen = niovs,
+                    // posix uses int, glibc uses size_t.  niovs is a
+                    // non-negative int and can be cast to either.
+                    .msg_iovlen = static_cast<decltype(msg.msg_iovlen)>(niovs),
             };
             ssize_t processSize =
                     TEMP_FAILURE_RETRY(sendOrReceiveFun(mSocket.get(), &msg, MSG_NOSIGNAL));
@@ -128,13 +134,13 @@
         }
     }
 
-    status_t interruptableWriteFully(FdTrigger* fdTrigger, iovec* iovs, size_t niovs,
+    status_t interruptableWriteFully(FdTrigger* fdTrigger, iovec* iovs, int niovs,
                                      const std::function<status_t()>& altPoll) override {
         return interruptableReadOrWrite(fdTrigger, iovs, niovs, sendmsg, "sendmsg", POLLOUT,
                                         altPoll);
     }
 
-    status_t interruptableReadFully(FdTrigger* fdTrigger, iovec* iovs, size_t niovs,
+    status_t interruptableReadFully(FdTrigger* fdTrigger, iovec* iovs, int niovs,
                                     const std::function<status_t()>& altPoll) override {
         return interruptableReadOrWrite(fdTrigger, iovs, niovs, recvmsg, "recvmsg", POLLIN,
                                         altPoll);
diff --git a/libs/binder/RpcTransportTls.cpp b/libs/binder/RpcTransportTls.cpp
index c05ea15..3936204 100644
--- a/libs/binder/RpcTransportTls.cpp
+++ b/libs/binder/RpcTransportTls.cpp
@@ -275,9 +275,9 @@
     RpcTransportTls(android::base::unique_fd socket, Ssl ssl)
           : mSocket(std::move(socket)), mSsl(std::move(ssl)) {}
     Result<size_t> peek(void* buf, size_t size) override;
-    status_t interruptableWriteFully(FdTrigger* fdTrigger, iovec* iovs, size_t niovs,
+    status_t interruptableWriteFully(FdTrigger* fdTrigger, iovec* iovs, int niovs,
                                      const std::function<status_t()>& altPoll) override;
-    status_t interruptableReadFully(FdTrigger* fdTrigger, iovec* iovs, size_t niovs,
+    status_t interruptableReadFully(FdTrigger* fdTrigger, iovec* iovs, int niovs,
                                     const std::function<status_t()>& altPoll) override;
 
 private:
@@ -303,16 +303,18 @@
     return ret;
 }
 
-status_t RpcTransportTls::interruptableWriteFully(FdTrigger* fdTrigger, iovec* iovs, size_t niovs,
+status_t RpcTransportTls::interruptableWriteFully(FdTrigger* fdTrigger, iovec* iovs, int niovs,
                                                   const std::function<status_t()>& altPoll) {
     MAYBE_WAIT_IN_FLAKE_MODE;
 
+    if (niovs < 0) return BAD_VALUE;
+
     // Before doing any I/O, check trigger once. This ensures the trigger is checked at least
     // once. The trigger is also checked via triggerablePoll() after every SSL_write().
     if (fdTrigger->isTriggered()) return DEAD_OBJECT;
 
     size_t size = 0;
-    for (size_t i = 0; i < niovs; i++) {
+    for (int i = 0; i < niovs; i++) {
         const iovec& iov = iovs[i];
         if (iov.iov_len == 0) {
             continue;
@@ -343,16 +345,18 @@
     return OK;
 }
 
-status_t RpcTransportTls::interruptableReadFully(FdTrigger* fdTrigger, iovec* iovs, size_t niovs,
+status_t RpcTransportTls::interruptableReadFully(FdTrigger* fdTrigger, iovec* iovs, int niovs,
                                                  const std::function<status_t()>& altPoll) {
     MAYBE_WAIT_IN_FLAKE_MODE;
 
+    if (niovs < 0) return BAD_VALUE;
+
     // Before doing any I/O, check trigger once. This ensures the trigger is checked at least
     // once. The trigger is also checked via triggerablePoll() after every SSL_write().
     if (fdTrigger->isTriggered()) return DEAD_OBJECT;
 
     size_t size = 0;
-    for (size_t i = 0; i < niovs; i++) {
+    for (int i = 0; i < niovs; i++) {
         const iovec& iov = iovs[i];
         if (iov.iov_len == 0) {
             continue;
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index 240e3c2..ea40db8 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -115,6 +115,17 @@
         unsigned int port;
     };
     virtual std::optional<ConnectionInfo> getConnectionInfo(const String16& name) = 0;
+
+    struct LocalRegistrationCallback : public virtual RefBase {
+        virtual void onServiceRegistration(const String16& instance, const sp<IBinder>& binder) = 0;
+        virtual ~LocalRegistrationCallback() {}
+    };
+
+    virtual status_t registerForNotifications(const String16& name,
+                                              const sp<LocalRegistrationCallback>& callback) = 0;
+
+    virtual status_t unregisterForNotifications(const String16& name,
+                                                const sp<LocalRegistrationCallback>& callback) = 0;
 };
 
 sp<IServiceManager> defaultServiceManager();
diff --git a/libs/binder/include/binder/RpcTransport.h b/libs/binder/include/binder/RpcTransport.h
index 348bfeb..ade2d94 100644
--- a/libs/binder/include/binder/RpcTransport.h
+++ b/libs/binder/include/binder/RpcTransport.h
@@ -58,10 +58,10 @@
      *   error - interrupted (failure or trigger)
      */
     [[nodiscard]] virtual status_t interruptableWriteFully(
-            FdTrigger *fdTrigger, iovec *iovs, size_t niovs,
+            FdTrigger *fdTrigger, iovec *iovs, int niovs,
             const std::function<status_t()> &altPoll) = 0;
     [[nodiscard]] virtual status_t interruptableReadFully(
-            FdTrigger *fdTrigger, iovec *iovs, size_t niovs,
+            FdTrigger *fdTrigger, iovec *iovs, int niovs,
             const std::function<status_t()> &altPoll) = 0;
 
 protected:
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index 90cbf9d..355b3b4 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -44,6 +44,7 @@
         "libtokio",
     ],
     host_supported: true,
+    vendor_available: true,
     target: {
         darwin: {
             enabled: false,
@@ -52,8 +53,10 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.compos",
+        "com.android.uwb",
         "com.android.virt",
     ],
+    min_sdk_version: "Tiramisu",
 }
 
 rust_library {
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 63a4b2c..700940a 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -1220,6 +1220,19 @@
     EXPECT_THAT(server->transact(BINDER_LIB_TEST_CAN_GET_SID, data, nullptr), StatusEq(OK));
 }
 
+TEST(ServiceNotifications, Unregister) {
+    auto sm = defaultServiceManager();
+    using LocalRegistrationCallback = IServiceManager::LocalRegistrationCallback;
+    class LocalRegistrationCallbackImpl : public virtual LocalRegistrationCallback {
+        void onServiceRegistration(const String16 &, const sp<IBinder> &) override {}
+        virtual ~LocalRegistrationCallbackImpl() {}
+    };
+    sp<LocalRegistrationCallback> cb = sp<LocalRegistrationCallbackImpl>::make();
+
+    EXPECT_EQ(sm->registerForNotifications(String16("RogerRafa"), cb), OK);
+    EXPECT_EQ(sm->unregisterForNotifications(String16("RogerRafa"), cb), OK);
+}
+
 class BinderLibRpcTestBase : public BinderLibTest {
 public:
     void SetUp() override {
diff --git a/libs/binder/tests/rpc_fuzzer/corpus/transact_on_binder b/libs/binder/tests/rpc_fuzzer/corpus/transact_on_binder
new file mode 100644
index 0000000..ae081e6
--- /dev/null
+++ b/libs/binder/tests/rpc_fuzzer/corpus/transact_on_binder
Binary files differ
diff --git a/libs/binderthreadstate/Android.bp b/libs/binderthreadstate/Android.bp
index 0a82463..4860613 100644
--- a/libs/binderthreadstate/Android.bp
+++ b/libs/binderthreadstate/Android.bp
@@ -31,7 +31,7 @@
     target: {
         darwin: {
             enabled: false,
-        }
+        },
     },
 
     shared_libs: [
@@ -46,6 +46,11 @@
         "-Werror",
     ],
     min_sdk_version: "29",
+    // static link, so it won't straddle a module boundary at runtime.
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+    ],
 }
 
 hidl_package_root {
diff --git a/libs/cputimeinstate/Android.bp b/libs/cputimeinstate/Android.bp
index 73f9d4d..4f63194 100644
--- a/libs/cputimeinstate/Android.bp
+++ b/libs/cputimeinstate/Android.bp
@@ -45,4 +45,5 @@
         "-Wextra",
     ],
     require_root: true,
+    test_suites: ["general-tests"],
 }
diff --git a/libs/cputimeinstate/TEST_MAPPING b/libs/cputimeinstate/TEST_MAPPING
new file mode 100644
index 0000000..4781520
--- /dev/null
+++ b/libs/cputimeinstate/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "libtimeinstate_test"
+    }
+  ]
+}
diff --git a/libs/cputimeinstate/testtimeinstate.cpp b/libs/cputimeinstate/testtimeinstate.cpp
index 2112b10..1513eca 100644
--- a/libs/cputimeinstate/testtimeinstate.cpp
+++ b/libs/cputimeinstate/testtimeinstate.cpp
@@ -27,6 +27,7 @@
 
 #include <gtest/gtest.h>
 
+#include <android-base/properties.h>
 #include <android-base/unique_fd.h>
 #include <bpf/BpfMap.h>
 #include <cputimeinstate.h>
@@ -40,24 +41,31 @@
 
 using std::vector;
 
-TEST(TimeInStateTest, IsTrackingSupported) {
-    isTrackingUidTimesSupported();
-    SUCCEED();
-}
+class TimeInStateTest : public testing::Test {
+  protected:
+    TimeInStateTest() {};
 
-TEST(TimeInStateTest, TotalTimeInState) {
+    void SetUp() {
+        if (!isTrackingUidTimesSupported() ||
+            !android::base::GetBoolProperty("sys.init.perf_lsm_hooks", false)) {
+            GTEST_SKIP();
+        }
+    }
+};
+
+TEST_F(TimeInStateTest, TotalTimeInState) {
     auto times = getTotalCpuFreqTimes();
     ASSERT_TRUE(times.has_value());
     EXPECT_FALSE(times->empty());
 }
 
-TEST(TimeInStateTest, SingleUidTimeInState) {
+TEST_F(TimeInStateTest, SingleUidTimeInState) {
     auto times = getUidCpuFreqTimes(0);
     ASSERT_TRUE(times.has_value());
     EXPECT_FALSE(times->empty());
 }
 
-TEST(TimeInStateTest, SingleUidConcurrentTimes) {
+TEST_F(TimeInStateTest, SingleUidConcurrentTimes) {
     auto concurrentTimes = getUidConcurrentTimes(0);
     ASSERT_TRUE(concurrentTimes.has_value());
     ASSERT_FALSE(concurrentTimes->active.empty());
@@ -117,7 +125,7 @@
     EXPECT_EQ(activeSum, policySum);
 }
 
-TEST(TimeInStateTest, SingleUidTimesConsistent) {
+TEST_F(TimeInStateTest, SingleUidTimesConsistent) {
     auto times = getUidCpuFreqTimes(0);
     ASSERT_TRUE(times.has_value());
 
@@ -127,7 +135,7 @@
     ASSERT_NO_FATAL_FAILURE(TestUidTimesConsistent(*times, *concurrentTimes));
 }
 
-TEST(TimeInStateTest, AllUidTimeInState) {
+TEST_F(TimeInStateTest, AllUidTimeInState) {
     uint64_t zero = 0;
     auto maps = {getUidsCpuFreqTimes(), getUidsUpdatedCpuFreqTimes(&zero)};
     for (const auto &map : maps) {
@@ -163,7 +171,7 @@
     ASSERT_LE(sumAfter - sumBefore, NSEC_PER_SEC);
 }
 
-TEST(TimeInStateTest, AllUidUpdatedTimeInState) {
+TEST_F(TimeInStateTest, AllUidUpdatedTimeInState) {
     uint64_t lastUpdate = 0;
     auto map1 = getUidsUpdatedCpuFreqTimes(&lastUpdate);
     ASSERT_TRUE(map1.has_value());
@@ -197,7 +205,7 @@
     }
 }
 
-TEST(TimeInStateTest, TotalAndAllUidTimeInStateConsistent) {
+TEST_F(TimeInStateTest, TotalAndAllUidTimeInStateConsistent) {
     auto allUid = getUidsCpuFreqTimes();
     auto total = getTotalCpuFreqTimes();
 
@@ -222,7 +230,7 @@
     }
 }
 
-TEST(TimeInStateTest, SingleAndAllUidTimeInStateConsistent) {
+TEST_F(TimeInStateTest, SingleAndAllUidTimeInStateConsistent) {
     uint64_t zero = 0;
     auto maps = {getUidsCpuFreqTimes(), getUidsUpdatedCpuFreqTimes(&zero)};
     for (const auto &map : maps) {
@@ -246,7 +254,7 @@
     }
 }
 
-TEST(TimeInStateTest, AllUidConcurrentTimes) {
+TEST_F(TimeInStateTest, AllUidConcurrentTimes) {
     uint64_t zero = 0;
     auto maps = {getUidsConcurrentTimes(), getUidsUpdatedConcurrentTimes(&zero)};
     for (const auto &map : maps) {
@@ -264,7 +272,7 @@
     }
 }
 
-TEST(TimeInStateTest, AllUidUpdatedConcurrentTimes) {
+TEST_F(TimeInStateTest, AllUidUpdatedConcurrentTimes) {
     uint64_t lastUpdate = 0;
     auto map1 = getUidsUpdatedConcurrentTimes(&lastUpdate);
     ASSERT_TRUE(map1.has_value());
@@ -299,7 +307,7 @@
     }
 }
 
-TEST(TimeInStateTest, SingleAndAllUidConcurrentTimesConsistent) {
+TEST_F(TimeInStateTest, SingleAndAllUidConcurrentTimesConsistent) {
     uint64_t zero = 0;
     auto maps = {getUidsConcurrentTimes(), getUidsUpdatedConcurrentTimes(&zero)};
     for (const auto &map : maps) {
@@ -328,7 +336,7 @@
     ASSERT_LE(after - before, NSEC_PER_SEC * 2 * get_nprocs_conf());
 }
 
-TEST(TimeInStateTest, TotalTimeInStateMonotonic) {
+TEST_F(TimeInStateTest, TotalTimeInStateMonotonic) {
     auto before = getTotalCpuFreqTimes();
     ASSERT_TRUE(before.has_value());
     sleep(1);
@@ -344,7 +352,7 @@
     }
 }
 
-TEST(TimeInStateTest, AllUidTimeInStateMonotonic) {
+TEST_F(TimeInStateTest, AllUidTimeInStateMonotonic) {
     auto map1 = getUidsCpuFreqTimes();
     ASSERT_TRUE(map1.has_value());
     sleep(1);
@@ -365,7 +373,7 @@
     }
 }
 
-TEST(TimeInStateTest, AllUidConcurrentTimesMonotonic) {
+TEST_F(TimeInStateTest, AllUidConcurrentTimesMonotonic) {
     auto map1 = getUidsConcurrentTimes();
     ASSERT_TRUE(map1.has_value());
     ASSERT_FALSE(map1->empty());
@@ -393,7 +401,7 @@
     }
 }
 
-TEST(TimeInStateTest, AllUidTimeInStateSanityCheck) {
+TEST_F(TimeInStateTest, AllUidTimeInStateSanityCheck) {
     uint64_t zero = 0;
     auto maps = {getUidsCpuFreqTimes(), getUidsUpdatedCpuFreqTimes(&zero)};
     for (const auto &map : maps) {
@@ -414,7 +422,7 @@
     }
 }
 
-TEST(TimeInStateTest, AllUidConcurrentTimesSanityCheck) {
+TEST_F(TimeInStateTest, AllUidConcurrentTimesSanityCheck) {
     uint64_t zero = 0;
     auto maps = {getUidsConcurrentTimes(), getUidsUpdatedConcurrentTimes(&zero)};
     for (const auto &concurrentMap : maps) {
@@ -441,7 +449,7 @@
     }
 }
 
-TEST(TimeInStateTest, AllUidConcurrentTimesFailsOnInvalidBucket) {
+TEST_F(TimeInStateTest, AllUidConcurrentTimesFailsOnInvalidBucket) {
     uint32_t uid = 0;
     {
         // Find an unused UID
@@ -463,7 +471,7 @@
     ASSERT_FALSE(deleteMapEntry(fd, &key));
 }
 
-TEST(TimeInStateTest, AllUidTimesConsistent) {
+TEST_F(TimeInStateTest, AllUidTimesConsistent) {
     auto tisMap = getUidsCpuFreqTimes();
     ASSERT_TRUE(tisMap.has_value());
 
@@ -481,7 +489,7 @@
     }
 }
 
-TEST(TimeInStateTest, RemoveUid) {
+TEST_F(TimeInStateTest, RemoveUid) {
     uint32_t uid = 0;
     {
         // Find an unused UID
@@ -547,7 +555,7 @@
     ASSERT_EQ(allConcurrentTimes->find(uid), allConcurrentTimes->end());
 }
 
-TEST(TimeInStateTest, GetCpuFreqs) {
+TEST_F(TimeInStateTest, GetCpuFreqs) {
     auto freqs = getCpuFreqs();
     ASSERT_TRUE(freqs.has_value());
 
@@ -583,7 +591,7 @@
     return nullptr;
 }
 
-TEST(TimeInStateTest, GetAggregatedTaskCpuFreqTimes) {
+TEST_F(TimeInStateTest, GetAggregatedTaskCpuFreqTimes) {
     uint64_t startTimeNs = timeNanos();
 
     sem_init(&pingsem, 0, 1);
diff --git a/libs/fakeservicemanager/ServiceManager.cpp b/libs/fakeservicemanager/ServiceManager.cpp
index 9f0754b..61e4a98 100644
--- a/libs/fakeservicemanager/ServiceManager.cpp
+++ b/libs/fakeservicemanager/ServiceManager.cpp
@@ -84,4 +84,14 @@
     return std::nullopt;
 }
 
+status_t ServiceManager::registerForNotifications(const String16&,
+                                                  const sp<LocalRegistrationCallback>&) {
+    return INVALID_OPERATION;
+}
+
+status_t ServiceManager::unregisterForNotifications(const String16&,
+                                                const sp<LocalRegistrationCallback>&) {
+    return INVALID_OPERATION;
+}
+
 }  // namespace android
diff --git a/libs/fakeservicemanager/ServiceManager.h b/libs/fakeservicemanager/ServiceManager.h
index b1496ba..6d6e008 100644
--- a/libs/fakeservicemanager/ServiceManager.h
+++ b/libs/fakeservicemanager/ServiceManager.h
@@ -53,6 +53,11 @@
 
     std::optional<IServiceManager::ConnectionInfo> getConnectionInfo(const String16& name) override;
 
+    status_t registerForNotifications(const String16& name,
+                                      const sp<LocalRegistrationCallback>& callback) override;
+
+    status_t unregisterForNotifications(const String16& name,
+                                        const sp<LocalRegistrationCallback>& callback) override;
 private:
     std::map<String16, sp<IBinder>> mNameToService;
 };
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index b7594df..fb9ed22 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -495,27 +495,6 @@
         return result;
     }
 
-    status_t getPreferredBootDisplayMode(const sp<IBinder>& display,
-                                         ui::DisplayModeId* displayModeId) override {
-        Parcel data, reply;
-        status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        if (result != NO_ERROR) {
-            ALOGE("getPreferredBootDisplayMode failed to writeInterfaceToken: %d", result);
-            return result;
-        }
-        result = data.writeStrongBinder(display);
-        if (result != NO_ERROR) {
-            ALOGE("getPreferredBootDisplayMode failed to writeStrongBinder: %d", result);
-            return result;
-        }
-        result = remote()->transact(BnSurfaceComposer::GET_PREFERRED_BOOT_DISPLAY_MODE, data,
-                                    &reply);
-        if (result == NO_ERROR) {
-            reply.writeInt32(*displayModeId);
-        }
-        return result;
-    }
-
     void setAutoLowLatencyMode(const sp<IBinder>& display, bool on) override {
         Parcel data, reply;
         status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
@@ -1659,21 +1638,6 @@
             }
             return clearBootDisplayMode(display);
         }
-        case GET_PREFERRED_BOOT_DISPLAY_MODE: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> display = nullptr;
-            status_t result = data.readStrongBinder(&display);
-            if (result != NO_ERROR) {
-                ALOGE("getPreferredBootDisplayMode failed to readStrongBinder: %d", result);
-                return result;
-            }
-            ui::DisplayModeId displayModeId;
-            result = getPreferredBootDisplayMode(display, &displayModeId);
-            if (result == NO_ERROR) {
-                reply->writeInt32(displayModeId);
-            }
-            return result;
-        }
         case SET_AUTO_LOW_LATENCY_MODE: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IBinder> display = nullptr;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index a7b2bcd..91b2fb1 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -2094,12 +2094,6 @@
     return ComposerService::getComposerService()->clearBootDisplayMode(display);
 }
 
-status_t SurfaceComposerClient::getPreferredBootDisplayMode(const sp<IBinder>& display,
-                                                            ui::DisplayModeId* displayModeId) {
-    return ComposerService::getComposerService()->getPreferredBootDisplayMode(display,
-                                                                              displayModeId);
-}
-
 status_t SurfaceComposerClient::setOverrideFrameRate(uid_t uid, float frameRate) {
     return ComposerService::getComposerService()->setOverrideFrameRate(uid, frameRate);
 }
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 8d356aa..1c7b270 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -52,7 +52,8 @@
 }
 
 bool WindowInfo::overlaps(const WindowInfo* other) const {
-    return frameLeft < other->frameRight && frameRight > other->frameLeft &&
+    const bool nonEmpty = (frameRight - frameLeft > 0) || (frameBottom - frameTop > 0);
+    return nonEmpty && frameLeft < other->frameRight && frameRight > other->frameLeft &&
             frameTop < other->frameBottom && frameBottom > other->frameTop;
 }
 
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index fb4fb7e..4b5cee2 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -235,12 +235,6 @@
     virtual status_t clearBootDisplayMode(const sp<IBinder>& display) = 0;
 
     /**
-     * Gets the display mode in which the device boots if there is no user-preferred display mode.
-     */
-    virtual status_t getPreferredBootDisplayMode(const sp<IBinder>& display,
-                                                 ui::DisplayModeId*) = 0;
-
-    /**
      * Gets whether boot time display mode operations are supported on the device.
      *
      * outSupport
@@ -684,7 +678,6 @@
         GET_BOOT_DISPLAY_MODE_SUPPORT,
         SET_BOOT_DISPLAY_MODE,
         CLEAR_BOOT_DISPLAY_MODE,
-        GET_PREFERRED_BOOT_DISPLAY_MODE,
         SET_OVERRIDE_FRAME_RATE,
         // Always append new enum to the end.
     };
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 366577d..61eeab3 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -174,8 +174,7 @@
     static status_t setBootDisplayMode(const sp<IBinder>& display, ui::DisplayModeId);
     // Clears the user-preferred display mode
     static status_t clearBootDisplayMode(const sp<IBinder>& display);
-    // Gets the display mode in which the device boots if there is no user-preferred display mode
-    static status_t getPreferredBootDisplayMode(const sp<IBinder>& display, ui::DisplayModeId*);
+
     // Sets the frame rate of a particular app (uid). This is currently called
     // by GameManager.
     static status_t setOverrideFrameRate(uid_t uid, float frameRate);
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 6f1263b..06a0aca 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -546,7 +546,10 @@
 }
 
 TEST_F(InputSurfacesTest, input_respects_scaled_surface_insets_overflow) {
+    std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100);
     std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100);
+    bgSurface->showAt(100, 100);
+
     // In case we pass the very big inset without any checking.
     fgSurface->mInputInfo.surfaceInset = INT32_MAX;
     fgSurface->showAt(100, 100);
@@ -554,8 +557,8 @@
     fgSurface->doTransaction([&](auto &t, auto &sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); });
 
     // expect no crash for overflow, and inset size to be clamped to surface size
-    injectTap(202, 202);
-    fgSurface->expectTap(1, 1);
+    injectTap(112, 124);
+    bgSurface->expectTap(12, 24);
 }
 
 // Ensure we ignore transparent region when getting screen bounds when positioning input frame.
@@ -987,6 +990,107 @@
     EXPECT_EQ(surface->consumeEvent(100), nullptr);
 }
 
+TEST_F(InputSurfacesTest, layer_with_empty_crop_cannot_be_focused) {
+    std::unique_ptr<InputSurface> bufferSurface =
+            InputSurface::makeBufferInputSurface(mComposerClient, 100, 100);
+
+    bufferSurface->showAt(50, 50, Rect::EMPTY_RECT);
+
+    bufferSurface->requestFocus();
+    EXPECT_EQ(bufferSurface->consumeEvent(100), nullptr);
+
+    bufferSurface->showAt(50, 50, Rect::INVALID_RECT);
+
+    bufferSurface->requestFocus();
+    EXPECT_EQ(bufferSurface->consumeEvent(100), nullptr);
+}
+
+TEST_F(InputSurfacesTest, layer_with_valid_crop_can_be_focused) {
+    std::unique_ptr<InputSurface> bufferSurface =
+            InputSurface::makeBufferInputSurface(mComposerClient, 100, 100);
+
+    bufferSurface->showAt(50, 50, Rect{0, 0, 100, 100});
+
+    bufferSurface->requestFocus();
+    bufferSurface->assertFocusChange(true);
+}
+
+/**
+ * If a cropped layer's touchable region is replaced with a null crop, it should receive input in
+ * its own crop.
+ */
+TEST_F(InputSurfacesTest, cropped_container_replaces_touchable_region_with_null_crop) {
+    std::unique_ptr<InputSurface> parentContainer =
+            InputSurface::makeContainerInputSurface(mComposerClient, 0, 0);
+    std::unique_ptr<InputSurface> containerSurface =
+            InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
+    containerSurface->doTransaction(
+            [&](auto &t, auto &sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
+    containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
+    containerSurface->mInputInfo.touchableRegionCropHandle = nullptr;
+    parentContainer->showAt(10, 10, Rect(0, 0, 20, 20));
+    containerSurface->showAt(10, 10, Rect(0, 0, 5, 5));
+
+    // Receives events inside its own crop
+    injectTap(21, 21);
+    containerSurface->expectTap(1, 1); // Event is in layer space
+
+    // Does not receive events outside its crop
+    injectTap(26, 26);
+    EXPECT_EQ(containerSurface->consumeEvent(100), nullptr);
+}
+
+/**
+ * If an un-cropped layer's touchable region is replaced with a null crop, it should receive input
+ * in its parent's touchable region. The input events should be in the layer's coordinate space.
+ */
+TEST_F(InputSurfacesTest, uncropped_container_replaces_touchable_region_with_null_crop) {
+    std::unique_ptr<InputSurface> parentContainer =
+            InputSurface::makeContainerInputSurface(mComposerClient, 0, 0);
+    std::unique_ptr<InputSurface> containerSurface =
+            InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
+    containerSurface->doTransaction(
+            [&](auto &t, auto &sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
+    containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
+    containerSurface->mInputInfo.touchableRegionCropHandle = nullptr;
+    parentContainer->showAt(10, 10, Rect(0, 0, 20, 20));
+    containerSurface->showAt(10, 10, Rect::INVALID_RECT);
+
+    // Receives events inside parent bounds
+    injectTap(21, 21);
+    containerSurface->expectTap(1, 1); // Event is in layer space
+
+    // Does not receive events outside parent bounds
+    injectTap(31, 31);
+    EXPECT_EQ(containerSurface->consumeEvent(100), nullptr);
+}
+
+/**
+ * If a layer's touchable region is replaced with a layer crop, it should receive input in the crop
+ * layer's bounds. The input events should be in the layer's coordinate space.
+ */
+TEST_F(InputSurfacesTest, replace_touchable_region_with_crop) {
+    std::unique_ptr<InputSurface> cropLayer =
+            InputSurface::makeContainerInputSurface(mComposerClient, 0, 0);
+    cropLayer->showAt(50, 50, Rect(0, 0, 20, 20));
+
+    std::unique_ptr<InputSurface> containerSurface =
+            InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
+    containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
+    containerSurface->mInputInfo.touchableRegionCropHandle =
+            cropLayer->mSurfaceControl->getHandle();
+    containerSurface->showAt(10, 10, Rect::INVALID_RECT);
+
+    // Receives events inside crop layer bounds
+    injectTap(51, 51);
+    containerSurface->expectTap(41, 41); // Event is in layer space
+
+    // Does not receive events outside crop layer bounds
+    injectTap(21, 21);
+    injectTap(71, 71);
+    EXPECT_EQ(containerSurface->consumeEvent(100), nullptr);
+}
+
 class MultiDisplayTests : public InputSurfacesTest {
 public:
     MultiDisplayTests() : InputSurfacesTest() { ProcessState::self()->startThreadPool(); }
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 0ebd11c..120ed48 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -762,10 +762,6 @@
         return NO_ERROR;
     }
     status_t clearBootDisplayMode(const sp<IBinder>& /*display*/) override { return NO_ERROR; }
-    status_t getPreferredBootDisplayMode(const sp<IBinder>& /*display*/,
-                                         ui::DisplayModeId* /*id*/) override {
-        return NO_ERROR;
-    }
     void setAutoLowLatencyMode(const sp<IBinder>& /*display*/, bool /*on*/) override {}
     void setGameContentType(const sp<IBinder>& /*display*/, bool /*on*/) override {}
 
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index e73c3b8..930d819 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -35,6 +35,7 @@
 
 cc_library {
     name: "libinput",
+    cpp_std: "c++20",
     host_supported: true,
     cflags: [
         "-Wall",
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index a065ce2..6195052 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -1318,10 +1318,6 @@
     return result;
 }
 
-bool InputConsumer::hasDeferredEvent() const {
-    return mMsgDeferred;
-}
-
 bool InputConsumer::hasPendingBatch() const {
     return !mBatches.empty();
 }
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index b6a9476..1c8658b 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -115,12 +115,9 @@
     static_assert(sizeof(InputMessage::Header) == 8);
 }
 
-/**
- * We cannot use the Body::size() method here because it is not static for
- * the Motion type, where "pointerCount" variable affects the size and can change at runtime.
- */
 void TestBodySize() {
     static_assert(sizeof(InputMessage::Body::Key) == 96);
+    static_assert(sizeof(InputMessage::Body::Motion::Pointer) == 136);
     static_assert(sizeof(InputMessage::Body::Motion) ==
                   offsetof(InputMessage::Body::Motion, pointers) +
                           sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS);
@@ -132,6 +129,38 @@
     // Timeline
     static_assert(GraphicsTimeline::SIZE == 2);
     static_assert(sizeof(InputMessage::Body::Timeline) == 24);
+
+    /**
+     * We cannot use the Body::size() method here because it is not static for
+     * the Motion type, where "pointerCount" variable affects the size and can change at runtime.
+     */
+    static_assert(sizeof(InputMessage::Body) ==
+                  offsetof(InputMessage::Body::Motion, pointers) +
+                          sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS);
+    static_assert(sizeof(InputMessage::Body) == 160 + 136 * 16);
+    static_assert(sizeof(InputMessage::Body) == 2336);
+}
+
+/**
+ * In general, we are sending a variable-length message across the socket, because the number of
+ * pointers varies. When we receive the message, we still need to allocate enough memory for the
+ * entire InputMessage struct. This size is, therefore, the worst case scenario. However, it is
+ * still helpful to compute to get an idea of the sizes that are involved.
+ */
+void TestWorstCaseInputMessageSize() {
+    static_assert(sizeof(InputMessage) == /*header*/ 8 + /*body*/ 2336);
+    static_assert(sizeof(InputMessage) == 2344);
+}
+
+/**
+ * Assuming a single pointer, how big is the message that we are sending across the socket?
+ */
+void CalculateSinglePointerInputMessageSize() {
+    constexpr size_t pointerCount = 1;
+    constexpr size_t bodySize = offsetof(InputMessage::Body::Motion, pointers) +
+            sizeof(InputMessage::Body::Motion::Pointer) * pointerCount;
+    static_assert(bodySize == 160 + 136);
+    static_assert(bodySize == 296); // For the total message size, add the small header
 }
 
 // --- VerifiedInputEvent ---
diff --git a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
index 6882ea3..0128859 100644
--- a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
@@ -593,6 +593,10 @@
 }
 
 void EGLConsumer::onFreeBufferLocked(int slotIndex) {
+    if (mEglSlots[slotIndex].mEglImage != nullptr &&
+        mEglSlots[slotIndex].mEglImage == mCurrentTextureImage) {
+        mCurrentTextureImage.clear();
+    }
     mEglSlots[slotIndex].mEglImage.clear();
 }
 
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index cb3361b..2578ee8 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -509,10 +509,6 @@
             ALOGE_IF(log, "AHARDWAREBUFFER_USAGE_SENSOR_DIRECT_DATA requires AHARDWAREBUFFER_FORMAT_BLOB");
             return false;
         }
-        if (desc->usage & AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER) {
-            ALOGE_IF(log, "AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER requires AHARDWAREBUFFER_FORMAT_BLOB");
-            return false;
-        }
     }
 
     if ((desc->usage & (AHARDWAREBUFFER_USAGE_CPU_READ_MASK | AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK)) &&
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 063ce67..763b82d 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -1104,6 +1104,15 @@
                 paint.setDither(true);
             }
             paint.setAlphaf(layer.alpha);
+
+            if (imageTextureRef->colorType() == kAlpha_8_SkColorType) {
+                LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with A8");
+                float matrix[] = { 0, 0, 0, 0, 0,
+                                   0, 0, 0, 0, 0,
+                                   0, 0, 0, 0, 0,
+                                   0, 0, 0, -1, 1 };
+                paint.setColorFilter(SkColorFilters::Matrix(matrix));
+            }
         } else {
             ATRACE_NAME("DrawColor");
             const auto color = layer.source.solidColor;
@@ -1125,7 +1134,11 @@
             paint.setBlendMode(SkBlendMode::kSrc);
         }
 
-        paint.setColorFilter(displayColorTransform);
+        // A color filter will have been set for an A8 buffer. Do not replace
+        // it with the displayColorTransform, which shouldn't affect A8.
+        if (!paint.getColorFilter()) {
+            paint.setColorFilter(displayColorTransform);
+        }
 
         if (!roundRectClip.isEmpty()) {
             canvas->clipRRect(roundRectClip, true);
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 2a25b0b..e197150 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -49,6 +49,50 @@
 namespace android {
 namespace renderengine {
 
+namespace {
+
+double EOTF_PQ(double channel) {
+    float m1 = (2610.0 / 4096.0) / 4.0;
+    float m2 = (2523.0 / 4096.0) * 128.0;
+    float c1 = (3424.0 / 4096.0);
+    float c2 = (2413.0 / 4096.0) * 32.0;
+    float c3 = (2392.0 / 4096.0) * 32.0;
+
+    float tmp = std::pow(std::clamp(channel, 0.0, 1.0), 1.0 / m2);
+    tmp = std::fmax(tmp - c1, 0.0) / (c2 - c3 * tmp);
+    return std::pow(tmp, 1.0 / m1);
+}
+
+vec3 EOTF_PQ(vec3 color) {
+    return vec3(EOTF_PQ(color.r), EOTF_PQ(color.g), EOTF_PQ(color.b));
+}
+
+double EOTF_HLG(double channel) {
+    const float a = 0.17883277;
+    const float b = 0.28466892;
+    const float c = 0.55991073;
+    return channel <= 0.5 ? channel * channel / 3.0 : (exp((channel - c) / a) + b) / 12.0;
+}
+
+vec3 EOTF_HLG(vec3 color) {
+    return vec3(EOTF_HLG(color.r), EOTF_HLG(color.g), EOTF_HLG(color.b));
+}
+
+double OETF_sRGB(double channel) {
+    return channel <= 0.0031308 ? channel * 12.92 : (pow(channel, 1.0 / 2.4) * 1.055) - 0.055;
+}
+
+int sign(float in) {
+    return in >= 0.0 ? 1 : -1;
+}
+
+vec3 OETF_sRGB(vec3 linear) {
+    return vec3(sign(linear.r) * OETF_sRGB(linear.r), sign(linear.g) * OETF_sRGB(linear.g),
+                sign(linear.b) * OETF_sRGB(linear.b));
+}
+
+} // namespace
+
 class RenderEngineFactory {
 public:
     virtual ~RenderEngineFactory() = default;
@@ -218,14 +262,35 @@
         uint8_t* pixels;
         buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                                   reinterpret_cast<void**>(&pixels));
-        pixels[0] = color.r;
-        pixels[1] = color.g;
-        pixels[2] = color.b;
-        pixels[3] = color.a;
+        for (uint32_t j = 0; j < height; j++) {
+            uint8_t* dst = pixels + (buffer->getBuffer()->getStride() * j * 4);
+            for (uint32_t i = 0; i < width; i++) {
+                dst[0] = color.r;
+                dst[1] = color.g;
+                dst[2] = color.b;
+                dst[3] = color.a;
+                dst += 4;
+            }
+        }
         buffer->getBuffer()->unlock();
         return buffer;
     }
 
+    std::shared_ptr<renderengine::ExternalTexture> allocateR8Buffer(int width, int height) {
+        auto buffer = new GraphicBuffer(width, height, android::PIXEL_FORMAT_R_8, 1,
+                                        GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                GRALLOC_USAGE_HW_TEXTURE,
+                                        "r8");
+        if (buffer->initCheck() != 0) {
+            // Devices are not required to support R8.
+            return nullptr;
+        }
+        return std::make_shared<
+                renderengine::impl::ExternalTexture>(std::move(buffer), *mRE,
+                                                     renderengine::impl::ExternalTexture::Usage::
+                                                             READABLE);
+    }
+
     RenderEngineTest() {
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
@@ -577,6 +642,12 @@
                                  const renderengine::ShadowSettings& shadow,
                                  const ubyte4& backgroundColor);
 
+    // Tonemaps grey values from sourceDataspace -> Display P3 and checks that GPU and CPU
+    // implementations are identical Also implicitly checks that the injected tonemap shader
+    // compiles
+    void tonemap(ui::Dataspace sourceDataspace, std::function<vec3(vec3)> eotf,
+                 std::function<vec3(vec3, float)> scaleOotf);
+
     void initializeRenderEngine();
 
     std::unique_ptr<renderengine::RenderEngine> mRE;
@@ -1397,6 +1468,119 @@
     invokeDraw(settings, layers);
 }
 
+void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function<vec3(vec3)> eotf,
+                               std::function<vec3(vec3, float)> scaleOotf) {
+    constexpr int32_t kGreyLevels = 256;
+
+    const auto rect = Rect(0, 0, kGreyLevels, 1);
+
+    constexpr float kMaxLuminance = 750.f;
+    constexpr float kCurrentLuminanceNits = 500.f;
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .maxLuminance = kMaxLuminance,
+            .currentLuminanceNits = kCurrentLuminanceNits,
+            .outputDataspace = ui::Dataspace::DISPLAY_P3,
+    };
+
+    auto buf = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(new GraphicBuffer(kGreyLevels, 1, HAL_PIXEL_FORMAT_RGBA_8888,
+                                                       1,
+                                                       GRALLOC_USAGE_SW_READ_OFTEN |
+                                                               GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                               GRALLOC_USAGE_HW_RENDER |
+                                                               GRALLOC_USAGE_HW_TEXTURE,
+                                                       "input"),
+                                     *mRE,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+    ASSERT_EQ(0, buf->getBuffer()->initCheck());
+    {
+        uint8_t* pixels;
+        buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                               reinterpret_cast<void**>(&pixels));
+
+        uint8_t color = 0;
+        for (int32_t j = 0; j < buf->getBuffer()->getHeight(); j++) {
+            uint8_t* dest = pixels + (buf->getBuffer()->getStride() * j * 4);
+            for (int32_t i = 0; i < buf->getBuffer()->getWidth(); i++) {
+                dest[0] = color;
+                dest[1] = color;
+                dest[2] = color;
+                dest[3] = 255;
+                color++;
+                dest += 4;
+            }
+        }
+        buf->getBuffer()->unlock();
+    }
+
+    mBuffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(new GraphicBuffer(kGreyLevels, 1, HAL_PIXEL_FORMAT_RGBA_8888,
+                                                       1,
+                                                       GRALLOC_USAGE_SW_READ_OFTEN |
+                                                               GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                               GRALLOC_USAGE_HW_RENDER |
+                                                               GRALLOC_USAGE_HW_TEXTURE,
+                                                       "output"),
+                                     *mRE,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+    ASSERT_EQ(0, mBuffer->getBuffer()->initCheck());
+
+    const renderengine::LayerSettings layer{.geometry.boundaries = rect.toFloatRect(),
+                                            .source =
+                                                    renderengine::PixelSource{
+                                                            .buffer =
+                                                                    renderengine::Buffer{
+                                                                            .buffer =
+                                                                                    std::move(buf),
+                                                                            .usePremultipliedAlpha =
+                                                                                    true,
+                                                                    },
+                                                    },
+                                            .alpha = 1.0f,
+                                            .sourceDataspace = sourceDataspace};
+
+    std::vector<renderengine::LayerSettings> layers{layer};
+    invokeDraw(display, layers);
+
+    ColorSpace displayP3 = ColorSpace::DisplayP3();
+    ColorSpace bt2020 = ColorSpace::BT2020();
+
+    tonemap::Metadata metadata{.displayMaxLuminance = 750.0f};
+
+    auto generator = [=](Point location) {
+        const double normColor = static_cast<double>(location.x) / (kGreyLevels - 1);
+        const vec3 rgb = vec3(normColor, normColor, normColor);
+
+        const vec3 linearRGB = eotf(rgb);
+
+        const vec3 xyz = bt2020.getRGBtoXYZ() * linearRGB;
+
+        const vec3 scaledXYZ = scaleOotf(xyz, kCurrentLuminanceNits);
+        const double gain =
+                tonemap::getToneMapper()
+                        ->lookupTonemapGain(static_cast<aidl::android::hardware::graphics::common::
+                                                                Dataspace>(sourceDataspace),
+                                            static_cast<aidl::android::hardware::graphics::common::
+                                                                Dataspace>(
+                                                    ui::Dataspace::DISPLAY_P3),
+                                            scaleOotf(linearRGB, kCurrentLuminanceNits), scaledXYZ,
+                                            metadata);
+        const vec3 normalizedXYZ = scaledXYZ * gain / metadata.displayMaxLuminance;
+
+        const vec3 targetRGB = OETF_sRGB(displayP3.getXYZtoRGB() * normalizedXYZ) * 255;
+        return ubyte4(static_cast<uint8_t>(targetRGB.r), static_cast<uint8_t>(targetRGB.g),
+                      static_cast<uint8_t>(targetRGB.b), 255);
+    };
+
+    expectBufferColor(Rect(kGreyLevels, 1), generator, 2);
+}
+
 INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
                          testing::Values(std::make_shared<GLESRenderEngineFactory>(),
                                          std::make_shared<GLESCMRenderEngineFactory>(),
@@ -2391,155 +2575,107 @@
     }
 }
 
-double EOTF_PQ(double channel) {
-    float m1 = (2610.0 / 4096.0) / 4.0;
-    float m2 = (2523.0 / 4096.0) * 128.0;
-    float c1 = (3424.0 / 4096.0);
-    float c2 = (2413.0 / 4096.0) * 32.0;
-    float c3 = (2392.0 / 4096.0) * 32.0;
-
-    float tmp = std::pow(std::clamp(channel, 0.0, 1.0), 1.0 / m2);
-    tmp = std::fmax(tmp - c1, 0.0) / (c2 - c3 * tmp);
-    return std::pow(tmp, 1.0 / m1);
-}
-
-vec3 EOTF_PQ(vec3 color) {
-    return vec3(EOTF_PQ(color.r), EOTF_PQ(color.g), EOTF_PQ(color.b));
-}
-
-double OETF_sRGB(double channel) {
-    return channel <= 0.0031308 ? channel * 12.92 : (pow(channel, 1.0 / 2.4) * 1.055) - 0.055;
-}
-
-int sign(float in) {
-    return in >= 0.0 ? 1 : -1;
-}
-
-vec3 OETF_sRGB(vec3 linear) {
-    return vec3(sign(linear.r) * OETF_sRGB(linear.r), sign(linear.g) * OETF_sRGB(linear.g),
-                sign(linear.b) * OETF_sRGB(linear.b));
-}
-
 TEST_P(RenderEngineTest, test_tonemapPQMatches) {
     if (!GetParam()->useColorManagement()) {
-        return;
+        GTEST_SKIP();
     }
 
     if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        GTEST_SKIP();
+    }
+
+    initializeRenderEngine();
+
+    tonemap(
+            static_cast<ui::Dataspace>(HAL_DATASPACE_STANDARD_BT2020 |
+                                       HAL_DATASPACE_TRANSFER_ST2084 | HAL_DATASPACE_RANGE_FULL),
+            [](vec3 color) { return EOTF_PQ(color); },
+            [](vec3 color, float) {
+                static constexpr float kMaxPQLuminance = 10000.f;
+                return color * kMaxPQLuminance;
+            });
+}
+
+TEST_P(RenderEngineTest, test_tonemapHLGMatches) {
+    if (!GetParam()->useColorManagement()) {
+        GTEST_SKIP();
+    }
+
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        GTEST_SKIP();
+    }
+
+    initializeRenderEngine();
+
+    tonemap(
+            static_cast<ui::Dataspace>(HAL_DATASPACE_STANDARD_BT2020 | HAL_DATASPACE_TRANSFER_HLG |
+                                       HAL_DATASPACE_RANGE_FULL),
+            [](vec3 color) { return EOTF_HLG(color); },
+            [](vec3 color, float currentLuminaceNits) {
+                static constexpr float kMaxHLGLuminance = 1000.f;
+                static const float kHLGGamma = 1.2 + 0.42 * std::log10(currentLuminaceNits / 1000);
+                return color * kMaxHLGLuminance * std::pow(color.y, kHLGGamma - 1);
+            });
+}
+
+TEST_P(RenderEngineTest, r8_behaves_as_mask) {
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
         return;
     }
 
     initializeRenderEngine();
 
-    constexpr int32_t kGreyLevels = 256;
+    const auto r8Buffer = allocateR8Buffer(2, 1);
+    if (!r8Buffer) {
+        return;
+    }
+    {
+        uint8_t* pixels;
+        r8Buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                    reinterpret_cast<void**>(&pixels));
+        // This will be drawn on top of a green buffer. We'll verify that 255
+        // results in keeping the original green and 0 results in black.
+        pixels[0] = 0;
+        pixels[1] = 255;
+        r8Buffer->getBuffer()->unlock();
+    }
 
-    const auto rect = Rect(0, 0, kGreyLevels, 1);
+    const auto rect = Rect(0, 0, 2, 1);
     const renderengine::DisplaySettings display{
             .physicalDisplay = rect,
             .clip = rect,
-            .maxLuminance = 750.0f,
-            .outputDataspace = ui::Dataspace::DISPLAY_P3,
+            .outputDataspace = ui::Dataspace::SRGB,
     };
 
-    auto buf = std::make_shared<
-            renderengine::impl::
-                    ExternalTexture>(new GraphicBuffer(kGreyLevels, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                       1,
-                                                       GRALLOC_USAGE_SW_READ_OFTEN |
-                                                               GRALLOC_USAGE_SW_WRITE_OFTEN |
-                                                               GRALLOC_USAGE_HW_RENDER |
-                                                               GRALLOC_USAGE_HW_TEXTURE,
-                                                       "input"),
-                                     *mRE,
-                                     renderengine::impl::ExternalTexture::Usage::READABLE |
-                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
-    ASSERT_EQ(0, buf->getBuffer()->initCheck());
-
-    {
-        uint8_t* pixels;
-        buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
-                               reinterpret_cast<void**>(&pixels));
-
-        uint8_t color = 0;
-        for (int32_t j = 0; j < buf->getBuffer()->getHeight(); j++) {
-            uint8_t* dest = pixels + (buf->getBuffer()->getStride() * j * 4);
-            for (int32_t i = 0; i < buf->getBuffer()->getWidth(); i++) {
-                dest[0] = color;
-                dest[1] = color;
-                dest[2] = color;
-                dest[3] = 255;
-                color++;
-                dest += 4;
-            }
-        }
-        buf->getBuffer()->unlock();
-    }
-
-    mBuffer = std::make_shared<
-            renderengine::impl::
-                    ExternalTexture>(new GraphicBuffer(kGreyLevels, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                       1,
-                                                       GRALLOC_USAGE_SW_READ_OFTEN |
-                                                               GRALLOC_USAGE_SW_WRITE_OFTEN |
-                                                               GRALLOC_USAGE_HW_RENDER |
-                                                               GRALLOC_USAGE_HW_TEXTURE,
-                                                       "output"),
-                                     *mRE,
-                                     renderengine::impl::ExternalTexture::Usage::READABLE |
-                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
-    ASSERT_EQ(0, mBuffer->getBuffer()->initCheck());
-
-    const renderengine::LayerSettings layer{
+    const auto greenBuffer = allocateAndFillSourceBuffer(2, 1, ubyte4(0, 255, 0, 255));
+    const renderengine::LayerSettings greenLayer{
             .geometry.boundaries = rect.toFloatRect(),
             .source =
                     renderengine::PixelSource{
                             .buffer =
                                     renderengine::Buffer{
-                                            .buffer = std::move(buf),
-                                            .usePremultipliedAlpha = true,
+                                            .buffer = greenBuffer,
                                     },
                     },
             .alpha = 1.0f,
-            .sourceDataspace = static_cast<ui::Dataspace>(HAL_DATASPACE_STANDARD_BT2020 |
-                                                          HAL_DATASPACE_TRANSFER_ST2084 |
-                                                          HAL_DATASPACE_RANGE_FULL),
+    };
+    const renderengine::LayerSettings r8Layer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = r8Buffer,
+                                    },
+                    },
+            .alpha = 1.0f,
     };
 
-    std::vector<renderengine::LayerSettings> layers{layer};
+    std::vector<renderengine::LayerSettings> layers{greenLayer, r8Layer};
     invokeDraw(display, layers);
 
-    ColorSpace displayP3 = ColorSpace::DisplayP3();
-    ColorSpace bt2020 = ColorSpace::BT2020();
-
-    tonemap::Metadata metadata{.displayMaxLuminance = 750.0f};
-
-    auto generator = [=](Point location) {
-        const double normColor = static_cast<double>(location.x) / (kGreyLevels - 1);
-        const vec3 rgb = vec3(normColor, normColor, normColor);
-
-        const vec3 linearRGB = EOTF_PQ(rgb);
-
-        static constexpr float kMaxPQLuminance = 10000.f;
-        const vec3 xyz = bt2020.getRGBtoXYZ() * linearRGB * kMaxPQLuminance;
-        const double gain =
-                tonemap::getToneMapper()
-                        ->lookupTonemapGain(static_cast<aidl::android::hardware::graphics::common::
-                                                                Dataspace>(
-                                                    HAL_DATASPACE_STANDARD_BT2020 |
-                                                    HAL_DATASPACE_TRANSFER_ST2084 |
-                                                    HAL_DATASPACE_RANGE_FULL),
-                                            static_cast<aidl::android::hardware::graphics::common::
-                                                                Dataspace>(
-                                                    ui::Dataspace::DISPLAY_P3),
-                                            linearRGB * 10000.0, xyz, metadata);
-        const vec3 scaledXYZ = xyz * gain / metadata.displayMaxLuminance;
-
-        const vec3 targetRGB = OETF_sRGB(displayP3.getXYZtoRGB() * scaledXYZ) * 255;
-        return ubyte4(static_cast<uint8_t>(targetRGB.r), static_cast<uint8_t>(targetRGB.g),
-                      static_cast<uint8_t>(targetRGB.b), 255);
-    };
-
-    expectBufferColor(Rect(kGreyLevels, 1), generator, 2);
+    expectBufferColor(Rect(0, 0, 1, 1), 0,   0, 0, 255);
+    expectBufferColor(Rect(1, 0, 2, 1), 0, 255, 0, 255);
 }
 } // namespace renderengine
 } // namespace android
diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp
index 4d88d5d..03da3ec 100644
--- a/libs/shaders/shaders.cpp
+++ b/libs/shaders/shaders.cpp
@@ -18,6 +18,7 @@
 
 #include <tonemap/tonemap.h>
 
+#include <cmath>
 #include <optional>
 
 #include <math/mat4.h>
@@ -26,12 +27,13 @@
 
 namespace android::shaders {
 
-static aidl::android::hardware::graphics::common::Dataspace toAidlDataspace(
-        ui::Dataspace dataspace) {
+namespace {
+
+aidl::android::hardware::graphics::common::Dataspace toAidlDataspace(ui::Dataspace dataspace) {
     return static_cast<aidl::android::hardware::graphics::common::Dataspace>(dataspace);
 }
 
-static void generateEOTF(ui::Dataspace dataspace, std::string& shader) {
+void generateEOTF(ui::Dataspace dataspace, std::string& shader) {
     switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
         case HAL_DATASPACE_TRANSFER_ST2084:
             shader.append(R"(
@@ -156,7 +158,7 @@
     }
 }
 
-static void generateXYZTransforms(std::string& shader) {
+void generateXYZTransforms(std::string& shader) {
     shader.append(R"(
         uniform float4x4 in_rgbToXyz;
         uniform float4x4 in_xyzToRgb;
@@ -171,8 +173,8 @@
 }
 
 // Conversion from relative light to absolute light (maps from [0, 1] to [0, maxNits])
-static void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace,
-                                           ui::Dataspace outputDataspace, std::string& shader) {
+void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace,
+                                    std::string& shader) {
     switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
         case HAL_DATASPACE_TRANSFER_ST2084:
             shader.append(R"(
@@ -183,8 +185,9 @@
             break;
         case HAL_DATASPACE_TRANSFER_HLG:
             shader.append(R"(
+                    uniform float in_hlgGamma;
                     float3 ScaleLuminance(float3 xyz) {
-                        return xyz * 1000.0 * pow(xyz.y, 0.2);
+                        return xyz * 1000.0 * pow(xyz.y, in_hlgGamma - 1);
                     }
                 )");
             break;
@@ -225,8 +228,10 @@
             break;
         case HAL_DATASPACE_TRANSFER_HLG:
             shader.append(R"(
+                    uniform float in_hlgGamma;
                     float3 NormalizeLuminance(float3 xyz) {
-                        return xyz / 1000.0 * pow(xyz.y / 1000.0, -0.2 / 1.2);
+                        return xyz / 1000.0 *
+                                pow(xyz.y / 1000.0, (1 - in_hlgGamma) / (in_hlgGamma));
                     }
                 )");
             break;
@@ -240,8 +245,8 @@
     }
 }
 
-static void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace,
-                         std::string& shader) {
+void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace,
+                  std::string& shader) {
     shader.append(tonemap::getToneMapper()
                           ->generateTonemapGainShaderSkSL(toAidlDataspace(inputDataspace),
                                                           toAidlDataspace(outputDataspace))
@@ -262,7 +267,7 @@
         )");
 }
 
-static void generateOETF(ui::Dataspace dataspace, std::string& shader) {
+void generateOETF(ui::Dataspace dataspace, std::string& shader) {
     switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
         case HAL_DATASPACE_TRANSFER_ST2084:
             shader.append(R"(
@@ -384,7 +389,7 @@
     }
 }
 
-static void generateEffectiveOOTF(bool undoPremultipliedAlpha, std::string& shader) {
+void generateEffectiveOOTF(bool undoPremultipliedAlpha, std::string& shader) {
     shader.append(R"(
         uniform shader child;
         half4 main(float2 xy) {
@@ -412,7 +417,7 @@
 }
 
 // please keep in sync with toSkColorSpace function in renderengine/skia/ColorSpaces.cpp
-static ColorSpace toColorSpace(ui::Dataspace dataspace) {
+ColorSpace toColorSpace(ui::Dataspace dataspace) {
     switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
         case HAL_DATASPACE_STANDARD_BT709:
             return ColorSpace::sRGB();
@@ -438,6 +443,21 @@
     }
 }
 
+template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
+std::vector<uint8_t> buildUniformValue(T value) {
+    std::vector<uint8_t> result;
+    result.resize(sizeof(value));
+    std::memcpy(result.data(), &value, sizeof(value));
+    return result;
+}
+
+// Refer to BT2100-2
+float computeHlgGamma(float currentDisplayBrightnessNits) {
+    return 1.2 + 0.42 * std::log10(currentDisplayBrightnessNits / 1000);
+}
+
+} // namespace
+
 std::string buildLinearEffectSkSL(const LinearEffect& linearEffect) {
     std::string shaderString;
     generateEOTF(linearEffect.fakeInputDataspace == ui::Dataspace::UNKNOWN
@@ -451,14 +471,6 @@
     return shaderString;
 }
 
-template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
-std::vector<uint8_t> buildUniformValue(T value) {
-    std::vector<uint8_t> result;
-    result.resize(sizeof(value));
-    std::memcpy(result.data(), &value, sizeof(value));
-    return result;
-}
-
 // Generates a list of uniforms to set on the LinearEffect shader above.
 std::vector<tonemap::ShaderUniform> buildLinearEffectUniforms(const LinearEffect& linearEffect,
                                                               const mat4& colorTransform,
@@ -480,8 +492,13 @@
                                     colorTransform * mat4(outputColorSpace.getXYZtoRGB()))});
     }
 
+    if ((linearEffect.inputDataspace & HAL_DATASPACE_TRANSFER_MASK) == HAL_DATASPACE_TRANSFER_HLG) {
+        uniforms.push_back(
+                {.name = "in_hlgGamma",
+                 .value = buildUniformValue<float>(computeHlgGamma(currentDisplayLuminanceNits))});
+    }
+
     tonemap::Metadata metadata{.displayMaxLuminance = maxDisplayLuminance,
-                               .currentDisplayLuminanceNits = currentDisplayLuminanceNits,
                                // If the input luminance is unknown, use display luminance (aka,
                                // no-op any luminance changes)
                                // This will be the case for eg screenshots in addition to
diff --git a/libs/tonemap/include/tonemap/tonemap.h b/libs/tonemap/include/tonemap/tonemap.h
index 6233e6c..b9abf8c 100644
--- a/libs/tonemap/include/tonemap/tonemap.h
+++ b/libs/tonemap/include/tonemap/tonemap.h
@@ -44,8 +44,6 @@
 struct Metadata {
     // The maximum luminance of the display in nits
     float displayMaxLuminance = 0.0;
-    // The current luminance of the display in nits
-    float currentDisplayLuminanceNits = 0.0;
     // The maximum luminance of the content in nits
     float contentMaxLuminance = 0.0;
 };
diff --git a/libs/tonemap/tests/tonemap_test.cpp b/libs/tonemap/tests/tonemap_test.cpp
index 7a7958f..1d46482 100644
--- a/libs/tonemap/tests/tonemap_test.cpp
+++ b/libs/tonemap/tests/tonemap_test.cpp
@@ -61,7 +61,7 @@
     EXPECT_GT(contentLumFloat, 0);
 }
 
-TEST_F(TonemapTest, generateTonemapGainShaderSkSL_containsEntryPoint) {
+TEST_F(TonemapTest, generateTonemapGainShaderSkSL_containsEntryPointForPQ) {
     const auto shader =
             tonemap::getToneMapper()
                     ->generateTonemapGainShaderSkSL(aidl::android::hardware::graphics::common::
@@ -73,4 +73,16 @@
     EXPECT_THAT(shader, HasSubstr("float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz)"));
 }
 
+TEST_F(TonemapTest, generateTonemapGainShaderSkSL_containsEntryPointForHLG) {
+    const auto shader =
+            tonemap::getToneMapper()
+                    ->generateTonemapGainShaderSkSL(aidl::android::hardware::graphics::common::
+                                                            Dataspace::BT2020_ITU_HLG,
+                                                    aidl::android::hardware::graphics::common::
+                                                            Dataspace::DISPLAY_P3);
+
+    // Other tests such as librenderengine_test will plug in the shader to check compilation.
+    EXPECT_THAT(shader, HasSubstr("float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz)"));
+}
+
 } // namespace android
diff --git a/libs/tonemap/tonemap.cpp b/libs/tonemap/tonemap.cpp
index c2372fe..bc0a884 100644
--- a/libs/tonemap/tonemap.cpp
+++ b/libs/tonemap/tonemap.cpp
@@ -407,7 +407,6 @@
             )");
         switch (sourceDataspaceInt & kTransferMask) {
             case kTransferST2084:
-            case kTransferHLG:
                 switch (destinationDataspaceInt & kTransferMask) {
                     case kTransferST2084:
                         program.append(R"(
@@ -428,39 +427,22 @@
                         break;
 
                     default:
-                        switch (sourceDataspaceInt & kTransferMask) {
-                            case kTransferST2084:
-                                program.append(R"(
-                                        float libtonemap_OETFTone(float channel) {
-                                            channel = channel / 10000.0;
-                                            float m1 = (2610.0 / 4096.0) / 4.0;
-                                            float m2 = (2523.0 / 4096.0) * 128.0;
-                                            float c1 = (3424.0 / 4096.0);
-                                            float c2 = (2413.0 / 4096.0) * 32.0;
-                                            float c3 = (2392.0 / 4096.0) * 32.0;
-
-                                            float tmp = pow(channel, float(m1));
-                                            tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
-                                            return pow(tmp, float(m2));
-                                        }
-                                    )");
-                                break;
-                            case kTransferHLG:
-                                program.append(R"(
-                                        float libtonemap_OETFTone(float channel) {
-                                            channel = channel / 1000.0;
-                                            const float a = 0.17883277;
-                                            const float b = 0.28466892;
-                                            const float c = 0.55991073;
-                                            return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) :
-                                                    a * log(12.0 * channel - b) + c;
-                                        }
-                                    )");
-                                break;
-                        }
                         // Here we're mapping from HDR to SDR content, so interpolate using a
                         // Hermitian polynomial onto the smaller luminance range.
                         program.append(R"(
+                                float libtonemap_OETFTone(float channel) {
+                                    channel = channel / 10000.0;
+                                    float m1 = (2610.0 / 4096.0) / 4.0;
+                                    float m2 = (2523.0 / 4096.0) * 128.0;
+                                    float c1 = (3424.0 / 4096.0);
+                                    float c2 = (2413.0 / 4096.0) * 32.0;
+                                    float c3 = (2392.0 / 4096.0) * 32.0;
+
+                                    float tmp = pow(channel, float(m1));
+                                    tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
+                                    return pow(tmp, float(m2));
+                                }
+
                                 float libtonemap_ToneMapTargetNits(float maxRGB) {
                                     float maxInLumi = in_libtonemap_inputMaxLuminance;
                                     float maxOutLumi = in_libtonemap_displayMaxLuminance;
@@ -508,6 +490,30 @@
                         break;
                 }
                 break;
+            case kTransferHLG:
+                switch (destinationDataspaceInt & kTransferMask) {
+                    // HLG -> HDR does not tone-map at all
+                    case kTransferST2084:
+                    case kTransferHLG:
+                        program.append(R"(
+                                    float libtonemap_ToneMapTargetNits(float maxRGB) {
+                                        return maxRGB;
+                                    }
+                                )");
+                        break;
+                    default:
+                        // libshaders follows BT2100 OOTF, but with a nominal peak display luminance
+                        // of 1000 nits. Renormalize to max display luminance if we're tone-mapping
+                        // down to SDR, as libshaders normalizes all SDR output from [0,
+                        // maxDisplayLumins] -> [0, 1]
+                        program.append(R"(
+                                    float libtonemap_ToneMapTargetNits(float maxRGB) {
+                                        return maxRGB * in_libtonemap_displayMaxLuminance / 1000.0;
+                                    }
+                                )");
+                        break;
+                }
+                break;
             default:
                 // Inverse tone-mapping and SDR-SDR mapping is not supported.
                 program.append(R"(
@@ -558,7 +564,6 @@
         double targetNits = 0.0;
         switch (sourceDataspaceInt & kTransferMask) {
             case kTransferST2084:
-            case kTransferHLG:
                 switch (destinationDataspaceInt & kTransferMask) {
                     case kTransferST2084:
                         targetNits = maxRGB;
@@ -587,19 +592,9 @@
                         double x2 = x1 + (x3 - x1) * 4.0 / 17.0;
                         double y2 = maxOutLumi * 0.9;
 
-                        double greyNorm1 = 0.0;
-                        double greyNorm2 = 0.0;
-                        double greyNorm3 = 0.0;
-
-                        if ((sourceDataspaceInt & kTransferMask) == kTransferST2084) {
-                            greyNorm1 = OETF_ST2084(x1);
-                            greyNorm2 = OETF_ST2084(x2);
-                            greyNorm3 = OETF_ST2084(x3);
-                        } else if ((sourceDataspaceInt & kTransferMask) == kTransferHLG) {
-                            greyNorm1 = OETF_HLG(x1);
-                            greyNorm2 = OETF_HLG(x2);
-                            greyNorm3 = OETF_HLG(x3);
-                        }
+                        const double greyNorm1 = OETF_ST2084(x1);
+                        const double greyNorm2 = OETF_ST2084(x2);
+                        const double greyNorm3 = OETF_ST2084(x3);
 
                         double slope2 = (y2 - y1) / (greyNorm2 - greyNorm1);
                         double slope3 = (y3 - y2) / (greyNorm3 - greyNorm2);
@@ -613,12 +608,7 @@
                             break;
                         }
 
-                        double greyNits = 0.0;
-                        if ((sourceDataspaceInt & kTransferMask) == kTransferST2084) {
-                            greyNits = OETF_ST2084(targetNits);
-                        } else if ((sourceDataspaceInt & kTransferMask) == kTransferHLG) {
-                            greyNits = OETF_HLG(targetNits);
-                        }
+                        const double greyNits = OETF_ST2084(targetNits);
 
                         if (greyNits <= greyNorm2) {
                             targetNits = (greyNits - greyNorm2) * slope2 + y2;
@@ -630,15 +620,20 @@
                         break;
                 }
                 break;
-            default:
+            case kTransferHLG:
                 switch (destinationDataspaceInt & kTransferMask) {
                     case kTransferST2084:
                     case kTransferHLG:
-                    default:
                         targetNits = maxRGB;
                         break;
+                    default:
+                        targetNits = maxRGB * metadata.displayMaxLuminance / 1000.0;
+                        break;
                 }
                 break;
+            default:
+                targetNits = maxRGB;
+                break;
         }
 
         return targetNits / maxRGB;
diff --git a/libs/ui/DynamicDisplayInfo.cpp b/libs/ui/DynamicDisplayInfo.cpp
index d5c4ef0..78ba996 100644
--- a/libs/ui/DynamicDisplayInfo.cpp
+++ b/libs/ui/DynamicDisplayInfo.cpp
@@ -41,7 +41,8 @@
             FlattenableHelpers::getFlattenedSize(activeColorMode) +
             FlattenableHelpers::getFlattenedSize(hdrCapabilities) +
             FlattenableHelpers::getFlattenedSize(autoLowLatencyModeSupported) +
-            FlattenableHelpers::getFlattenedSize(gameContentTypeSupported);
+            FlattenableHelpers::getFlattenedSize(gameContentTypeSupported) +
+            FlattenableHelpers::getFlattenedSize(preferredBootDisplayMode);
 }
 
 status_t DynamicDisplayInfo::flatten(void* buffer, size_t size) const {
@@ -55,6 +56,7 @@
     RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, hdrCapabilities));
     RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, autoLowLatencyModeSupported));
     RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, gameContentTypeSupported));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, preferredBootDisplayMode));
     return OK;
 }
 
@@ -66,6 +68,7 @@
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &hdrCapabilities));
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &autoLowLatencyModeSupported));
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &gameContentTypeSupported));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &preferredBootDisplayMode));
     return OK;
 }
 
diff --git a/libs/ui/Fence.cpp b/libs/ui/Fence.cpp
index 33ab7c4..cc96f83 100644
--- a/libs/ui/Fence.cpp
+++ b/libs/ui/Fence.cpp
@@ -132,9 +132,13 @@
         ALOGE("sync_file_info returned NULL for fd %d", mFenceFd.get());
         return SIGNAL_TIME_INVALID;
     }
+
     if (finfo->status != 1) {
+        const auto status = finfo->status;
+        ALOGE_IF(status < 0, "%s: sync_file_info contains an error: <%d> for fd: <%d>", __func__,
+                 status, mFenceFd.get());
         sync_file_info_free(finfo);
-        return SIGNAL_TIME_PENDING;
+        return status < 0 ? SIGNAL_TIME_INVALID : SIGNAL_TIME_PENDING;
     }
 
     uint64_t timestamp = 0;
diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp
index 040a62b..f23f10a 100644
--- a/libs/ui/Gralloc2.cpp
+++ b/libs/ui/Gralloc2.cpp
@@ -110,6 +110,15 @@
               descriptorInfo->usage & ~validUsageBits);
         return BAD_VALUE;
     }
+
+    // Gralloc2 implementations never understand non-BLOB with GPU_DATA_BUFFER
+    // and do not reliably reject it.
+    if (descriptorInfo->usage & BufferUsage::GPU_DATA_BUFFER &&
+        descriptorInfo->format != hardware::graphics::common::V1_1::PixelFormat::BLOB) {
+        ALOGE("gralloc2 does not support non-BLOB pixel formats with GPU_DATA_BUFFER usage");
+        return BAD_VALUE;
+    }
+
     return NO_ERROR;
 }
 
diff --git a/libs/ui/Gralloc3.cpp b/libs/ui/Gralloc3.cpp
index 882674f..15c60bc 100644
--- a/libs/ui/Gralloc3.cpp
+++ b/libs/ui/Gralloc3.cpp
@@ -101,6 +101,15 @@
               descriptorInfo->usage & ~validUsageBits);
         return BAD_VALUE;
     }
+
+    // Gralloc3 implementations never understand non-BLOB with GPU_DATA_BUFFER
+    // and do not reliably reject it.
+    if (descriptorInfo->usage & BufferUsage::GPU_DATA_BUFFER &&
+        descriptorInfo->format != hardware::graphics::common::V1_2::PixelFormat::BLOB) {
+        ALOGE("gralloc3 does not support non-BLOB pixel formats with GPU_DATA_BUFFER usage");
+        return BAD_VALUE;
+    }
+
     return NO_ERROR;
 }
 
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index 1f8a2f0..1a42642 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -41,6 +41,7 @@
 using android::hardware::hidl_vec;
 using android::hardware::graphics::allocator::V4_0::IAllocator;
 using android::hardware::graphics::common::V1_2::BufferUsage;
+using android::hardware::graphics::common::V1_2::PixelFormat;
 using android::hardware::graphics::mapper::V4_0::BufferDescriptor;
 using android::hardware::graphics::mapper::V4_0::Error;
 using android::hardware::graphics::mapper::V4_0::IMapper;
@@ -61,6 +62,9 @@
 static constexpr Error kTransactionError = Error::NO_RESOURCES;
 static const auto kAidlAllocatorServiceName = AidlIAllocator::descriptor + std::string("/default");
 
+// TODO(b/72323293, b/72703005): Remove these invalid bits from callers
+static constexpr uint64_t kRemovedUsageBits = static_cast<uint64_t>((1 << 10) | (1 << 13));
+
 uint64_t getValidUsageBits() {
     static const uint64_t validUsageBits = []() -> uint64_t {
         uint64_t bits = 0;
@@ -70,7 +74,7 @@
         }
         return bits;
     }();
-    return validUsageBits;
+    return validUsageBits | kRemovedUsageBits;
 }
 
 uint64_t getValidUsageBits41() {
@@ -92,9 +96,48 @@
     outRect.height = rect.height();
     return outRect;
 }
-static inline void sBufferDescriptorInfo(std::string name, uint32_t width, uint32_t height,
-                                         PixelFormat format, uint32_t layerCount, uint64_t usage,
-                                         IMapper::BufferDescriptorInfo* outDescriptorInfo) {
+
+// See if gralloc "4.1" is available.
+static bool hasIAllocatorAidl() {
+    // Avoid re-querying repeatedly for this information;
+    static bool sHasIAllocatorAidl = []() -> bool {
+        if (__builtin_available(android 31, *)) {
+            return AServiceManager_isDeclared(kAidlAllocatorServiceName.c_str());
+        }
+        return false;
+    }();
+    return sHasIAllocatorAidl;
+}
+
+// Determines whether the passed info is compatible with the mapper.
+static status_t validateBufferDescriptorInfo(IMapper::BufferDescriptorInfo* descriptorInfo) {
+    uint64_t validUsageBits = getValidUsageBits();
+    if (hasIAllocatorAidl()) {
+        validUsageBits |= getValidUsageBits41();
+    }
+
+    if (descriptorInfo->usage & ~validUsageBits) {
+        ALOGE("buffer descriptor contains invalid usage bits 0x%" PRIx64,
+              descriptorInfo->usage & ~validUsageBits);
+        return BAD_VALUE;
+    }
+
+    // Combinations that are only allowed with gralloc 4.1.
+    // Previous grallocs must be protected from this.
+    if (!hasIAllocatorAidl() &&
+            descriptorInfo->format != hardware::graphics::common::V1_2::PixelFormat::BLOB &&
+            descriptorInfo->usage & BufferUsage::GPU_DATA_BUFFER) {
+        ALOGE("non-BLOB pixel format with GPU_DATA_BUFFER usage is not supported prior to gralloc 4.1");
+        return BAD_VALUE;
+    }
+
+    return NO_ERROR;
+}
+
+static inline status_t sBufferDescriptorInfo(std::string name, uint32_t width, uint32_t height,
+                                             PixelFormat format, uint32_t layerCount,
+                                             uint64_t usage,
+                                             IMapper::BufferDescriptorInfo* outDescriptorInfo) {
     outDescriptorInfo->name = name;
     outDescriptorInfo->width = width;
     outDescriptorInfo->height = height;
@@ -102,21 +145,8 @@
     outDescriptorInfo->format = static_cast<hardware::graphics::common::V1_2::PixelFormat>(format);
     outDescriptorInfo->usage = usage;
     outDescriptorInfo->reservedSize = 0;
-}
 
-// See if gralloc "4.1" is available.
-static bool hasIAllocatorAidl() {
-    // Avoid re-querying repeatedly for this information;
-    static bool sHasIAllocatorAidl = []() -> bool {
-        // TODO: Enable after landing sepolicy changes
-        if constexpr ((true)) return false;
-
-        if (__builtin_available(android 31, *)) {
-            return AServiceManager_isDeclared(kAidlAllocatorServiceName.c_str());
-        }
-        return false;
-    }();
-    return sHasIAllocatorAidl;
+    return validateBufferDescriptorInfo(outDescriptorInfo);
 }
 
 } // anonymous namespace
@@ -140,21 +170,6 @@
     return mMapper != nullptr;
 }
 
-status_t Gralloc4Mapper::validateBufferDescriptorInfo(
-        IMapper::BufferDescriptorInfo* descriptorInfo) const {
-    uint64_t validUsageBits = getValidUsageBits();
-    if (hasIAllocatorAidl()) {
-        validUsageBits |= getValidUsageBits41();
-    }
-
-    if (descriptorInfo->usage & ~validUsageBits) {
-        ALOGE("buffer descriptor contains invalid usage bits 0x%" PRIx64,
-              descriptorInfo->usage & ~validUsageBits);
-        return BAD_VALUE;
-    }
-    return NO_ERROR;
-}
-
 status_t Gralloc4Mapper::createDescriptor(void* bufferDescriptorInfo,
                                           void* outBufferDescriptor) const {
     IMapper::BufferDescriptorInfo* descriptorInfo =
@@ -207,8 +222,10 @@
                                             uint32_t layerCount, uint64_t usage,
                                             uint32_t stride) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo("validateBufferSize", width, height, format, layerCount, usage,
-                          &descriptorInfo);
+    if (auto error = sBufferDescriptorInfo("validateBufferSize", width, height, format, layerCount,
+                                           usage, &descriptorInfo) != OK) {
+        return error;
+    }
 
     auto buffer = const_cast<native_handle_t*>(bufferHandle);
     auto ret = mMapper->validateBufferSize(buffer, descriptorInfo, stride);
@@ -427,7 +444,7 @@
             if (fd >= 0) {
                 releaseFence = fd;
             } else {
-                ALOGD("failed to dup unlock release fence");
+                ALOGW("failed to dup unlock release fence");
                 sync_wait(fenceHandle->data[0], -1);
             }
         }
@@ -448,7 +465,12 @@
                                      uint32_t layerCount, uint64_t usage,
                                      bool* outSupported) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo("isSupported", width, height, format, layerCount, usage, &descriptorInfo);
+    if (auto error = sBufferDescriptorInfo("isSupported", width, height, format, layerCount, usage,
+                                           &descriptorInfo) != OK) {
+        // Usage isn't known to the HAL or otherwise failed validation.
+        *outSupported = false;
+        return OK;
+    }
 
     Error error;
     auto ret = mMapper->isSupported(descriptorInfo,
@@ -691,7 +713,10 @@
     }
 
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo("getDefault", width, height, format, layerCount, usage, &descriptorInfo);
+    if (auto error = sBufferDescriptorInfo("getDefault", width, height, format, layerCount, usage,
+                                           &descriptorInfo) != OK) {
+        return error;
+    }
 
     hidl_vec<uint8_t> vec;
     Error error;
@@ -1133,7 +1158,10 @@
                                      uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
                                      buffer_handle_t* outBufferHandles, bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo(requestorName, width, height, format, layerCount, usage, &descriptorInfo);
+    if (auto error = sBufferDescriptorInfo(requestorName, width, height, format, layerCount, usage,
+                                           &descriptorInfo) != OK) {
+        return error;
+    }
 
     BufferDescriptor descriptor;
     status_t error = mMapper.createDescriptor(static_cast<void*>(&descriptorInfo),
diff --git a/libs/ui/include/ui/DynamicDisplayInfo.h b/libs/ui/include/ui/DynamicDisplayInfo.h
index a4c2f71..ce75a65 100644
--- a/libs/ui/include/ui/DynamicDisplayInfo.h
+++ b/libs/ui/include/ui/DynamicDisplayInfo.h
@@ -35,7 +35,7 @@
     // This struct is going to be serialized over binder, so
     // we can't use size_t because it may have different width
     // in the client process.
-    int32_t activeDisplayModeId;
+    ui::DisplayModeId activeDisplayModeId;
 
     std::vector<ui::ColorMode> supportedColorModes;
     ui::ColorMode activeColorMode;
@@ -49,6 +49,9 @@
     // For more information, see the HDMI 1.4 specification.
     bool gameContentTypeSupported;
 
+    // The boot display mode preferred by the implementation.
+    ui::DisplayModeId preferredBootDisplayMode;
+
     std::optional<ui::DisplayMode> getActiveDisplayMode() const;
 
     bool isFixedSize() const { return false; }
diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h
index 6bafcd6..fe38709 100644
--- a/libs/ui/include/ui/Gralloc4.h
+++ b/libs/ui/include/ui/Gralloc4.h
@@ -155,10 +155,6 @@
 private:
     friend class GraphicBufferAllocator;
 
-    // Determines whether the passed info is compatible with the mapper.
-    status_t validateBufferDescriptorInfo(
-            hardware::graphics::mapper::V4_0::IMapper::BufferDescriptorInfo* descriptorInfo) const;
-
     template <class T>
     using DecodeFunction = status_t (*)(const hardware::hidl_vec<uint8_t>& input, T* output);
 
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index ec58325..22a69e5 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -49,6 +49,7 @@
     srcs: [
         "InputClassifier.cpp",
         "InputClassifierConverter.cpp",
+        "UnwantedInteractionBlocker.cpp",
         "InputManager.cpp",
     ],
 }
@@ -60,6 +61,7 @@
         "android.hardware.input.classifier@1.0",
         "libbase",
         "libbinder",
+        "libchrome",
         "libcrypto",
         "libcutils",
         "libhidlbase",
@@ -76,6 +78,7 @@
     ],
     static_libs: [
         "libattestation",
+        "libpalmrejection",
     ],
 }
 
diff --git a/services/inputflinger/InputClassifierConverter.cpp b/services/inputflinger/InputClassifierConverter.cpp
index fc8c7c3..b58a188 100644
--- a/services/inputflinger/InputClassifierConverter.cpp
+++ b/services/inputflinger/InputClassifierConverter.cpp
@@ -325,11 +325,6 @@
     return out;
 }
 
-static uint8_t getActionIndex(int32_t action) {
-    return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
-            AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
-}
-
 static void getHidlPropertiesAndCoords(const NotifyMotionArgs& args,
         std::vector<common::V1_0::PointerProperties>* outPointerProperties,
         std::vector<common::V1_0::PointerCoords>* outPointerCoords) {
@@ -360,7 +355,7 @@
     event.eventTime = args.eventTime;
     event.deviceTimestamp = 0;
     event.action = getAction(args.action & AMOTION_EVENT_ACTION_MASK);
-    event.actionIndex = getActionIndex(args.action);
+    event.actionIndex = MotionEvent::getActionIndex(args.action);
     event.actionButton = getActionButton(args.actionButton);
     event.flags = getFlags(args.flags);
     event.policyFlags = getPolicyFlags(args.policyFlags);
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
index 158d0eb..73b63e3 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -188,6 +188,25 @@
     return true;
 }
 
+std::string NotifyMotionArgs::dump() const {
+    std::string coords;
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        if (!coords.empty()) {
+            coords += ", ";
+        }
+        coords += StringPrintf("{%" PRIu32 ": ", i);
+        coords +=
+                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));
+        coords += "}";
+    }
+    return StringPrintf("NotifyMotionArgs(id=%" PRId32 ", eventTime=%" PRId64 ", deviceId=%" PRId32
+                        ", source=%s, action=%s, pointerCount=%" PRIu32 " pointers=%s)",
+                        id, eventTime, deviceId, inputEventSourceToString(source).c_str(),
+                        MotionEvent::actionToString(action).c_str(), pointerCount, coords.c_str());
+}
+
 void NotifyMotionArgs::notify(InputListenerInterface& listener) const {
     listener.notifyMotion(this);
 }
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 221e193..7a9862d 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -21,6 +21,7 @@
 #include "InputManager.h"
 #include "InputDispatcherFactory.h"
 #include "InputReaderFactory.h"
+#include "UnwantedInteractionBlocker.h"
 
 #include <binder/IPCThreadState.h>
 
@@ -54,12 +55,17 @@
     }
 }
 
+/**
+ * The event flow is via the "InputListener" interface, as follows:
+ * InputReader -> UnwantedInteractionBlocker -> InputClassifier -> InputDispatcher
+ */
 InputManager::InputManager(
         const sp<InputReaderPolicyInterface>& readerPolicy,
         const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
     mDispatcher = createInputDispatcher(dispatcherPolicy);
     mClassifier = std::make_unique<InputClassifier>(*mDispatcher);
-    mReader = createInputReader(readerPolicy, *mClassifier);
+    mUnwantedInteractionBlocker = std::make_unique<UnwantedInteractionBlocker>(*mClassifier);
+    mReader = createInputReader(readerPolicy, *mUnwantedInteractionBlocker);
 }
 
 InputManager::~InputManager() {
@@ -106,6 +112,10 @@
     return *mReader;
 }
 
+UnwantedInteractionBlockerInterface& InputManager::getUnwantedInteractionBlocker() {
+    return *mUnwantedInteractionBlocker;
+}
+
 InputClassifierInterface& InputManager::getClassifier() {
     return *mClassifier;
 }
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index e000283..35d2b0f 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -23,6 +23,7 @@
 
 #include "InputClassifier.h"
 #include "InputReaderBase.h"
+#include "include/UnwantedInteractionBlockerInterface.h"
 
 #include <InputDispatcherInterface.h>
 #include <InputDispatcherPolicyInterface.h>
@@ -46,11 +47,16 @@
  * The input manager has three components.
  *
  * 1. The InputReader class starts a thread that reads and preprocesses raw input events, applies
- *    policy, and posts messages to a queue managed by the InputClassifier.
- * 2. The InputClassifier class starts a thread to communicate with the device-specific
- *    classifiers. It then waits on the queue of events from InputReader, applies a classification
- *    to them, and queues them for the InputDispatcher.
- * 3. The InputDispatcher class starts a thread that waits for new events on the
+ *    policy, and posts messages to a queue managed by the UnwantedInteractionBlocker.
+ * 2. The UnwantedInteractionBlocker is responsible for removing unwanted interactions. For example,
+ *    this could be a palm on the screen. This stage would alter the event stream to remove either
+ *    partially (some of the pointers) or fully (all touches) the unwanted interaction. The events
+ *    are processed on the InputReader thread, without any additional queue. The events are then
+ *    posted to the queue managed by the InputClassifier.
+ * 3. The InputClassifier class starts a thread to communicate with the device-specific
+ *    classifiers. It then waits on the queue of events from UnwantedInteractionBlocker, applies
+ *    a classification to them, and queues them for the InputDispatcher.
+ * 4. The InputDispatcher class starts a thread that waits for new events on the
  *    previous queue and asynchronously dispatches them to applications.
  *
  * By design, none of these classes share any internal state.  Moreover, all communication is
@@ -76,6 +82,9 @@
     /* Gets the input reader. */
     virtual InputReaderInterface& getReader() = 0;
 
+    /* Gets the unwanted interaction blocker. */
+    virtual UnwantedInteractionBlockerInterface& getUnwantedInteractionBlocker() = 0;
+
     /* Gets the input classifier */
     virtual InputClassifierInterface& getClassifier() = 0;
 
@@ -96,6 +105,7 @@
     status_t stop() override;
 
     InputReaderInterface& getReader() override;
+    UnwantedInteractionBlockerInterface& getUnwantedInteractionBlocker() override;
     InputClassifierInterface& getClassifier() override;
     InputDispatcherInterface& getDispatcher() override;
 
@@ -107,6 +117,8 @@
 private:
     std::unique_ptr<InputReaderInterface> mReader;
 
+    std::unique_ptr<UnwantedInteractionBlockerInterface> mUnwantedInteractionBlocker;
+
     std::unique_ptr<InputClassifierInterface> mClassifier;
 
     std::unique_ptr<InputDispatcherInterface> mDispatcher;
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 3d85bef..9b72ff4 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -15,6 +15,9 @@
       "name": "inputflinger_tests"
     },
     {
+      "name": "libpalmrejection_test"
+    },
+    {
       "name": "InputTests"
     },
     {
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
new file mode 100644
index 0000000..64dbb8c
--- /dev/null
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -0,0 +1,687 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "UnwantedInteractionBlocker"
+#include "UnwantedInteractionBlocker.h"
+
+#include <android-base/stringprintf.h>
+#include <inttypes.h>
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+#include <server_configurable_flags/get_flags.h>
+
+#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
+#include "ui/events/ozone/evdev/touch_filter/palm_model/onedevice_train_palm_detection_filter_model.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+
+// Category (=namespace) name for the input settings that are applied at boot time
+static const char* INPUT_NATIVE_BOOT = "input_native_boot";
+/**
+ * Feature flag name. This flag determines whether palm rejection is enabled. To enable, specify
+ * 'true' (not case sensitive) or '1'. To disable, specify any other value.
+ */
+static const char* PALM_REJECTION_ENABLED = "palm_rejection_enabled";
+
+static std::string toLower(std::string s) {
+    std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
+    return s;
+}
+
+static bool isFromTouchscreen(int32_t source) {
+    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 true if palm rejection is enabled via the server configurable flags. Return false
+ * otherwise.
+ */
+static bool isPalmRejectionEnabled() {
+    std::string value = toLower(
+            server_configurable_flags::GetServerConfigurableFlag(INPUT_NATIVE_BOOT,
+                                                                 PALM_REJECTION_ENABLED, "false"));
+    if (value == "true" || 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;
+    }
+    ALOGW("Got tool type %" PRId32 ", converting to MT_TOOL_FINGER", toolType);
+    return MT_TOOL_FINGER;
+}
+
+static std::string addPrefix(std::string str, const std::string& prefix) {
+    std::stringstream ss;
+    bool newLineStarted = true;
+    for (const auto& ch : str) {
+        if (newLineStarted) {
+            ss << prefix;
+            newLineStarted = false;
+        }
+        if (ch == '\n') {
+            newLineStarted = true;
+        }
+        ss << ch;
+    }
+    return ss.str();
+}
+
+template <typename T>
+static std::string dumpSet(const std::set<T>& v) {
+    static_assert(std::is_integral<T>::value, "Only integral types can be printed.");
+    std::string out;
+    for (const T& entry : v) {
+        out += out.empty() ? "{" : ", ";
+        out += android::base::StringPrintf("%i", entry);
+    }
+    return out.empty() ? "{}" : (out + "}");
+}
+
+template <typename K, typename V>
+static std::string dumpMap(const std::map<K, V>& map) {
+    static_assert(std::is_integral<K>::value, "Keys should have integral type to be printed.");
+    static_assert(std::is_integral<V>::value, "Values should have integral type to be printed.");
+    std::string out;
+    for (const auto& [k, v] : map) {
+        if (!out.empty()) {
+            out += "\n";
+        }
+        out += android::base::StringPrintf("%i : %i", static_cast<int>(k), static_cast<int>(v));
+    }
+    return out;
+}
+
+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;
+}
+
+static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        if (pointerId == args.pointerProperties[i].id) {
+            return AMOTION_EVENT_ACTION_POINTER_UP |
+                    (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+        }
+    }
+    LOG_ALWAYS_FATAL("Can't find pointerId %" PRId32 " in %s", pointerId, args.dump().c_str());
+}
+
+/**
+ * Find the action for individual pointer at the given pointer index.
+ * This is always equal to MotionEvent::getActionMasked, except for
+ * POINTER_UP or POINTER_DOWN events. For example, in a POINTER_UP event, the action for
+ * the active pointer is ACTION_POINTER_UP, while the action for the other pointers is ACTION_MOVE.
+ */
+static int32_t resolveActionForPointer(uint8_t pointerIndex, int32_t action) {
+    const int32_t actionMasked = MotionEvent::getActionMasked(action);
+    if (actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN &&
+        actionMasked != AMOTION_EVENT_ACTION_POINTER_UP) {
+        return actionMasked;
+    }
+    // This is a POINTER_DOWN or POINTER_UP event
+    const uint8_t actionIndex = MotionEvent::getActionIndex(action);
+    if (pointerIndex == actionIndex) {
+        return actionMasked;
+    }
+    // When POINTER_DOWN or POINTER_UP happens, it's actually a MOVE for all of the other
+    // pointers
+    return AMOTION_EVENT_ACTION_MOVE;
+}
+
+static const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
+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);
+    const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
+    const bool isPointerUpOrDownAction = actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN ||
+            actionMasked == AMOTION_EVENT_ACTION_POINTER_UP;
+
+    NotifyMotionArgs newArgs{args};
+    newArgs.pointerCount = 0;
+    int32_t newActionIndex = 0;
+    for (uint32_t i = 0; i < args.pointerCount; i++) {
+        const int32_t pointerId = args.pointerProperties[i].id;
+        if (pointerIds.find(pointerId) != pointerIds.end()) {
+            // skip this pointer
+            if (isPointerUpOrDownAction && i == actionIndex) {
+                // The active pointer is being removed, so the action is no longer valid.
+                // Set the action to 'UNKNOWN' here. The caller is responsible for updating this
+                // action later to a proper value.
+                newArgs.action = ACTION_UNKNOWN;
+            }
+            continue;
+        }
+        newArgs.pointerProperties[newArgs.pointerCount].copyFrom(args.pointerProperties[i]);
+        newArgs.pointerCoords[newArgs.pointerCount].copyFrom(args.pointerCoords[i]);
+        if (i == actionIndex) {
+            newActionIndex = newArgs.pointerCount;
+        }
+        newArgs.pointerCount++;
+    }
+    // Update POINTER_DOWN or POINTER_UP actions
+    if (isPointerUpOrDownAction && newArgs.action != ACTION_UNKNOWN) {
+        newArgs.action =
+                actionMasked | (newActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+        // Convert POINTER_DOWN and POINTER_UP to DOWN and UP if there's only 1 pointer remaining
+        if (newArgs.pointerCount == 1) {
+            if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+                newArgs.action = AMOTION_EVENT_ACTION_DOWN;
+            } else if (actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
+                newArgs.action = AMOTION_EVENT_ACTION_UP;
+            }
+        }
+    }
+    return newArgs;
+}
+
+std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
+        const InputDeviceInfo& deviceInfo) {
+    if (!isFromTouchscreen(deviceInfo.getSources())) {
+        return std::nullopt;
+    }
+    AndroidPalmFilterDeviceInfo out;
+    const InputDeviceInfo::MotionRange* axisX =
+            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN);
+    if (axisX != nullptr) {
+        out.max_x = axisX->max;
+        out.x_res = axisX->resolution;
+    } else {
+        ALOGW("Palm rejection is disabled for %s because AXIS_X is not supported",
+              deviceInfo.getDisplayName().c_str());
+        return std::nullopt;
+    }
+    const InputDeviceInfo::MotionRange* axisY =
+            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN);
+    if (axisY != nullptr) {
+        out.max_y = axisY->max;
+        out.y_res = axisY->resolution;
+    } else {
+        ALOGW("Palm rejection is disabled for %s because AXIS_Y is not supported",
+              deviceInfo.getDisplayName().c_str());
+        return std::nullopt;
+    }
+    const InputDeviceInfo::MotionRange* axisMajor =
+            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN);
+    if (axisMajor != nullptr) {
+        out.major_radius_res = axisMajor->resolution;
+        out.touch_major_res = axisMajor->resolution;
+    } else {
+        return std::nullopt;
+    }
+    const InputDeviceInfo::MotionRange* axisMinor =
+            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHSCREEN);
+    if (axisMinor != nullptr) {
+        out.minor_radius_res = axisMinor->resolution;
+        out.touch_minor_res = axisMinor->resolution;
+        out.minor_radius_supported = true;
+    } else {
+        out.minor_radius_supported = false;
+    }
+
+    return out;
+}
+
+/**
+ * Synthesize CANCEL events for any new pointers that should be canceled, while removing pointers
+ * that have already been canceled.
+ * The flow of the function is as follows:
+ * 1. Remove all already canceled pointers
+ * 2. Cancel all newly suppressed pointers
+ * 3. Decide what to do with the current event : keep it, or drop it
+ * The pointers can never be "unsuppressed": once a pointer is canceled, it will never become valid.
+ */
+std::vector<NotifyMotionArgs> cancelSuppressedPointers(
+        const NotifyMotionArgs& args, const std::set<int32_t>& oldSuppressedPointerIds,
+        const std::set<int32_t>& newSuppressedPointerIds) {
+    LOG_ALWAYS_FATAL_IF(args.pointerCount == 0, "0 pointers in %s", args.dump().c_str());
+
+    // First, let's remove the old suppressed pointers. They've already been canceled previously.
+    NotifyMotionArgs oldArgs = removePointerIds(args, oldSuppressedPointerIds);
+
+    // Cancel any newly suppressed pointers.
+    std::vector<NotifyMotionArgs> out;
+    const int32_t activePointerId =
+            args.pointerProperties[MotionEvent::getActionIndex(args.action)].id;
+    const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
+    // We will iteratively remove pointers from 'removedArgs'.
+    NotifyMotionArgs removedArgs{oldArgs};
+    for (uint32_t i = 0; i < oldArgs.pointerCount; i++) {
+        const int32_t pointerId = oldArgs.pointerProperties[i].id;
+        if (newSuppressedPointerIds.find(pointerId) == newSuppressedPointerIds.end()) {
+            // This is a pointer that should not be canceled. Move on.
+            continue;
+        }
+        if (pointerId == activePointerId && actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+            // Remove this pointer, but don't cancel it. We'll just not send the POINTER_DOWN event
+            removedArgs = removePointerIds(removedArgs, {pointerId});
+            continue;
+        }
+
+        if (removedArgs.pointerCount == 1) {
+            // We are about to remove the last pointer, which means there will be no more gesture
+            // remaining. This is identical to canceling all pointers, so just send a single CANCEL
+            // event, without any of the preceding POINTER_UP with FLAG_CANCELED events.
+            oldArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
+            oldArgs.action = AMOTION_EVENT_ACTION_CANCEL;
+            return {oldArgs};
+        }
+        // Cancel the current pointer
+        out.push_back(removedArgs);
+        out.back().flags |= AMOTION_EVENT_FLAG_CANCELED;
+        out.back().action = getActionUpForPointerId(out.back(), pointerId);
+
+        // Remove the newly canceled pointer from the args
+        removedArgs = removePointerIds(removedArgs, {pointerId});
+    }
+
+    // Now 'removedArgs' contains only pointers that are valid.
+    if (removedArgs.pointerCount <= 0 || removedArgs.action == ACTION_UNKNOWN) {
+        return out;
+    }
+    out.push_back(removedArgs);
+    return out;
+}
+
+UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener)
+      : UnwantedInteractionBlocker(listener, isPalmRejectionEnabled()){};
+
+UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener,
+                                                       bool enablePalmRejection)
+      : mListener(listener), mEnablePalmRejection(enablePalmRejection) {}
+
+void UnwantedInteractionBlocker::notifyConfigurationChanged(
+        const NotifyConfigurationChangedArgs* args) {
+    mListener.notifyConfigurationChanged(args);
+}
+
+void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs* args) {
+    mListener.notifyKey(args);
+}
+
+void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) {
+    auto it = mPalmRejectors.find(args->deviceId);
+    const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source);
+    if (!sendToPalmRejector) {
+        mListener.notifyMotion(args);
+        return;
+    }
+
+    const std::vector<NotifyMotionArgs> newMotions = it->second.processMotion(*args);
+    for (const NotifyMotionArgs& newArgs : newMotions) {
+        mListener.notifyMotion(&newArgs);
+    }
+}
+
+void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs* args) {
+    mListener.notifySwitch(args);
+}
+
+void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs* args) {
+    mListener.notifySensor(args);
+}
+
+void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs* args) {
+    mListener.notifyVibratorState(args);
+}
+void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+    auto it = mPalmRejectors.find(args->deviceId);
+    if (it != mPalmRejectors.end()) {
+        AndroidPalmFilterDeviceInfo info = it->second.getPalmFilterDeviceInfo();
+        // Re-create the object instead of resetting it
+        mPalmRejectors.erase(it);
+        mPalmRejectors.emplace(args->deviceId, info);
+    }
+    mListener.notifyDeviceReset(args);
+}
+
+void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
+        const NotifyPointerCaptureChangedArgs* args) {
+    mListener.notifyPointerCaptureChanged(args);
+}
+
+void UnwantedInteractionBlocker::notifyInputDevicesChanged(
+        const std::vector<InputDeviceInfo>& inputDevices) {
+    if (!mEnablePalmRejection) {
+        // Palm rejection is disabled. Don't create any palm rejector objects.
+        return;
+    }
+
+    // Let's see which of the existing devices didn't change, so that we can keep them
+    // and prevent event stream disruption
+    std::set<int32_t /*deviceId*/> devicesToKeep;
+    for (const InputDeviceInfo& device : inputDevices) {
+        std::optional<AndroidPalmFilterDeviceInfo> info = createPalmFilterDeviceInfo(device);
+        if (!info) {
+            continue;
+        }
+
+        auto [it, emplaced] = mPalmRejectors.try_emplace(device.getId(), *info);
+        if (!emplaced && *info != it->second.getPalmFilterDeviceInfo()) {
+            // Re-create the PalmRejector because the device info has changed.
+            mPalmRejectors.erase(it);
+            mPalmRejectors.emplace(device.getId(), *info);
+        }
+        devicesToKeep.insert(device.getId());
+    }
+    // Delete all devices that we don't need to keep
+    std::erase_if(mPalmRejectors, [&devicesToKeep](const auto& item) {
+        auto const& [deviceId, _] = item;
+        return devicesToKeep.find(deviceId) == devicesToKeep.end();
+    });
+}
+
+void UnwantedInteractionBlocker::dump(std::string& dump) {
+    dump += "UnwantedInteractionBlocker:\n";
+    dump += StringPrintf("  mEnablePalmRejection: %s\n", toString(mEnablePalmRejection));
+    dump += StringPrintf("  isPalmRejectionEnabled (flag value): %s\n",
+                         toString(isPalmRejectionEnabled()));
+    dump += mPalmRejectors.empty() ? "  mPalmRejectors: None\n" : "  mPalmRejectors:\n";
+    for (const auto& [deviceId, palmRejector] : mPalmRejectors) {
+        dump += StringPrintf("    deviceId = %" PRId32 ":\n", deviceId);
+        dump += addPrefix(palmRejector.dump(), "      ");
+    }
+}
+
+void UnwantedInteractionBlocker::monitor() {}
+
+UnwantedInteractionBlocker::~UnwantedInteractionBlocker() {}
+
+void SlotState::update(const NotifyMotionArgs& args) {
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        const int32_t pointerId = args.pointerProperties[i].id;
+        const int32_t resolvedAction = resolveActionForPointer(i, args.action);
+        processPointerId(pointerId, resolvedAction);
+    }
+}
+
+size_t SlotState::findUnusedSlot() const {
+    size_t unusedSlot = 0;
+    // Since the collection is ordered, we can rely on the in-order traversal
+    for (const auto& [slot, trackingId] : mPointerIdsBySlot) {
+        if (unusedSlot != slot) {
+            break;
+        }
+        unusedSlot++;
+    }
+    return unusedSlot;
+}
+
+void SlotState::processPointerId(int pointerId, int32_t actionMasked) {
+    switch (MotionEvent::getActionMasked(actionMasked)) {
+        case AMOTION_EVENT_ACTION_DOWN:
+        case AMOTION_EVENT_ACTION_POINTER_DOWN:
+        case AMOTION_EVENT_ACTION_HOVER_ENTER: {
+            // New pointer going down
+            size_t newSlot = findUnusedSlot();
+            mPointerIdsBySlot[newSlot] = pointerId;
+            mSlotsByPointerId[pointerId] = newSlot;
+            return;
+        }
+        case AMOTION_EVENT_ACTION_MOVE:
+        case AMOTION_EVENT_ACTION_HOVER_MOVE: {
+            return;
+        }
+        case AMOTION_EVENT_ACTION_CANCEL:
+        case AMOTION_EVENT_ACTION_POINTER_UP:
+        case AMOTION_EVENT_ACTION_UP:
+        case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+            auto it = mSlotsByPointerId.find(pointerId);
+            LOG_ALWAYS_FATAL_IF(it == mSlotsByPointerId.end());
+            size_t slot = it->second;
+            // Erase this pointer from both collections
+            mPointerIdsBySlot.erase(slot);
+            mSlotsByPointerId.erase(pointerId);
+            return;
+        }
+    }
+    LOG_ALWAYS_FATAL("Unhandled action : %s", MotionEvent::actionToString(actionMasked).c_str());
+    return;
+}
+
+std::optional<size_t> SlotState::getSlotForPointerId(int32_t pointerId) const {
+    auto it = mSlotsByPointerId.find(pointerId);
+    if (it == mSlotsByPointerId.end()) {
+        return std::nullopt;
+    }
+    return it->second;
+}
+
+std::string SlotState::dump() const {
+    std::string out = "mSlotsByPointerId:\n";
+    out += addPrefix(dumpMap(mSlotsByPointerId), "  ") + "\n";
+    out += "mPointerIdsBySlot:\n";
+    out += addPrefix(dumpMap(mPointerIdsBySlot), "  ") + "\n";
+    return out;
+}
+
+PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info,
+                           std::unique_ptr<::ui::PalmDetectionFilter> filter)
+      : mSharedPalmState(std::make_unique<::ui::SharedPalmDetectionFilterState>()),
+        mDeviceInfo(info),
+        mPalmDetectionFilter(std::move(filter)) {
+    if (mPalmDetectionFilter != nullptr) {
+        // This path is used for testing. Non-testing invocations should let this constructor
+        // create a real PalmDetectionFilter
+        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::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
+                                                   const AndroidPalmFilterDeviceInfo& deviceInfo,
+                                                   const SlotState& oldSlotState,
+                                                   const SlotState& newSlotState) {
+    std::vector<::ui::InProgressTouchEvdev> touches;
+
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        const int32_t pointerId = args.pointerProperties[i].id;
+        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);
+
+        // Whether there is new information for the touch.
+        touches.back().altered = true;
+
+        // Whether the touch was cancelled. Touch events should be ignored till a
+        // new touch is initiated.
+        touches.back().was_cancelled = false;
+
+        // Whether the touch is going to be canceled.
+        touches.back().cancelled = false;
+
+        // Whether the touch is delayed at first appearance. Will not be reported yet.
+        touches.back().delayed = false;
+
+        // Whether the touch was delayed before.
+        touches.back().was_delayed = false;
+
+        // Whether the touch is held until end or no longer held.
+        touches.back().held = false;
+
+        // Whether this touch was held before being sent.
+        touches.back().was_held = false;
+
+        const int32_t resolvedAction = resolveActionForPointer(i, args.action);
+        const bool isDown = resolvedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
+                resolvedAction == AMOTION_EVENT_ACTION_DOWN;
+        touches.back().was_touching = !isDown;
+
+        const bool isUpOrCancel = resolvedAction == AMOTION_EVENT_ACTION_CANCEL ||
+                resolvedAction == AMOTION_EVENT_ACTION_UP ||
+                resolvedAction == AMOTION_EVENT_ACTION_POINTER_UP;
+
+        touches.back().x = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X);
+        touches.back().y = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+        std::optional<size_t> slot = newSlotState.getSlotForPointerId(pointerId);
+        if (!slot) {
+            slot = oldSlotState.getSlotForPointerId(pointerId);
+        }
+        LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer %d", pointerId);
+        touches.back().slot = *slot;
+        touches.back().tracking_id = (!isUpOrCancel) ? pointerId : -1;
+        touches.back().touching = !isUpOrCancel;
+
+        // 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;
+        // 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;
+        touches.back().stylus_button = false;
+    }
+    return touches;
+}
+
+std::vector<NotifyMotionArgs> PalmRejector::processMotion(const NotifyMotionArgs& args) {
+    if (mPalmDetectionFilter == nullptr) {
+        return {args};
+    }
+    const bool skipThisEvent = args.action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+            args.action == AMOTION_EVENT_ACTION_HOVER_MOVE ||
+            args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
+            args.action == AMOTION_EVENT_ACTION_BUTTON_PRESS ||
+            args.action == AMOTION_EVENT_ACTION_BUTTON_RELEASE ||
+            args.action == AMOTION_EVENT_ACTION_SCROLL;
+    if (skipThisEvent) {
+        // Lets not process hover events, button events, or scroll for now.
+        return {args};
+    }
+    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::vector<NotifyMotionArgs> argsWithoutUnwantedPointers =
+            cancelSuppressedPointers(args, oldSuppressedIds, mSuppressedPointerIds);
+    for (const NotifyMotionArgs& checkArgs : argsWithoutUnwantedPointers) {
+        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());
+        }
+    }
+
+    return argsWithoutUnwantedPointers;
+}
+
+const AndroidPalmFilterDeviceInfo& PalmRejector::getPalmFilterDeviceInfo() {
+    return mDeviceInfo;
+}
+
+std::string PalmRejector::dump() const {
+    std::string out;
+    out += "mDeviceInfo:\n";
+    out += addPrefix(dumpDeviceInfo(mDeviceInfo), "  ");
+    out += "mSlotState:\n";
+    out += addPrefix(mSlotState.dump(), "  ");
+    out += "mSuppressedPointerIds: ";
+    out += dumpSet(mSuppressedPointerIds) + "\n";
+    return out;
+}
+
+} // namespace android
diff --git a/services/inputflinger/UnwantedInteractionBlocker.h b/services/inputflinger/UnwantedInteractionBlocker.h
new file mode 100644
index 0000000..14068fd
--- /dev/null
+++ b/services/inputflinger/UnwantedInteractionBlocker.h
@@ -0,0 +1,153 @@
+/*
+ * 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 <map>
+#include <set>
+
+#include "include/UnwantedInteractionBlockerInterface.h"
+#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h"
+#include "ui/events/ozone/evdev/touch_filter/palm_detection_filter.h"
+
+namespace android {
+
+// --- Functions for manipulation of event streams
+
+struct AndroidPalmFilterDeviceInfo : ::ui::PalmFilterDeviceInfo {
+    // Additional fields from 'TouchEventConverterEvdev', added here for convenience
+    int32_t touch_major_res = 1; // info.GetAbsInfoByCode(ABS_MT_TOUCH_MAJOR).resolution;
+    int32_t touch_minor_res = 1; // info.GetAbsInfoByCode(ABS_MT_TOUCH_MINOR).resolution;
+
+    auto operator<=>(const AndroidPalmFilterDeviceInfo&) const = default;
+};
+
+std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
+        const InputDeviceInfo& deviceInfo);
+
+static constexpr int32_t ACTION_UNKNOWN = -1;
+
+NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
+                                  const std::set<int32_t>& pointerIds);
+
+std::vector<NotifyMotionArgs> cancelSuppressedPointers(
+        const NotifyMotionArgs& args, const std::set<int32_t>& oldSuppressedPointerIds,
+        const std::set<int32_t>& newSuppressedPointerIds);
+
+std::string toString(const ::ui::InProgressTouchEvdev& touch);
+
+// --- Main classes and interfaces ---
+
+class PalmRejector;
+
+// --- Implementations ---
+
+/**
+ * Implementation of the UnwantedInteractionBlockerInterface.
+ * Represents a separate stage of input processing. All of the input events go through this stage.
+ * Acts as a passthrough for all input events except for motion events.
+ *
+ * The events of motion type are sent to PalmRejectors. PalmRejectors detect unwanted touches,
+ * and emit input streams with the bad pointers removed.
+ */
+class UnwantedInteractionBlocker : public UnwantedInteractionBlockerInterface {
+public:
+    explicit UnwantedInteractionBlocker(InputListenerInterface& listener);
+    explicit UnwantedInteractionBlocker(InputListenerInterface& listener, bool enablePalmRejection);
+
+    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override;
+    void notifyKey(const NotifyKeyArgs* args) override;
+    void notifyMotion(const NotifyMotionArgs* args) override;
+    void notifySwitch(const NotifySwitchArgs* args) override;
+    void notifySensor(const NotifySensorArgs* args) override;
+    void notifyVibratorState(const NotifyVibratorStateArgs* args) override;
+    void notifyDeviceReset(const NotifyDeviceResetArgs* args) override;
+    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
+
+    void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
+    void dump(std::string& dump) override;
+    void monitor() override;
+
+    ~UnwantedInteractionBlocker();
+
+private:
+    // The next stage to pass input events to
+    InputListenerInterface& mListener;
+    const bool mEnablePalmRejection;
+
+    // Detect and reject unwanted palms on screen
+    // Use a separate palm rejector for every touch device.
+    std::map<int32_t /*deviceId*/, PalmRejector> mPalmRejectors;
+};
+
+class SlotState {
+public:
+    /**
+     * Update the state using the new information provided in the NotifyMotionArgs
+     */
+    void update(const NotifyMotionArgs& args);
+    std::optional<size_t> getSlotForPointerId(int32_t pointerId) const;
+    std::string dump() const;
+
+private:
+    // Process a pointer with the provided action, and return the slot associated with it
+    void processPointerId(int32_t pointerId, int32_t action);
+    // The map from tracking id to slot state. Since the PalmRejectionFilter works close to the
+    // evdev level, the only way to tell it about UP or CANCEL events is by sending tracking id = -1
+    // to the appropriate touch slot. So we need to reconstruct the original slot.
+    // The two collections below must always be in-sync.
+    // Use std::map instead of std::unordered_map because we rely on these collections being
+    // ordered. It also has better space efficiency than unordered_map because we only have a few
+    // pointers most of the time.
+    std::map<int32_t /*pointerId*/, size_t /*slot*/> mSlotsByPointerId;
+    std::map<size_t /*slot*/, int32_t /*pointerId */> mPointerIdsBySlot;
+
+    size_t findUnusedSlot() const;
+};
+
+/**
+ * Convert an Android event to a linux-like 'InProgressTouchEvdev'. The provided SlotState's
+ * are used to figure out which slot does each pointer belong to.
+ */
+std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
+                                                   const AndroidPalmFilterDeviceInfo& deviceInfo,
+                                                   const SlotState& oldSlotState,
+                                                   const SlotState& newSlotState);
+
+class PalmRejector {
+public:
+    explicit PalmRejector(const AndroidPalmFilterDeviceInfo& info,
+                          std::unique_ptr<::ui::PalmDetectionFilter> filter = nullptr);
+    std::vector<NotifyMotionArgs> processMotion(const NotifyMotionArgs& args);
+
+    // Get the device info of this device, for comparison purposes
+    const AndroidPalmFilterDeviceInfo& getPalmFilterDeviceInfo();
+    std::string dump() const;
+
+private:
+    PalmRejector(const PalmRejector&) = delete;
+    PalmRejector& operator=(const PalmRejector&) = delete;
+
+    std::unique_ptr<::ui::SharedPalmDetectionFilterState> mSharedPalmState;
+    AndroidPalmFilterDeviceInfo mDeviceInfo;
+    std::unique_ptr<::ui::PalmDetectionFilter> mPalmDetectionFilter;
+    std::set<int32_t> mSuppressedPointerIds;
+
+    // Used to help convert an Android touch stream to Linux input stream.
+    SlotState mSlotState;
+};
+
+} // namespace android
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 441a1de..aa3643c 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -120,7 +120,8 @@
 
 // Amount of time to allow for an event to be dispatched (measured since its eventTime)
 // before considering it stale and dropping it.
-constexpr nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL; // 10sec
+const nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL // 10sec
+        * HwTimeoutMultiplier();
 
 // Log a warning when an event takes longer than this to process, even if an ANR does not occur.
 constexpr nsecs_t SLOW_EVENT_PROCESSING_WARNING_TIMEOUT = 2000 * 1000000LL; // 2sec
@@ -215,7 +216,7 @@
         return false;
     }
     if (pointerCount < 1 || pointerCount > MAX_POINTERS) {
-        ALOGE("Motion event has invalid pointer count %zu; value must be between 1 and %d.",
+        ALOGE("Motion event has invalid pointer count %zu; value must be between 1 and %zu.",
               pointerCount, MAX_POINTERS);
         return false;
     }
@@ -547,6 +548,7 @@
         mAppSwitchSawKeyDown(false),
         mAppSwitchDueTime(LONG_LONG_MAX),
         mNextUnblockedEvent(nullptr),
+        mMonitorDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT),
         mDispatchEnabled(false),
         mDispatchFrozen(false),
         mInputFilterEnabled(false),
@@ -706,8 +708,13 @@
     return LONG_LONG_MIN;
 }
 
-std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked(const sp<IBinder>& token) {
-    sp<WindowInfoHandle> window = getWindowHandleLocked(token);
+std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked(
+        const sp<Connection>& connection) {
+    if (connection->monitor) {
+        return mMonitorDispatchingTimeout;
+    }
+    const sp<WindowInfoHandle> window =
+            getWindowHandleLocked(connection->inputChannel->getConnectionToken());
     if (window != nullptr) {
         return window->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
     }
@@ -3204,8 +3211,7 @@
     while (connection->status == Connection::Status::NORMAL && !connection->outboundQueue.empty()) {
         DispatchEntry* dispatchEntry = connection->outboundQueue.front();
         dispatchEntry->deliveryTime = currentTime;
-        const std::chrono::nanoseconds timeout =
-                getDispatchingTimeoutLocked(connection->inputChannel->getConnectionToken());
+        const std::chrono::nanoseconds timeout = getDispatchingTimeoutLocked(connection);
         dispatchEntry->timeoutTime = currentTime + timeout.count();
 
         // Publish the event.
@@ -6399,4 +6405,9 @@
     mLooper->wake();
 }
 
+void InputDispatcher::setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout) {
+    std::scoped_lock _l(mLock);
+    mMonitorDispatchingTimeout = timeout;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 7564839..bff6cac 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -148,6 +148,9 @@
 
     void cancelCurrentTouch() override;
 
+    // Public to allow tests to verify that a Monitor can get ANR.
+    void setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout);
+
 private:
     enum class DropReason {
         NOT_DROPPED,
@@ -324,8 +327,12 @@
     bool runCommandsLockedInterruptable() REQUIRES(mLock);
     void postCommandLocked(Command&& command) REQUIRES(mLock);
 
+    // The dispatching timeout to use for Monitors.
+    std::chrono::nanoseconds mMonitorDispatchingTimeout GUARDED_BY(mLock);
+
     nsecs_t processAnrsLocked() REQUIRES(mLock);
-    std::chrono::nanoseconds getDispatchingTimeoutLocked(const sp<IBinder>& token) REQUIRES(mLock);
+    std::chrono::nanoseconds getDispatchingTimeoutLocked(const sp<Connection>& connection)
+            REQUIRES(mLock);
 
     // Input filter processing.
     bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) REQUIRES(mLock);
diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h
index db63104..dff5894 100644
--- a/services/inputflinger/include/InputListener.h
+++ b/services/inputflinger/include/InputListener.h
@@ -142,6 +142,8 @@
     bool operator==(const NotifyMotionArgs& rhs) const;
 
     void notify(InputListenerInterface& listener) const override;
+
+    std::string dump() const;
 };
 
 /* Describes a sensor event. */
diff --git a/services/inputflinger/include/UnwantedInteractionBlockerInterface.h b/services/inputflinger/include/UnwantedInteractionBlockerInterface.h
new file mode 100644
index 0000000..2327266
--- /dev/null
+++ b/services/inputflinger/include/UnwantedInteractionBlockerInterface.h
@@ -0,0 +1,53 @@
+/*
+ * 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 "InputListener.h"
+
+namespace android {
+
+/**
+ * Base interface for an InputListener stage.
+ * Blocks unintentional input events. Not thread safe. Must be called from the same
+ * thread. All work is performed on the calling threads.
+ */
+class UnwantedInteractionBlockerInterface : public InputListenerInterface {
+public:
+    /* Notifies the input reader policy that some input devices have changed
+     * and provides information about all current input devices.
+     * Important! This call should happen on the same thread as the calls to the
+     * InputListenerInterface methods.
+     * That is, same thread should call 'notifyMotion' and 'notifyInputDevicesChanged' and
+     * 'notifyDeviceReset'. If this architecture changes, we will need to make the implementation
+     * of this interface thread-safe.
+     */
+    virtual void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) = 0;
+
+    /**
+     * Dump the state of the interaction blocker.
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual void dump(std::string& dump) = 0;
+
+    /* Called by the heatbeat to ensures that the dispatcher has not deadlocked. */
+    virtual void monitor() = 0;
+
+    UnwantedInteractionBlockerInterface() {}
+    ~UnwantedInteractionBlockerInterface() override {}
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 3e95fa9..a050963 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -309,15 +309,15 @@
                 const auto& displayPort = ports.find(inputPort);
                 if (displayPort != ports.end()) {
                     mAssociatedDisplayPort = std::make_optional(displayPort->second);
+                } else {
+                    const std::unordered_map<std::string, std::string>& displayUniqueIds =
+                            config->uniqueIdAssociations;
+                    const auto& displayUniqueId = displayUniqueIds.find(inputPort);
+                    if (displayUniqueId != displayUniqueIds.end()) {
+                        mAssociatedDisplayUniqueId = displayUniqueId->second;
+                    }
                 }
             }
-            const std::string& inputDeviceName = mIdentifier.name;
-            const std::unordered_map<std::string, std::string>& names =
-                    config->uniqueIdAssociations;
-            const auto& displayUniqueId = names.find(inputDeviceName);
-            if (displayUniqueId != names.end()) {
-                mAssociatedDisplayUniqueId = displayUniqueId->second;
-            }
 
             // If the device was explicitly disabled by the user, it would be present in the
             // "disabledDevices" list. If it is associated with a specific display, and it was not
@@ -338,7 +338,7 @@
                 if (!mAssociatedViewport) {
                     ALOGW("Input device %s should be associated with display %s but the "
                           "corresponding viewport cannot be found",
-                          inputDeviceName.c_str(), mAssociatedDisplayUniqueId->c_str());
+                          getName().c_str(), mAssociatedDisplayUniqueId->c_str());
                     enabled = false;
                 }
             }
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 11c074a..694daa9 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -61,6 +61,9 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mAssociatedDisplayPort;
     }
+    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
+        return mAssociatedDisplayUniqueId;
+    }
     inline std::optional<DisplayViewport> getAssociatedViewport() const {
         return mAssociatedViewport;
     }
@@ -386,6 +389,9 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mDevice.getAssociatedDisplayPort();
     }
+    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
+        return mDevice.getAssociatedDisplayUniqueId();
+    }
     inline std::optional<DisplayViewport> getAssociatedViewport() const {
         return mDevice.getAssociatedViewport();
     }
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 4bd1cd8..ff3a592 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -282,7 +282,7 @@
 
         if (outCount >= MAX_POINTERS) {
             if (DEBUG_POINTERS) {
-                ALOGD("MultiTouch device %s emitted more than maximum of %d pointers; "
+                ALOGD("MultiTouch device %s emitted more than maximum of %zu pointers; "
                       "ignoring the rest.",
                       getDeviceName().c_str(), MAX_POINTERS);
             }
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 4772beb..f729ba9 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -565,6 +565,12 @@
             return getDeviceContext().getAssociatedViewport();
         }
 
+        const std::optional<std::string> associatedDisplayUniqueId =
+                getDeviceContext().getAssociatedDisplayUniqueId();
+        if (associatedDisplayUniqueId) {
+            return getDeviceContext().getAssociatedViewport();
+        }
+
         if (mDeviceMode == DeviceMode::POINTER) {
             std::optional<DisplayViewport> viewport =
                     mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId);
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index e686924..9d200bd 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -49,6 +49,7 @@
         "LatencyTracker_test.cpp",
         "TestInputListener.cpp",
         "UinputDevice.cpp",
+        "UnwantedInteractionBlocker_test.cpp",
     ],
     aidl: {
         include_dirs: [
@@ -57,7 +58,7 @@
         ],
     },
     static_libs: [
-        "libc++fs"
+        "libc++fs",
     ],
     require_root: true,
     test_suites: ["device-tests"],
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 0814bc2..f97a9ec 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -17,6 +17,7 @@
 #include "../dispatcher/InputDispatcher.h"
 
 #include <android-base/properties.h>
+#include <android-base/silent_death_test.h>
 #include <android-base/stringprintf.h>
 #include <android-base/thread_annotations.h>
 #include <binder/Binder.h>
@@ -558,7 +559,7 @@
     MotionEvent event;
     PointerProperties pointerProperties[MAX_POINTERS + 1];
     PointerCoords pointerCoords[MAX_POINTERS + 1];
-    for (int i = 0; i <= MAX_POINTERS; i++) {
+    for (size_t i = 0; i <= MAX_POINTERS; i++) {
         pointerProperties[i].clear();
         pointerProperties[i].id = i;
         pointerCoords[i].clear();
@@ -2766,9 +2767,10 @@
 class FakeMonitorReceiver {
 public:
     FakeMonitorReceiver(const std::unique_ptr<InputDispatcher>& dispatcher, const std::string name,
-                        int32_t displayId, bool isGestureMonitor = false) {
+                        int32_t displayId) {
         base::Result<std::unique_ptr<InputChannel>> channel =
-                dispatcher->createInputMonitor(displayId, isGestureMonitor, name, MONITOR_PID);
+                dispatcher->createInputMonitor(displayId, false /*isGestureMonitor*/, name,
+                                               MONITOR_PID);
         mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
     }
 
@@ -2829,6 +2831,8 @@
     std::unique_ptr<FakeInputReceiver> mInputReceiver;
 };
 
+using InputDispatcherMonitorTest = InputDispatcherTest;
+
 /**
  * Two entities that receive touch: A window, and a global monitor.
  * The touch goes to the window, and then the window disappears.
@@ -2837,14 +2841,12 @@
  * 1. foregroundWindow
  * 2. monitor <-- global monitor (doesn't observe z order, receives all events)
  */
-TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_GlobalMonitorTouchIsCanceled) {
+TEST_F(InputDispatcherMonitorTest, MonitorTouchIsCanceledWhenForegroundWindowDisappears) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
 
-    FakeMonitorReceiver monitor =
-            FakeMonitorReceiver(mDispatcher, "GlobalMonitor", ADISPLAY_ID_DEFAULT,
-                                false /*isGestureMonitor*/);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -2880,15 +2882,13 @@
     monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
 }
 
-// Tests for gesture monitors
-TEST_F(InputDispatcherTest, GestureMonitor_ReceivesMotionEvents) {
+TEST_F(InputDispatcherMonitorTest, ReceivesMotionEvents) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
@@ -2897,71 +2897,34 @@
     monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
 }
 
-TEST_F(InputDispatcherTest, GestureMonitor_DoesNotReceiveKeyEvents) {
-    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+TEST_F(InputDispatcherMonitorTest, MonitorCannotPilferPointers) {
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
-    window->setFocusable(true);
-
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-    setFocusedWindow(window);
-
-    window->consumeFocusEvent(true);
-
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
-
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT))
-            << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
-    monitor.assertNoEvents();
-}
-
-TEST_F(InputDispatcherTest, GestureMonitor_CanPilferAfterWindowIsRemovedMidStream) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
-
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
 
-    window->releaseChannel();
-
-    mDispatcher->pilferPointers(monitor.getToken());
+    // Pilfer pointers from the monitor.
+    // This should not do anything and the window should continue to receive events.
+    EXPECT_NE(OK, mDispatcher->pilferPointers(monitor.getToken()));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+
+    monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionMove(ADISPLAY_ID_DEFAULT);
 }
 
-TEST_F(InputDispatcherTest, UnresponsiveGestureMonitor_GetsAnr) {
-    FakeMonitorReceiver monitor =
-            FakeMonitorReceiver(mDispatcher, "Gesture monitor", ADISPLAY_ID_DEFAULT,
-                                true /*isGestureMonitor*/);
-
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
-    std::optional<uint32_t> consumeSeq = monitor.receiveEvent();
-    ASSERT_TRUE(consumeSeq);
-
-    mFakePolicy->assertNotifyMonitorUnresponsiveWasCalled(DISPATCHING_TIMEOUT);
-    monitor.finishEvent(*consumeSeq);
-    ASSERT_TRUE(mDispatcher->waitForIdle());
-    mFakePolicy->assertNotifyMonitorResponsiveWasCalled();
-}
-
-// Tests for gesture monitors
-TEST_F(InputDispatcherTest, GestureMonitor_NoWindowTransform) {
+TEST_F(InputDispatcherMonitorTest, NoWindowTransform) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
@@ -2969,8 +2932,7 @@
     window->setWindowOffset(20, 40);
     window->setWindowTransform(0, 1, -1, 0);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
@@ -2981,15 +2943,14 @@
     ASSERT_EQ(ui::Transform(), event->getTransform());
 }
 
-TEST_F(InputDispatcherTest, GestureMonitor_NoWindow) {
+TEST_F(InputDispatcherMonitorTest, InjectionFailsWithNoWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+            << "Injection should fail if there is a monitor, but no touchable window";
+    monitor.assertNoEvents();
 }
 
 TEST_F(InputDispatcherTest, TestMoveEvent) {
@@ -3018,91 +2979,6 @@
                          0 /*expectedFlags*/);
 }
 
-TEST_F(InputDispatcherTest, GestureMonitor_SplitIfNoWindowTouched) {
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
-
-    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    // Create a non touch modal window that supports split touch
-    sp<FakeWindowHandle> window =
-            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
-    window->setFrame(Rect(0, 0, 100, 100));
-    window->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-
-    // First finger down, no window touched.
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    window->assertNoEvents();
-
-    // Second finger down on window, the window should receive touch down.
-    const MotionEvent secondFingerDownEvent =
-            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
-                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(100)
-                                     .y(200))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .build();
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
-                                InputEventInjectionSync::WAIT_FOR_RESULT))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitor.consumeMotionPointerDown(1 /* pointerIndex */);
-}
-
-TEST_F(InputDispatcherTest, GestureMonitor_NoSplitAfterPilfer) {
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
-
-    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    // Create a non touch modal window that supports split touch
-    sp<FakeWindowHandle> window =
-            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
-    window->setFrame(Rect(0, 0, 100, 100));
-    window->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-
-    // First finger down, no window touched.
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    window->assertNoEvents();
-
-    // Gesture monitor pilfer the pointers.
-    mDispatcher->pilferPointers(monitor.getToken());
-
-    // Second finger down on window, the window should not receive touch down.
-    const MotionEvent secondFingerDownEvent =
-            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
-                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(100)
-                                     .y(200))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .build();
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
-                                InputEventInjectionSync::WAIT_FOR_RESULT))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-
-    window->assertNoEvents();
-    monitor.consumeMotionPointerDown(1 /* pointerIndex */);
-}
-
 /**
  * Dispatcher has touch mode enabled by default. Typically, the policy overrides that value to
  * the device default right away. In the test scenario, we check both the default value,
@@ -4442,6 +4318,17 @@
                   injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                  WINDOW_LOCATION));
     }
+
+    sp<FakeWindowHandle> addSpyWindow() {
+        sp<FakeWindowHandle> spy =
+                new FakeWindowHandle(mApplication, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+        spy->setTrustedOverlay(true);
+        spy->setFocusable(false);
+        spy->setInputFeatures(WindowInfo::Feature::SPY);
+        spy->setDispatchingTimeout(30ms);
+        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, mWindow}}});
+        return spy;
+    }
 };
 
 // Send a tap and respond, which should not cause an ANR.
@@ -4617,12 +4504,31 @@
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
 }
 
-// If an app is not responding to a key event, gesture monitors should continue to receive
+// A spy window can receive an ANR
+TEST_F(InputDispatcherSingleWindowAnr, SpyWindowAnr) {
+    sp<FakeWindowHandle> spy = addSpyWindow();
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               WINDOW_LOCATION));
+    mWindow->consumeMotionDown();
+
+    std::optional<uint32_t> sequenceNum = spy->receiveEvent(); // ACTION_DOWN
+    ASSERT_TRUE(sequenceNum);
+    const std::chrono::duration timeout = spy->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, spy->getToken());
+
+    spy->finishEvent(*sequenceNum);
+    spy->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT,
+                      0 /*flags*/);
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(spy->getToken());
+}
+
+// If an app is not responding to a key event, spy windows should continue to receive
 // new motion events
-TEST_F(InputDispatcherSingleWindowAnr, GestureMonitors_ReceiveEventsDuringAppAnrOnKey) {
-    FakeMonitorReceiver monitor =
-            FakeMonitorReceiver(mDispatcher, "Gesture monitor", ADISPLAY_ID_DEFAULT,
-                                true /*isGestureMonitor*/);
+TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnKey) {
+    sp<FakeWindowHandle> spy = addSpyWindow();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT));
@@ -4633,44 +4539,64 @@
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
 
-    // New tap will go to the gesture monitor, but not to the window
+    // New tap will go to the spy window, but not to the window
     tapOnWindow();
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
 
     mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT); // still the previous motion
     mDispatcher->waitForIdle();
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
     mWindow->assertNoEvents();
-    monitor.assertNoEvents();
+    spy->assertNoEvents();
 }
 
-// If an app is not responding to a motion event, gesture monitors should continue to receive
+// If an app is not responding to a motion event, spy windows should continue to receive
 // new motion events
-TEST_F(InputDispatcherSingleWindowAnr, GestureMonitors_ReceiveEventsDuringAppAnrOnMotion) {
-    FakeMonitorReceiver monitor =
-            FakeMonitorReceiver(mDispatcher, "Gesture monitor", ADISPLAY_ID_DEFAULT,
-                                true /*isGestureMonitor*/);
+TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnMotion) {
+    sp<FakeWindowHandle> spy = addSpyWindow();
 
     tapOnWindow();
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
 
     mWindow->consumeMotionDown();
     // Stuck on the ACTION_UP
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
 
-    // New tap will go to the gesture monitor, but not to the window
+    // New tap will go to the spy window, but not to the window
     tapOnWindow();
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
 
     mWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); // still the previous motion
     mDispatcher->waitForIdle();
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
     mWindow->assertNoEvents();
-    monitor.assertNoEvents();
+    spy->assertNoEvents();
+}
+
+TEST_F(InputDispatcherSingleWindowAnr, UnresponsiveMonitorAnr) {
+    mDispatcher->setMonitorDispatchingTimeoutForTest(30ms);
+
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               WINDOW_LOCATION));
+
+    mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    const std::optional<uint32_t> consumeSeq = monitor.receiveEvent();
+    ASSERT_TRUE(consumeSeq);
+
+    mFakePolicy->assertNotifyMonitorUnresponsiveWasCalled(30ms);
+
+    monitor.finishEvent(*consumeSeq);
+    monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
+
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertNotifyMonitorResponsiveWasCalled();
 }
 
 // If a window is unresponsive, then you get anr. if the window later catches up and starts to
@@ -6338,6 +6264,7 @@
         sp<FakeWindowHandle> window =
                 new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
         window->addFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
+        window->setFocusable(true);
         return window;
     }
 
@@ -6345,10 +6272,13 @@
     int mSpyCount{0};
 };
 
+using InputDispatcherSpyWindowDeathTest = InputDispatcherSpyWindowTest;
 /**
  * Adding a spy window that is not a trusted overlay causes Dispatcher to abort.
  */
-TEST_F(InputDispatcherSpyWindowTest, UntrustedSpy_AbortsDispatcher) {
+TEST_F(InputDispatcherSpyWindowDeathTest, UntrustedSpy_AbortsDispatcher) {
+    ScopedSilentDeath _silentDeath;
+
     auto spy = createSpy(WindowInfo::Flag::NOT_TOUCH_MODAL);
     spy->setTrustedOverlay(false);
     ASSERT_DEATH(mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy}}}),
@@ -6532,7 +6462,7 @@
     spy2->consumeMotionDown();
 
     // Pilfer pointers from the second spy window.
-    mDispatcher->pilferPointers(spy2->getToken());
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spy2->getToken()));
     spy2->assertNoEvents();
     spy1->consumeMotionCancel();
     window->consumeMotionCancel();
@@ -6548,6 +6478,80 @@
 }
 
 /**
+ * A spy window can pilfer pointers for a gesture even after the foreground window has been removed
+ * in the middle of the gesture.
+ */
+TEST_F(InputDispatcherSpyWindowTest, CanPilferAfterWindowIsRemovedMidStream) {
+    auto window = createForeground();
+    auto spy = createSpy(WindowInfo::Flag::NOT_TOUCH_MODAL);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    window->releaseChannel();
+
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+}
+
+/**
+ * After a spy window pilfers pointers, new pointers that go down should not go to any foreground
+ * windows.
+ */
+TEST_F(InputDispatcherSpyWindowTest, NoSplitAfterPilfer) {
+    // Create a touch modal spy that spies on the entire display.
+    auto spy = createSpy(static_cast<WindowInfo::Flag>(0));
+
+    // Create a non touch modal window that supports split touch.
+    auto window = createForeground();
+    window->setFrame(Rect(0, 0, 100, 100));
+    window->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+    // First finger down, no window touched.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->assertNoEvents();
+
+    // Spy window pilfer the pointers.
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+
+    // Second finger down on window, the window should not receive touch down.
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                               AINPUT_SOURCE_TOUCHSCREEN)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(100)
+                                     .y(200))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    window->assertNoEvents();
+    // Since we no longer allow splitting, the spy will not receive new pointers.
+    // TODO(b/217376964): Add a way for the pilfering window to receive the rest of the gesture.
+    spy->consumeMotionMove();
+}
+
+/**
  * Even when a spy window spans over multiple foreground windows, the spy should receive all
  * pointers that are down within its bounds.
  */
@@ -6619,6 +6623,76 @@
     spyRight->consumeMotionDown();
 }
 
+/**
+ * The spy window should not be able to affect whether or not touches are split. Only the foreground
+ * windows should be allowed to control split touch.
+ */
+TEST_F(InputDispatcherSpyWindowTest, SplitIfNoForegroundWindowTouched) {
+    // Create a touch modal spy that spies on the entire display.
+    // This spy window does not set the SPLIT_TOUCH flag. However, we still expect to split touches
+    // because a foreground window has not disabled splitting.
+    auto spy = createSpy(static_cast<WindowInfo::Flag>(0));
+
+    // Create a non touch modal window that supports split touch.
+    auto window = createForeground();
+    window->setFrame(Rect(0, 0, 100, 100));
+    window->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+    // First finger down, no window touched.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->assertNoEvents();
+
+    // Second finger down on window, the window should receive touch down.
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                               AINPUT_SOURCE_TOUCHSCREEN)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(100)
+                                     .y(200))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionPointerDown(1 /* pointerIndex */);
+}
+
+/**
+ * A spy window will usually be implemented as an un-focusable window. Verify that these windows
+ * do not receive key events.
+ */
+TEST_F(InputDispatcherSpyWindowTest, UnfocusableSpyDoesNotReceiveKeyEvents) {
+    auto spy = createSpy(static_cast<WindowInfo::Flag>(0));
+    spy->setFocusable(false);
+
+    auto window = createForeground();
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+            << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeKeyDown(ADISPLAY_ID_NONE);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+            << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeKeyUp(ADISPLAY_ID_NONE);
+
+    spy->assertNoEvents();
+}
+
 class InputDispatcherStylusInterceptorTest : public InputDispatcherTest {
 public:
     std::pair<sp<FakeWindowHandle>, sp<FakeWindowHandle>> setupStylusOverlayScenario() {
@@ -6665,7 +6739,11 @@
     }
 };
 
-TEST_F(InputDispatcherStylusInterceptorTest, UntrustedOverlay_AbortsDispatcher) {
+using InputDispatcherStylusInterceptorDeathTest = InputDispatcherStylusInterceptorTest;
+
+TEST_F(InputDispatcherStylusInterceptorDeathTest, UntrustedOverlay_AbortsDispatcher) {
+    ScopedSilentDeath _silentDeath;
+
     auto [overlay, window] = setupStylusOverlayScenario();
     overlay->setTrustedOverlay(false);
     // Configuring an untrusted overlay as a stylus interceptor should cause Dispatcher to abort.
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index e2596f0..54cf15d 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2862,7 +2862,7 @@
     // 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_NAME, DISPLAY_UNIQUE_ID);
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
     mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                        InputReaderConfiguration::CHANGE_DISPLAY_INFO);
     ASSERT_FALSE(mDevice->isEnabled());
@@ -2887,6 +2887,21 @@
     ASSERT_FALSE(mDevice->isEnabled());
 }
 
+TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) {
+    mFakePolicy->clearViewports();
+    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,
+                                    NO_PORT, ViewportType::INTERNAL);
+    mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                       InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId());
+}
+
 // --- InputMapperTest ---
 
 class InputMapperTest : public testing::Test {
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
new file mode 100644
index 0000000..a3220cc
--- /dev/null
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -0,0 +1,938 @@
+/*
+ * 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 "../UnwantedInteractionBlocker.h"
+#include <android-base/silent_death_test.h>
+#include <gtest/gtest.h>
+#include <gui/constants.h>
+#include <linux/input.h>
+
+#include "TestInputListener.h"
+
+namespace android {
+
+constexpr int32_t DEVICE_ID = 3;
+constexpr int32_t X_RESOLUTION = 11;
+constexpr int32_t Y_RESOLUTION = 11;
+constexpr int32_t MAJOR_RESOLUTION = 1;
+
+constexpr int POINTER_0_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int POINTER_2_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int POINTER_0_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int POINTER_1_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int POINTER_2_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int DOWN = AMOTION_EVENT_ACTION_DOWN;
+constexpr int MOVE = AMOTION_EVENT_ACTION_MOVE;
+constexpr int UP = AMOTION_EVENT_ACTION_UP;
+constexpr int CANCEL = AMOTION_EVENT_ACTION_CANCEL;
+
+struct PointerData {
+    float x;
+    float y;
+    float major;
+};
+
+static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, int32_t action,
+                                           const std::vector<PointerData>& points) {
+    size_t pointerCount = points.size();
+    if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_UP) {
+        EXPECT_EQ(1U, pointerCount) << "Actions DOWN and UP can only contain a single pointer";
+    }
+
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties[i].clear();
+        pointerProperties[i].id = i;
+        pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+
+        pointerCoords[i].clear();
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, points[i].x);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, points[i].y);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, points[i].major);
+    }
+
+    // Define a valid motion event.
+    NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, DEVICE_ID,
+                          AINPUT_SOURCE_TOUCHSCREEN, 0 /*displayId*/, POLICY_FLAG_PASS_TO_USER,
+                          action, /* actionButton */ 0,
+                          /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
+                          MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
+                          pointerProperties, pointerCoords, /* xPrecision */ 0, /* yPrecision */ 0,
+                          AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                          AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /* videoFrames */ {});
+
+    return args;
+}
+
+static InputDeviceInfo generateTestDeviceInfo() {
+    InputDeviceIdentifier identifier;
+
+    auto info = InputDeviceInfo();
+    info.initialize(DEVICE_ID, /*generation*/ 1, /*controllerNumber*/ 1, identifier, "alias",
+                    /*isExternal*/ false, /*hasMic*/ false);
+    info.addSource(AINPUT_SOURCE_TOUCHSCREEN);
+    info.addMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN, 0, 1599, /*flat*/ 0,
+                        /*fuzz*/ 0, X_RESOLUTION);
+    info.addMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN, 0, 2559, /*flat*/ 0,
+                        /*fuzz*/ 0, Y_RESOLUTION);
+    info.addMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN, 0, 255,
+                        /*flat*/ 0, /*fuzz*/ 0, MAJOR_RESOLUTION);
+
+    return info;
+}
+
+static AndroidPalmFilterDeviceInfo generatePalmFilterDeviceInfo() {
+    InputDeviceInfo androidInfo = generateTestDeviceInfo();
+    std::optional<AndroidPalmFilterDeviceInfo> info = createPalmFilterDeviceInfo(androidInfo);
+    if (!info) {
+        ADD_FAILURE() << "Could not convert android device info to ::ui version";
+        return {};
+    }
+    return *info;
+}
+
+TEST(DeviceInfoConversionTest, TabletDeviceTest) {
+    AndroidPalmFilterDeviceInfo info = generatePalmFilterDeviceInfo();
+    ASSERT_EQ(X_RESOLUTION, info.x_res);
+    ASSERT_EQ(Y_RESOLUTION, info.y_res);
+    ASSERT_EQ(MAJOR_RESOLUTION, info.touch_major_res);
+    ASSERT_EQ(1599, info.max_x);
+    ASSERT_EQ(2559, info.max_y);
+}
+
+static void assertArgs(const NotifyMotionArgs& args, int32_t action,
+                       const std::vector<std::pair<int32_t /*pointerId*/, PointerData>>& pointers) {
+    ASSERT_EQ(action, args.action);
+    ASSERT_EQ(pointers.size(), args.pointerCount);
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        const auto& [pointerId, pointerData] = pointers[i];
+        ASSERT_EQ(pointerId, args.pointerProperties[i].id);
+        ASSERT_EQ(pointerData.x, args.pointerCoords[i].getX());
+        ASSERT_EQ(pointerData.y, args.pointerCoords[i].getY());
+        ASSERT_EQ(pointerData.major,
+                  args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR));
+    }
+}
+
+TEST(RemovePointerIdsTest, RemoveOnePointer) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0,
+                                               AMOTION_EVENT_ACTION_MOVE, {{1, 2, 3}, {4, 5, 6}});
+
+    NotifyMotionArgs pointer1Only = removePointerIds(args, {0});
+    assertArgs(pointer1Only, AMOTION_EVENT_ACTION_MOVE, {{1, {4, 5, 6}}});
+
+    NotifyMotionArgs pointer0Only = removePointerIds(args, {1});
+    assertArgs(pointer0Only, AMOTION_EVENT_ACTION_MOVE, {{0, {1, 2, 3}}});
+}
+
+/**
+ * Remove 2 out of 3 pointers during a MOVE event.
+ */
+TEST(RemovePointerIdsTest, RemoveTwoPointers) {
+    NotifyMotionArgs args =
+            generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, AMOTION_EVENT_ACTION_MOVE,
+                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+
+    NotifyMotionArgs pointer1Only = removePointerIds(args, {0, 2});
+    assertArgs(pointer1Only, AMOTION_EVENT_ACTION_MOVE, {{1, {4, 5, 6}}});
+}
+
+/**
+ * Remove an active pointer during a POINTER_DOWN event, and also remove a non-active
+ * pointer during a POINTER_DOWN event.
+ */
+TEST(RemovePointerIdsTest, ActionPointerDown) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_DOWN,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+
+    NotifyMotionArgs pointers0And2 = removePointerIds(args, {1});
+    assertArgs(pointers0And2, ACTION_UNKNOWN, {{0, {1, 2, 3}}, {2, {7, 8, 9}}});
+
+    NotifyMotionArgs pointers1And2 = removePointerIds(args, {0});
+    assertArgs(pointers1And2, POINTER_0_DOWN, {{1, {4, 5, 6}}, {2, {7, 8, 9}}});
+}
+
+/**
+ * Remove all pointers during a MOVE event.
+ */
+TEST(RemovePointerIdsTest, RemoveAllPointersDuringMove) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0,
+                                               AMOTION_EVENT_ACTION_MOVE, {{1, 2, 3}, {4, 5, 6}});
+
+    NotifyMotionArgs noPointers = removePointerIds(args, {0, 1});
+    ASSERT_EQ(0u, noPointers.pointerCount);
+}
+
+/**
+ * If we have ACTION_POINTER_DOWN, and we remove all pointers except for the active pointer,
+ * then we should just have ACTION_DOWN. Likewise, a POINTER_UP event should become an UP event.
+ */
+TEST(RemovePointerIdsTest, PointerDownBecomesDown) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_DOWN,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+
+    NotifyMotionArgs pointer1 = removePointerIds(args, {0, 2});
+    assertArgs(pointer1, DOWN, {{1, {4, 5, 6}}});
+
+    args.action = POINTER_1_UP;
+    pointer1 = removePointerIds(args, {0, 2});
+    assertArgs(pointer1, UP, {{1, {4, 5, 6}}});
+}
+
+/**
+ * If a pointer that is now going down is canceled, then we can just drop the POINTER_DOWN event.
+ */
+TEST(CancelSuppressedPointersTest, CanceledPointerDownIsDropped) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_DOWN,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
+                                     /*newSuppressedPointerIds*/ {1});
+    ASSERT_TRUE(result.empty());
+}
+
+/**
+ * If a pointer is already suppressed, the POINTER_UP event for this pointer should be dropped
+ */
+TEST(CancelSuppressedPointersTest, SuppressedPointerUpIsDropped) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_UP,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {1},
+                                     /*newSuppressedPointerIds*/ {1});
+    ASSERT_TRUE(result.empty());
+}
+
+/**
+ * If a pointer is already suppressed, it should be removed from a MOVE event.
+ */
+TEST(CancelSuppressedPointersTest, SuppressedPointerIsRemovedDuringMove) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {1},
+                                     /*newSuppressedPointerIds*/ {1});
+    ASSERT_EQ(1u, result.size());
+    assertArgs(result[0], MOVE, {{0, {1, 2, 3}}, {2, {7, 8, 9}}});
+}
+
+/**
+ * If a pointer just got canceled during a MOVE event, we should see two events:
+ * 1) ACTION_POINTER_UP with FLAG_CANCELED so that this pointer is lifted
+ * 2) A MOVE event without this pointer
+ */
+TEST(CancelSuppressedPointersTest, NewlySuppressedPointerIsCanceled) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
+                                     /*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);
+    assertArgs(result[1], MOVE, {{0, {1, 2, 3}}, {2, {7, 8, 9}}});
+}
+
+/**
+ * If we have a single pointer that gets canceled during a MOVE, the entire gesture
+ * should be canceled with ACTION_CANCEL.
+ */
+TEST(CancelSuppressedPointersTest, SingleSuppressedPointerIsCanceled) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE, {{1, 2, 3}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
+                                     /*newSuppressedPointerIds*/ {0});
+    ASSERT_EQ(1u, result.size());
+    assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}});
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+}
+
+/**
+ * If one of 3 pointers gets canceled during a POINTER_UP event, we should proceed with POINTER_UP,
+ * but this event should also have FLAG_CANCELED to indicate that this pointer was unintentional.
+ */
+TEST(CancelSuppressedPointersTest, SuppressedPointer1GoingUpIsCanceled) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_UP,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
+                                     /*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);
+}
+
+/**
+ * Same test as above, but we change the pointer's index to 0 instead of 1. This helps detect
+ * errors with handling pointer index inside the action.
+ */
+TEST(CancelSuppressedPointersTest, SuppressedPointer0GoingUpIsCanceled) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_0_UP,
+                                               {{1, 2, 3}, {4, 5, 6}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
+                                     /*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);
+}
+
+/**
+ * If two pointers are canceled simultaneously during MOVE, we should see a single ACTION_CANCEL
+ * event. This event would cancel the entire gesture.
+ */
+TEST(CancelSuppressedPointersTest, TwoNewlySuppressedPointersAreBothCanceled) {
+    NotifyMotionArgs args =
+            generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE, {{1, 2, 3}, {4, 5, 6}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
+                                     /*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);
+}
+
+/**
+ * Similar test to above. During a POINTER_UP event, both pointers are detected as 'palm' and
+ * therefore should be removed. In this case, we should send a single ACTION_CANCEL that
+ * would undo the entire gesture.
+ */
+TEST(CancelSuppressedPointersTest, TwoPointersAreCanceledDuringPointerUp) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_UP,
+                                               {{1, 2, 3}, {4, 5, 6}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {1},
+                                     /*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);
+}
+
+/**
+ * When all pointers have been removed from the touch stream, and we have a new POINTER_DOWN,
+ * this should become a regular DOWN event because it's the only pointer that will be valid now.
+ */
+TEST(CancelSuppressedPointersTest, NewPointerDownBecomesDown) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_2_DOWN,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {0, 1},
+                                     /*newSuppressedPointerIds*/ {0, 1});
+    ASSERT_EQ(1u, result.size());
+    assertArgs(result[0], DOWN, {{2, {7, 8, 9}}});
+    ASSERT_EQ(0, result[0].flags);
+}
+
+/**
+ * Call 'getTouches' for a DOWN event and check that the resulting 'InProgressTouchEvdev'
+ * struct is populated as expected.
+ */
+TEST(GetTouchesTest, ConvertDownEvent) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, DOWN, {{1, 2, 3}});
+    AndroidPalmFilterDeviceInfo deviceInfo = generatePalmFilterDeviceInfo();
+    SlotState slotState;
+    SlotState oldSlotState = slotState;
+    slotState.update(args);
+    std::vector<::ui::InProgressTouchEvdev> touches =
+            getTouches(args, deviceInfo, oldSlotState, slotState);
+    ASSERT_EQ(1u, touches.size());
+    ::ui::InProgressTouchEvdev expected;
+
+    expected.major = 3;
+    expected.minor = 0;
+    expected.tool_type = MT_TOOL_FINGER;
+    expected.altered = true;
+    expected.was_cancelled = false;
+    expected.cancelled = false;
+    expected.delayed = false;
+    expected.was_delayed = false;
+    expected.held = false;
+    expected.was_held = false;
+    expected.was_touching = false;
+    expected.touching = true;
+    expected.x = 1;
+    expected.y = 2;
+    expected.tracking_id = 0;
+    std::optional<size_t> slot = slotState.getSlotForPointerId(0);
+    ASSERT_TRUE(slot);
+    expected.slot = *slot;
+    expected.pressure = 0;
+    expected.tool_code = BTN_TOOL_FINGER;
+    expected.reported_tool_type = ::ui::EventPointerType::kTouch;
+    expected.stylus_button = false;
+
+    ASSERT_EQ(expected, touches[0]) << toString(touches[0]);
+}
+
+// --- UnwantedInteractionBlockerTest ---
+
+class UnwantedInteractionBlockerTest : public testing::Test {
+protected:
+    TestInputListener mTestListener;
+    std::unique_ptr<UnwantedInteractionBlockerInterface> mBlocker;
+
+    void SetUp() override {
+        mBlocker = std::make_unique<UnwantedInteractionBlocker>(mTestListener,
+                                                                /*enablePalmRejection*/ true);
+    }
+};
+
+/**
+ * Create a basic configuration change and send it to input classifier.
+ * Expect that the event is received by the next input stage, unmodified.
+ */
+TEST_F(UnwantedInteractionBlockerTest, ConfigurationChangedIsPassedToNextListener) {
+    // Create a basic configuration change and send to classifier
+    NotifyConfigurationChangedArgs args(1 /*sequenceNum*/, 2 /*eventTime*/);
+
+    mBlocker->notifyConfigurationChanged(&args);
+    NotifyConfigurationChangedArgs outArgs;
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyConfigurationChangedWasCalled(&outArgs));
+    ASSERT_EQ(args, outArgs);
+}
+
+/**
+ * Keys are not handled in 'UnwantedInteractionBlocker' and should be passed
+ * to next stage unmodified.
+ */
+TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) {
+    // Create a basic key event and send to classifier
+    NotifyKeyArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 21 /*readTime*/, 3 /*deviceId*/,
+                       AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, 0 /*policyFlags*/,
+                       AKEY_EVENT_ACTION_DOWN, 4 /*flags*/, AKEYCODE_HOME, 5 /*scanCode*/,
+                       AMETA_NONE, 6 /*downTime*/);
+
+    mBlocker->notifyKey(&args);
+    NotifyKeyArgs outArgs;
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(&outArgs));
+    ASSERT_EQ(args, outArgs);
+}
+
+/**
+ * Create a basic motion event. Since it's just a DOWN event, it should not
+ * be detected as palm and should be sent to the next listener stage
+ * unmodified.
+ */
+TEST_F(UnwantedInteractionBlockerTest, DownEventIsPassedToNextListener) {
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+    mBlocker->notifyMotion(&motionArgs);
+    NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(motionArgs, args);
+}
+
+/**
+ * Create a basic switch event and send it to the UnwantedInteractionBlocker.
+ * Expect that the event is received by the next input stage, unmodified.
+ */
+TEST_F(UnwantedInteractionBlockerTest, SwitchIsPassedToNextListener) {
+    NotifySwitchArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*policyFlags*/, 4 /*switchValues*/,
+                          5 /*switchMask*/);
+
+    mBlocker->notifySwitch(&args);
+    NotifySwitchArgs outArgs;
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifySwitchWasCalled(&outArgs));
+    ASSERT_EQ(args, outArgs);
+}
+
+/**
+ * Create a basic device reset event and send it to UnwantedInteractionBlocker.
+ * Expect that the event is received by the next input stage, unmodified.
+ */
+TEST_F(UnwantedInteractionBlockerTest, DeviceResetIsPassedToNextListener) {
+    NotifyDeviceResetArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, DEVICE_ID);
+
+    mBlocker->notifyDeviceReset(&args);
+    NotifyDeviceResetArgs outArgs;
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyDeviceResetWasCalled(&outArgs));
+    ASSERT_EQ(args, outArgs);
+}
+
+/**
+ * The state should be reset when device reset happens. That means, we can reset in the middle of a
+ * gesture, and start a new stream. There should be no crash. If the state wasn't reset correctly,
+ * a crash due to inconsistent event stream could have occurred.
+ */
+TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenResetHappens) {
+    NotifyMotionArgs args;
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}})));
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}})));
+    NotifyDeviceResetArgs resetArgs(1 /*sequenceNum*/, 3 /*eventTime*/, DEVICE_ID);
+    mBlocker->notifyDeviceReset(&resetArgs);
+    // Start a new gesture with a DOWN event, even though the previous event stream was incomplete.
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, DOWN, {{7, 8, 9}})));
+}
+
+/**
+ * If input devices have changed, but the important device info that's used by the
+ * UnwantedInteractionBlocker has not changed, there should not be a reset.
+ */
+TEST_F(UnwantedInteractionBlockerTest, NoResetIfDeviceInfoChanges) {
+    NotifyMotionArgs args;
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}})));
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}})));
+
+    // Now pretend the device changed, even though nothing is different for DEVICE_ID in practice.
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+
+    // The MOVE event continues the gesture that started before 'devices changed', so it should not
+    // cause a crash.
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, MOVE, {{7, 8, 9}})));
+}
+
+using UnwantedInteractionBlockerTestDeathTest = UnwantedInteractionBlockerTest;
+
+/**
+ * The state should be reset when device reset happens. If we receive an inconsistent event after
+ * the reset happens, crash should occur.
+ */
+TEST_F(UnwantedInteractionBlockerTestDeathTest, InconsistentEventAfterResetCausesACrash) {
+    ScopedSilentDeath _silentDeath;
+    NotifyMotionArgs args;
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}})));
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}})));
+    NotifyDeviceResetArgs resetArgs(1 /*sequenceNum*/, 3 /*eventTime*/, DEVICE_ID);
+    mBlocker->notifyDeviceReset(&resetArgs);
+    // Sending MOVE without a DOWN -> should crash!
+    ASSERT_DEATH(
+            {
+                mBlocker->notifyMotion(&(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/,
+                                                                   MOVE, {{7, 8, 9}})));
+            },
+            "Could not find slot");
+}
+
+/**
+ * There should be a crash when an inconsistent event is received.
+ */
+TEST_F(UnwantedInteractionBlockerTestDeathTest, WhenMoveWithoutDownCausesACrash) {
+    ScopedSilentDeath _silentDeath;
+    NotifyMotionArgs args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 2, 3}});
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    ASSERT_DEATH({ mBlocker->notifyMotion(&args); }, "Could not find slot");
+}
+
+class PalmRejectorTest : public testing::Test {
+protected:
+    std::unique_ptr<PalmRejector> mPalmRejector;
+
+    void SetUp() override {
+        AndroidPalmFilterDeviceInfo info = generatePalmFilterDeviceInfo();
+        mPalmRejector = std::make_unique<PalmRejector>(info);
+    }
+};
+
+using PalmRejectorTestDeathTest = PalmRejectorTest;
+
+TEST_F(PalmRejectorTestDeathTest, InconsistentEventCausesACrash) {
+    ScopedSilentDeath _silentDeath;
+    constexpr nsecs_t downTime = 0;
+    NotifyMotionArgs args =
+            generateMotionArgs(downTime, 2 /*eventTime*/, MOVE, {{1406.0, 650.0, 52.0}});
+    ASSERT_DEATH({ mPalmRejector->processMotion(args); }, "Could not find slot");
+}
+
+/**
+ * Use PalmRejector with actual touchscreen data and real model.
+ * Two pointers that should both be classified as palms.
+ */
+TEST_F(PalmRejectorTest, TwoPointersAreCanceled) {
+    std::vector<NotifyMotionArgs> argsList;
+    constexpr nsecs_t downTime = 255955749837000;
+
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955759313000, MOVE, {{1406.0, 650.0, 52.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955766361000, MOVE, {{1429.0, 672.0, 46.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955775989000, MOVE, {{1417.0, 685.0, 41.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955775989000, POINTER_1_DOWN,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783039000, MOVE,
+                               {{1414.0, 702.0, 41.0}, {1059.0, 731.0, 12.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955792536000, MOVE,
+                               {{1415.0, 719.0, 44.0}, {1060.0, 760.0, 11.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955799474000, MOVE,
+                               {{1421.0, 733.0, 42.0}, {1065.0, 769.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955809177000, MOVE,
+                               {{1426.0, 742.0, 43.0}, {1068.0, 771.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955816131000, MOVE,
+                               {{1430.0, 748.0, 45.0}, {1069.0, 772.0, 13.0}}));
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955825907000, 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,
+                               {{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(MOVE, argsList[1].action);
+    ASSERT_EQ(1u, argsList[1].pointerCount);
+    ASSERT_EQ(0, argsList[1].flags);
+
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955842432000, MOVE,
+                               {{1433.0, 751.0, 42.0}, {1071.0, 770.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955849380000, MOVE,
+                               {{1433.0, 751.0, 45.0}, {1072.0, 769.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955859046000, MOVE,
+                               {{1433.0, 751.0, 43.0}, {1072.0, 768.0, 13.0}}));
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955869823000, 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,
+                               {{1433.0, 751.0, 43.0}, {1072.0, 766.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955882693000, MOVE,
+                               {{1433.0, 750.0, 44.0}, {1072.0, 765.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955892324000, MOVE,
+                               {{1433.0, 750.0, 42.0}, {1072.0, 763.0, 14.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955899425000, MOVE,
+                               {{1434.0, 750.0, 44.0}, {1073.0, 761.0, 14.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955909400000, MOVE,
+                               {{1435.0, 750.0, 43.0}, {1073.0, 759.0, 15.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955915885000, MOVE,
+                               {{1436.0, 750.0, 45.0}, {1074.0, 757.0, 15.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955925607000, MOVE,
+                               {{1436.0, 750.0, 44.0}, {1074.0, 755.0, 15.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955932580000, MOVE,
+                               {{1436.0, 750.0, 45.0}, {1074.0, 753.0, 15.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955942231000, MOVE,
+                               {{1436.0, 749.0, 44.0}, {1074.0, 751.0, 15.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955949204000, MOVE,
+                               {{1435.0, 748.0, 45.0}, {1074.0, 749.0, 15.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955959103000, MOVE,
+                               {{1434.0, 746.0, 44.0}, {1074.0, 747.0, 14.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955965884000, MOVE,
+                               {{1433.0, 744.0, 44.0}, {1075.0, 745.0, 14.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955975649000, MOVE,
+                               {{1431.0, 741.0, 43.0}, {1075.0, 742.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955982537000, MOVE,
+                               {{1428.0, 738.0, 43.0}, {1076.0, 739.0, 12.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955992284000, MOVE,
+                               {{1400.0, 726.0, 54.0}, {1076.0, 739.0, 13.0}}));
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955999348000, 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}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956008885000, MOVE, {{1347.0, 707.0, 54.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956015791000, MOVE, {{1340.0, 698.0, 54.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956025804000, MOVE, {{1338.0, 694.0, 55.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956032314000, MOVE, {{1336.0, 690.0, 53.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956042329000, MOVE, {{1334.0, 685.0, 47.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956048979000, MOVE, {{1333.0, 679.0, 46.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956058813000, MOVE, {{1332.0, 672.0, 45.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956065592000, MOVE, {{1333.0, 666.0, 40.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956075276000, MOVE, {{1336.0, 661.0, 24.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956082198000, MOVE, {{1338.0, 656.0, 16.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956092059000, MOVE, {{1341.0, 649.0, 1.0}}));
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956098764000, UP, {{1341.0, 649.0, 1.0}}));
+    ASSERT_TRUE(argsList.empty());
+}
+
+/**
+ * A test implementation of PalmDetectionFilter that allows you to specify which pointer you want
+ * the model to consider 'suppressed'. The pointer is specified using its position (x, y).
+ * Current limitation:
+ *      Pointers may not cross each other in space during motion. Otherwise, any pointer with the
+ *      position matching the suppressed position will be considered "palm".
+ */
+class TestFilter : public ::ui::PalmDetectionFilter {
+public:
+    TestFilter(::ui::SharedPalmDetectionFilterState* state,
+               std::vector<std::pair<float, float>>& suppressedPointers)
+          : ::ui::PalmDetectionFilter(state), mSuppressedPointers(suppressedPointers) {}
+
+    void Filter(const std::vector<::ui::InProgressTouchEvdev>& touches, ::base::TimeTicks time,
+                std::bitset<::ui::kNumTouchEvdevSlots>* slots_to_hold,
+                std::bitset<::ui::kNumTouchEvdevSlots>* slots_to_suppress) override {
+        updateSuppressedSlots(touches);
+        *slots_to_suppress = mSuppressedSlots;
+    }
+
+    std::string FilterNameForTesting() const override { return "test filter"; }
+
+private:
+    void updateSuppressedSlots(const std::vector<::ui::InProgressTouchEvdev>& touches) {
+        for (::ui::InProgressTouchEvdev touch : touches) {
+            for (const auto& [x, y] : mSuppressedPointers) {
+                const float dx = (touch.x - x);
+                const float dy = (touch.y - y);
+                const float distanceSquared = dx * dx + dy * dy;
+                if (distanceSquared < 1) {
+                    mSuppressedSlots.set(touch.slot, true);
+                }
+            }
+        }
+    }
+
+    std::bitset<::ui::kNumTouchEvdevSlots> mSuppressedSlots;
+    std::vector<std::pair<float, float>>& mSuppressedPointers;
+};
+
+class PalmRejectorFakeFilterTest : public testing::Test {
+protected:
+    std::unique_ptr<PalmRejector> mPalmRejector;
+
+    void SetUp() override {
+        std::unique_ptr<::ui::PalmDetectionFilter> filter =
+                std::make_unique<TestFilter>(&mSharedPalmState, /*byref*/ mSuppressedPointers);
+        mPalmRejector =
+                std::make_unique<PalmRejector>(generatePalmFilterDeviceInfo(), std::move(filter));
+    }
+
+    void suppressPointerAtPosition(float x, float y) { mSuppressedPointers.push_back({x, y}); }
+
+private:
+    std::vector<std::pair<float, float>> mSuppressedPointers;
+    ::ui::SharedPalmDetectionFilterState mSharedPalmState; // unused, but we must retain ownership
+};
+
+/**
+ * When a MOVE event happens, the model identifies the pointer as palm. At that time, the palm
+ * rejector should send a POINTER_UP event for this pointer with FLAG_CANCELED, and subsequent
+ * events should have this pointer removed.
+ */
+TEST_F(PalmRejectorFakeFilterTest, OneOfTwoPointersIsCanceled) {
+    std::vector<NotifyMotionArgs> argsList;
+    constexpr nsecs_t downTime = 0;
+
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 1, POINTER_1_DOWN,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
+    // Cancel the second pointer
+    suppressPointerAtPosition(1059, 731);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783039000, MOVE,
+                               {{1414.0, 702.0, 41.0}, {1059.0, 731.0, 12.0}}));
+    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);
+    // Second event - send MOVE for the remaining pointer
+    ASSERT_EQ(MOVE, argsList[1].action);
+    ASSERT_EQ(0, argsList[1].flags);
+
+    // Future move events only contain 1 pointer, because the second pointer will continue
+    // to be suppressed
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783039000, MOVE,
+                               {{1433.0, 751.0, 43.0}, {1072.0, 766.0, 13.0}}));
+    ASSERT_EQ(1u, argsList.size());
+    ASSERT_EQ(MOVE, argsList[0].action);
+    ASSERT_EQ(1u, argsList[0].pointerCount);
+    ASSERT_EQ(1433, argsList[0].pointerCoords[0].getX());
+    ASSERT_EQ(751, argsList[0].pointerCoords[0].getY());
+}
+
+/**
+ * Send two pointers, and suppress both of them. Check that ACTION_CANCEL is generated.
+ * Afterwards:
+ *  1) Future MOVE events are ignored.
+ *  2) When a new pointer goes down, ACTION_DOWN is generated
+ */
+TEST_F(PalmRejectorFakeFilterTest, NewDownEventAfterCancel) {
+    std::vector<NotifyMotionArgs> argsList;
+    constexpr nsecs_t downTime = 0;
+
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 1, POINTER_1_DOWN,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
+    // Cancel both pointers
+    suppressPointerAtPosition(1059, 731);
+    suppressPointerAtPosition(1400, 680);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 1, MOVE, {{1400, 680, 41}, {1059, 731, 10}}));
+    ASSERT_EQ(1u, argsList.size());
+    // Cancel all
+    ASSERT_EQ(CANCEL, argsList[0].action);
+    ASSERT_EQ(2u, argsList[0].pointerCount);
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, argsList[0].flags);
+
+    // Future move events are ignored
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783039000, MOVE,
+                               {{1433.0, 751.0, 43.0}, {1072.0, 766.0, 13.0}}));
+    ASSERT_EQ(0u, argsList.size());
+
+    // When a new pointer goes down, a new DOWN event is generated
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783039000, POINTER_2_DOWN,
+                               {{1433.0, 751.0, 43.0}, {1072.0, 766.0, 13.0}, {1000, 700, 10}}));
+    ASSERT_EQ(1u, argsList.size());
+    ASSERT_EQ(DOWN, argsList[0].action);
+    ASSERT_EQ(1u, argsList[0].pointerCount);
+    ASSERT_EQ(2, argsList[0].pointerProperties[0].id);
+}
+
+/**
+ * 2 pointers are classified as palm simultaneously. When they are later
+ * released by Android, make sure that we drop both of these POINTER_UP events.
+ * Since they are classified as palm at the same time, we just need to receive a single CANCEL
+ * event. From MotionEvent docs: """A pointer id remains valid until the pointer eventually goes up
+ * (indicated by ACTION_UP or ACTION_POINTER_UP) or when the gesture is canceled (indicated by
+ *  ACTION_CANCEL)."""
+ * This means that generating additional POINTER_UP events is not necessary.
+ * The risk here is that "oldSuppressedPointerIds" will not be correct, because it will update after
+ * each motion, but pointers are canceled one at a time by Android.
+ */
+TEST_F(PalmRejectorFakeFilterTest, TwoPointersCanceledWhenOnePointerGoesUp) {
+    std::vector<NotifyMotionArgs> argsList;
+    constexpr nsecs_t downTime = 0;
+
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_1_DOWN,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
+    // Suppress both pointers!!
+    suppressPointerAtPosition(1414, 702);
+    suppressPointerAtPosition(1059, 731);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783039000, POINTER_1_UP,
+                               {{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);
+
+    // Future move events should not go to the listener.
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783049000, MOVE, {{1435.0, 755.0, 43.0}}));
+    ASSERT_EQ(0u, argsList.size());
+
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783059000, UP, {{1436.0, 756.0, 43.0}}));
+    ASSERT_EQ(0u, argsList.size());
+}
+
+/**
+ * Send 3 pointers, and then cancel one of them during a MOVE event. We should see ACTION_POINTER_UP
+ * generated for that. Next, another pointer is canceled during ACTION_POINTER_DOWN. For that
+ * pointer, we simply shouldn't send the event.
+ */
+TEST_F(PalmRejectorFakeFilterTest, CancelTwoPointers) {
+    std::vector<NotifyMotionArgs> argsList;
+    constexpr nsecs_t downTime = 0;
+
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_1_DOWN,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
+
+    // Suppress second pointer (pointer 1)
+    suppressPointerAtPosition(1060, 700);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, /*eventTime*/ 1, MOVE,
+                               {{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(MOVE, argsList[1].action) << MotionEvent::actionToString(argsList[1].action);
+    ASSERT_EQ(0, argsList[1].flags);
+
+    // A new pointer goes down and gets suppressed right away. It should just be dropped
+    suppressPointerAtPosition(1001, 601);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_2_DOWN,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}, {1001, 601, 5}}));
+
+    ASSERT_EQ(0u, argsList.size());
+    // Likewise, pointer that's already canceled should be ignored
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_2_UP,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}, {1001, 601, 5}}));
+    ASSERT_EQ(0u, argsList.size());
+
+    // Cancel all pointers when pointer 1 goes up. Pointer 1 was already canceled earlier.
+    suppressPointerAtPosition(1417, 685);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_1_UP,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
+    ASSERT_EQ(1u, argsList.size());
+    ASSERT_EQ(CANCEL, argsList[0].action);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index e797b5d..b7e2ff3 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -306,7 +306,7 @@
                 : aidl::android::hardware::graphics::composer3::Composition::DEVICE;
     }
 
-    compositionState->buffer = mBufferInfo.mBuffer->getBuffer();
+    compositionState->buffer = getBuffer();
     compositionState->bufferSlot = (mBufferInfo.mBufferSlot == BufferQueue::INVALID_BUFFER_SLOT)
             ? 0
             : mBufferInfo.mBufferSlot;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
index 16cb41b..47aacc9 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;
 
+    // Returns the boot display mode preferred by HWC.
+    virtual int32_t getPreferredBootModeId() const = 0;
+
 protected:
     ~Display() = default;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index b777241..ebe112b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -62,6 +62,7 @@
     bool isSecure() const override;
     bool isVirtual() const override;
     void disconnect() override;
+    int32_t getPreferredBootModeId() const override;
     void createDisplayColorProfile(
             const compositionengine::DisplayColorProfileCreationArgs&) override;
     void createRenderSurface(const compositionengine::RenderSurfaceCreationArgs&) override;
@@ -87,6 +88,7 @@
     DisplayId mId;
     bool mIsDisconnected = false;
     Hwc2::PowerAdvisor* mPowerAdvisor = nullptr;
+    int32_t mPreferredBootDisplayModeId = -1;
 };
 
 // This template factory function standardizes the implementation details of the
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
index 49cb912..08cfaa6 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
@@ -90,6 +90,10 @@
     // The dataspace for this layer
     ui::Dataspace dataspace{ui::Dataspace::UNKNOWN};
 
+    // A hint to the HWC that this region is transparent and may be skipped in
+    // order to save power.
+    Region outputSpaceBlockingRegionHint;
+
     // Overrides the buffer, acquire fence, and display frame stored in LayerFECompositionState
     struct {
         std::shared_ptr<renderengine::ExternalTexture> buffer = nullptr;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
index 6fb3e08..84afb59 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
@@ -34,6 +34,7 @@
     MOCK_CONST_METHOD0(getId, DisplayId());
     MOCK_CONST_METHOD0(isSecure, bool());
     MOCK_CONST_METHOD0(isVirtual, bool());
+    MOCK_CONST_METHOD0(getPreferredBootModeId, int32_t());
 
     MOCK_METHOD0(disconnect, void());
 
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 186e191..29c146b 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -57,6 +57,16 @@
     editState().isSecure = args.isSecure;
     editState().displaySpace.setBounds(args.pixels);
     setName(args.name);
+    bool isBootModeSupported = getCompositionEngine().getHwComposer().getBootDisplayModeSupport();
+    const auto physicalId = PhysicalDisplayId::tryCast(mId);
+    if (!physicalId || !isBootModeSupported) {
+        return;
+    }
+    std::optional<hal::HWConfigId> preferredBootModeId =
+            getCompositionEngine().getHwComposer().getPreferredBootDisplayMode(*physicalId);
+    if (preferredBootModeId.has_value()) {
+        mPreferredBootDisplayModeId = static_cast<int32_t>(preferredBootModeId.value());
+    }
 }
 
 bool Display::isValid() const {
@@ -79,6 +89,10 @@
     return mId;
 }
 
+int32_t Display::getPreferredBootModeId() const {
+    return mPreferredBootDisplayModeId;
+}
+
 void Display::disconnect() {
     if (mIsDisconnected) {
         return;
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 162d84e..65f9731 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -50,6 +50,8 @@
 
 #include "TracedOrdinal.h"
 
+using aidl::android::hardware::graphics::composer3::Composition;
+
 namespace android::compositionengine {
 
 Output::~Output() = default;
@@ -529,11 +531,18 @@
 
     /*
      * transparentRegion: area of a surface that is hinted to be completely
-     * transparent. This is only used to tell when the layer has no visible non-
-     * transparent regions and can be removed from the layer list. It does not
-     * affect the visibleRegion of this layer or any layers beneath it. The hint
-     * may not be correct if apps don't respect the SurfaceView restrictions
-     * (which, sadly, some don't).
+     * transparent.
+     * This is used to tell when the layer has no visible non-transparent
+     * regions and can be removed from the layer list. It does not affect the
+     * visibleRegion of this layer or any layers beneath it. The hint may not
+     * be correct if apps don't respect the SurfaceView restrictions (which,
+     * sadly, some don't).
+     *
+     * In addition, it is used on DISPLAY_DECORATION layers to specify the
+     * blockingRegion, allowing the DPU to skip it to save power. Once we have
+     * hardware that supports a blockingRegion on frames with AFBC, it may be
+     * useful to use this for other layers, too, so long as we can prevent
+     * regressions on b/7179570.
      */
     Region transparentRegion;
 
@@ -674,6 +683,9 @@
     outputLayerState.outputSpaceVisibleRegion = outputState.transform.transform(
             visibleNonShadowRegion.intersect(outputState.layerStackSpace.getContent()));
     outputLayerState.shadowRegion = shadowRegion;
+    outputLayerState.outputSpaceBlockingRegionHint =
+            layerFEState->compositionType == Composition::DISPLAY_DECORATION ? transparentRegion
+                                                                             : Region();
 }
 
 void Output::setReleasedLayers(const compositionengine::CompositionRefreshArgs&) {
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 95cc5a8..4ccf11f 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -484,6 +484,14 @@
         visibleRegion.dump(LOG_TAG);
     }
 
+    if (auto error =
+                hwcLayer->setBlockingRegion(outputDependentState.outputSpaceBlockingRegionHint);
+        error != hal::Error::NONE) {
+        ALOGE("[%s] Failed to set blocking region: %s (%d)", getLayerFE().getDebugName(),
+              to_string(error).c_str(), static_cast<int32_t>(error));
+        outputDependentState.outputSpaceBlockingRegionHint.dump(LOG_TAG);
+    }
+
     const auto dataspace = outputDependentState.overrideInfo.buffer
             ? outputDependentState.overrideInfo.dataspace
             : outputDependentState.dataspace;
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 0918510..8d26747 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -401,6 +401,19 @@
     return true;
 }
 
+namespace {
+bool isDisplayDecoration(const CachedSet& cachedSet) {
+    return cachedSet.getLayerCount() == 1 &&
+            cachedSet.getFirstLayer()
+                    .getState()
+                    ->getOutputLayer()
+                    ->getLayerFE()
+                    .getCompositionState()
+                    ->compositionType ==
+            aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
+}
+} // namespace
+
 std::vector<Flattener::Run> Flattener::findCandidateRuns(time_point now) const {
     ATRACE_CALL();
     std::vector<Run> runs;
@@ -424,7 +437,7 @@
         }
 
         if (layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur) &&
-            !currentSet->hasUnsupportedDataspace()) {
+            !currentSet->hasUnsupportedDataspace() && !isDisplayDecoration(*currentSet)) {
             if (isPartOfRun) {
                 builder.increment();
             } else {
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 7dd4c21..03c6f8d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -164,6 +164,7 @@
         EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
         EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
         EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
+        EXPECT_CALL(mHwComposer, getBootDisplayModeSupport()).WillRepeatedly(Return(false));
     }
 
     DisplayCreationArgs getDisplayCreationArgsForPhysicalDisplay() {
@@ -971,7 +972,8 @@
 
     DisplayFunctionalTest() {
         EXPECT_CALL(mCompositionEngine, getHwComposer()).WillRepeatedly(ReturnRef(mHwComposer));
-
+        mDisplay = createDisplay();
+        mRenderSurface = createRenderSurface();
         mDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
     }
 
@@ -980,24 +982,29 @@
     NiceMock<mock::CompositionEngine> mCompositionEngine;
     sp<mock::NativeWindow> mNativeWindow = new NiceMock<mock::NativeWindow>();
     sp<mock::DisplaySurface> mDisplaySurface = new NiceMock<mock::DisplaySurface>();
+    std::shared_ptr<Display> mDisplay;
+    impl::RenderSurface* mRenderSurface;
 
-    std::shared_ptr<Display> mDisplay = impl::createDisplayTemplated<
-            Display>(mCompositionEngine,
-                     DisplayCreationArgsBuilder()
-                             .setId(DEFAULT_DISPLAY_ID)
-                             .setPixels(DEFAULT_RESOLUTION)
-                             .setIsSecure(true)
-                             .setPowerAdvisor(&mPowerAdvisor)
-                             .build());
+    std::shared_ptr<Display> createDisplay() {
+        return impl::createDisplayTemplated<Display>(mCompositionEngine,
+                                                     DisplayCreationArgsBuilder()
+                                                             .setId(DEFAULT_DISPLAY_ID)
+                                                             .setPixels(DEFAULT_RESOLUTION)
+                                                             .setIsSecure(true)
+                                                             .setPowerAdvisor(&mPowerAdvisor)
+                                                             .build());
+        ;
+    }
 
-    impl::RenderSurface* mRenderSurface =
-            new impl::RenderSurface{mCompositionEngine, *mDisplay,
-                                    RenderSurfaceCreationArgsBuilder()
-                                            .setDisplayWidth(DEFAULT_RESOLUTION.width)
-                                            .setDisplayHeight(DEFAULT_RESOLUTION.height)
-                                            .setNativeWindow(mNativeWindow)
-                                            .setDisplaySurface(mDisplaySurface)
-                                            .build()};
+    impl::RenderSurface* createRenderSurface() {
+        return new impl::RenderSurface{mCompositionEngine, *mDisplay,
+                                       RenderSurfaceCreationArgsBuilder()
+                                               .setDisplayWidth(DEFAULT_RESOLUTION.width)
+                                               .setDisplayHeight(DEFAULT_RESOLUTION.height)
+                                               .setNativeWindow(mNativeWindow)
+                                               .setDisplaySurface(mDisplaySurface)
+                                               .build()};
+    }
 };
 
 TEST_F(DisplayFunctionalTest, postFramebufferCriticalCallsAreOrdered) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 6443c2b..660f664 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -108,7 +108,8 @@
                           hal::VsyncPeriodChangeTimeline*));
     MOCK_METHOD2(setBootDisplayMode, status_t(PhysicalDisplayId, hal::HWConfigId));
     MOCK_METHOD1(clearBootDisplayMode, status_t(PhysicalDisplayId));
-    MOCK_METHOD1(getPreferredBootDisplayMode, hal::HWConfigId(PhysicalDisplayId));
+    MOCK_METHOD1(getPreferredBootDisplayMode, std::optional<hal::HWConfigId>(PhysicalDisplayId));
+    MOCK_METHOD0(getBootDisplayModeSupport, bool());
     MOCK_METHOD2(setAutoLowLatencyMode, status_t(PhysicalDisplayId, bool));
     MOCK_METHOD2(getSupportedContentTypes,
                  status_t(PhysicalDisplayId, std::vector<hal::ContentType>*));
@@ -127,7 +128,6 @@
                 (const, override));
     MOCK_METHOD(std::optional<hal::HWDisplayId>, fromPhysicalDisplayId, (PhysicalDisplayId),
                 (const, override));
-    MOCK_METHOD(bool, getBootDisplayModeSupport, (), (override));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 132ac02..0b123b1 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -846,7 +846,8 @@
                                    ui::Dataspace dataspace = kDataspace,
                                    const Region& visibleRegion = kOutputSpaceVisibleRegion,
                                    const Region& surfaceDamage = kSurfaceDamage,
-                                   float whitePointNits = kWhitePointNits) {
+                                   float whitePointNits = kWhitePointNits,
+                                   const Region& blockingRegion = Region()) {
         EXPECT_CALL(*mHwcLayer, setVisibleRegion(RegionEq(visibleRegion))).WillOnce(Return(kError));
         EXPECT_CALL(*mHwcLayer, setDataspace(dataspace)).WillOnce(Return(kError));
         EXPECT_CALL(*mHwcLayer, setWhitePointNits(whitePointNits)).WillOnce(Return(kError));
@@ -855,6 +856,8 @@
                                          ? hal::Error::UNSUPPORTED
                                          : hal::Error::NONE));
         EXPECT_CALL(*mHwcLayer, setSurfaceDamage(RegionEq(surfaceDamage))).WillOnce(Return(kError));
+        EXPECT_CALL(*mHwcLayer, setBlockingRegion(RegionEq(blockingRegion)))
+                .WillOnce(Return(kError));
     }
 
     void expectSetCompositionTypeCall(Composition compositionType) {
@@ -1278,6 +1281,23 @@
     EXPECT_EQ(Composition::DEVICE, mOutputLayer.getState().hwc->hwcCompositionType);
 }
 
+TEST_F(OutputLayerWriteStateToHWCTest, setBlockingRegion) {
+    mLayerFEState.compositionType = Composition::DISPLAY_DECORATION;
+    const auto blockingRegion = Region(Rect(0, 0, 1000, 1000));
+    mOutputLayer.editState().outputSpaceBlockingRegionHint = blockingRegion;
+
+    expectGeometryCommonCalls();
+    expectPerFrameCommonCalls(SimulateUnsupported::None, kDataspace, kOutputSpaceVisibleRegion,
+                              kSurfaceDamage, kWhitePointNits, blockingRegion);
+    expectSetHdrMetadataAndBufferCalls();
+    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
+    expectSetCompositionTypeCall(Composition::DISPLAY_DECORATION);
+
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/
+                                 false);
+}
+
 /*
  * OutputLayer::writeCursorPositionToHWC()
  */
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 655db4b..f7d5991 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -1291,7 +1291,7 @@
         mLayer.layerFEState.contentDirty = true;
         mLayer.layerFEState.geomLayerBounds = FloatRect{0, 0, 100, 200};
         mLayer.layerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
-        mLayer.layerFEState.transparentRegionHint = Region(Rect(0, 0, 100, 100));
+        mLayer.layerFEState.transparentRegionHint = kTransparentRegionHint;
 
         mLayer.outputLayerState.visibleRegion = Region(Rect(0, 0, 50, 200));
         mLayer.outputLayerState.coveredRegion = Region(Rect(50, 0, 100, 200));
@@ -1309,6 +1309,7 @@
     static const Region kRightHalfBoundsNoRotation;
     static const Region kLowerHalfBoundsNoRotation;
     static const Region kFullBounds90Rotation;
+    static const Region kTransparentRegionHint;
 
     StrictMock<OutputPartialMock> mOutput;
     LayerFESet mGeomSnapshots;
@@ -1326,6 +1327,8 @@
         Region(Rect(50, 0, 100, 200));
 const Region OutputEnsureOutputLayerIfVisibleTest::kFullBounds90Rotation =
         Region(Rect(0, 0, 200, 100));
+const Region OutputEnsureOutputLayerIfVisibleTest::kTransparentRegionHint =
+        Region(Rect(0, 0, 100, 100));
 
 TEST_F(OutputEnsureOutputLayerIfVisibleTest, performsGeomLatchBeforeCheckingIfLayerIncluded) {
     EXPECT_CALL(mOutput, includesLayer(sp<LayerFE>(mLayer.layerFE))).WillOnce(Return(false));
@@ -1749,6 +1752,33 @@
     ensureOutputLayerIfVisible();
 }
 
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, displayDecorSetsBlockingFromTransparentRegion) {
+    mLayer.layerFEState.isOpaque = false;
+    mLayer.layerFEState.contentDirty = true;
+    mLayer.layerFEState.compositionType =
+            aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(std::nullopt), Eq(mLayer.layerFE)))
+            .WillOnce(Return(&mLayer.outputLayer));
+    ensureOutputLayerIfVisible();
+
+    EXPECT_THAT(mLayer.outputLayerState.outputSpaceBlockingRegionHint,
+                RegionEq(kTransparentRegionHint));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, normalLayersDoNotSetBlockingRegion) {
+    mLayer.layerFEState.isOpaque = false;
+    mLayer.layerFEState.contentDirty = true;
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(std::nullopt), Eq(mLayer.layerFE)))
+            .WillOnce(Return(&mLayer.outputLayer));
+    ensureOutputLayerIfVisible();
+
+    EXPECT_THAT(mLayer.outputLayerState.outputSpaceBlockingRegionHint, RegionEq(Region()));
+}
+
 /*
  * Output::present()
  */
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 58dc244..656ef9a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -1337,5 +1337,56 @@
     EXPECT_NE(nullptr, overrideBuffer4);
 }
 
+TEST_F(FlattenerTest, flattenLayers_skips_DISPLAY_DECORATION) {
+    auto& layerState1 = mTestLayers[0]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+
+    auto& layerState2 = mTestLayers[1]->layerState;
+    const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+
+    // The third layer uses DISPLAY_DECORATION, which should never be cached.
+    auto& layerState3 = mTestLayers[2]->layerState;
+    const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+    mTestLayers[2]->layerFECompositionState.compositionType =
+            aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
+    mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer);
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+            layerState3.get(),
+    };
+
+    initializeFlattener(layers);
+
+    mTime += 200ms;
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+    // This will render a CachedSet.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    // We've rendered a CachedSet, but we haven't merged it in.
+    EXPECT_EQ(nullptr, overrideBuffer1);
+    EXPECT_EQ(nullptr, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+
+    // This time we merge the CachedSet in, so we have a new hash, and we should
+    // only have two sets.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    initializeOverrideBuffer(layers);
+    EXPECT_NE(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    EXPECT_NE(nullptr, overrideBuffer1);
+    EXPECT_EQ(overrideBuffer1, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index a36ea72..f542161 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -92,10 +92,12 @@
     }
 
     mCompositionDisplay->createDisplayColorProfile(
-            compositionengine::DisplayColorProfileCreationArgs{args.hasWideColorGamut,
-                                                               std::move(args.hdrCapabilities),
-                                                               args.supportedPerFrameMetadata,
-                                                               args.hwcColorModes});
+            compositionengine::DisplayColorProfileCreationArgsBuilder()
+                    .setHasWideColorGamut(args.hasWideColorGamut)
+                    .setHdrCapabilities(std::move(args.hdrCapabilities))
+                    .setSupportedPerFrameMetadata(args.supportedPerFrameMetadata)
+                    .setHwcColorModes(std::move(args.hwcColorModes))
+                    .Build());
 
     if (!mCompositionDisplay->isValid()) {
         ALOGE("Composition Display did not validate!");
@@ -454,6 +456,10 @@
                            capabilities.getDesiredMinLuminance());
 }
 
+ui::DisplayModeId DisplayDevice::getPreferredBootModeId() const {
+    return mCompositionDisplay->getPreferredBootModeId();
+}
+
 void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinnner) {
     if (!enable) {
         mRefreshRateOverlay.reset();
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 0c9063d..3cae30f 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -157,6 +157,9 @@
     // respectively if hardware composer doesn't return meaningful values.
     HdrCapabilities getHdrCapabilities() const;
 
+    // Returns the boot display mode preferred by the implementation.
+    ui::DisplayModeId getPreferredBootModeId() const;
+
     // Return true if intent is supported by the display.
     bool hasRenderIntent(ui::RenderIntent intent) const;
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 9174ec7..2696bd8 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -772,18 +772,16 @@
     return NO_ERROR;
 }
 
-hal::HWConfigId HWComposer::getPreferredBootDisplayMode(PhysicalDisplayId displayId) {
-    RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
-    hal::HWConfigId displayModeId = -1;
+std::optional<hal::HWConfigId> HWComposer::getPreferredBootDisplayMode(
+        PhysicalDisplayId displayId) {
+    RETURN_IF_INVALID_DISPLAY(displayId, std::nullopt);
+    hal::HWConfigId displayModeId;
     const auto error =
             mDisplayData[displayId].hwcDisplay->getPreferredBootDisplayConfig(&displayModeId);
-    if (error == hal::Error::UNSUPPORTED) {
-        RETURN_IF_HWC_ERROR(error, displayId, INVALID_OPERATION);
+    if (error != hal::Error::NONE) {
+        LOG_DISPLAY_ERROR(displayId, to_string(error).c_str());
+        return std::nullopt;
     }
-    if (error == hal::Error::BAD_PARAMETER) {
-        RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
-    }
-    RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
     return displayModeId;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 3e68028..29335d5 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -260,7 +260,7 @@
     virtual bool getBootDisplayModeSupport() = 0;
     virtual status_t setBootDisplayMode(PhysicalDisplayId, hal::HWConfigId) = 0;
     virtual status_t clearBootDisplayMode(PhysicalDisplayId) = 0;
-    virtual hal::HWConfigId getPreferredBootDisplayMode(PhysicalDisplayId) = 0;
+    virtual std::optional<hal::HWConfigId> getPreferredBootDisplayMode(PhysicalDisplayId) = 0;
 };
 
 namespace impl {
@@ -391,7 +391,7 @@
     bool getBootDisplayModeSupport() override;
     status_t setBootDisplayMode(PhysicalDisplayId, hal::HWConfigId) override;
     status_t clearBootDisplayMode(PhysicalDisplayId) override;
-    hal::HWConfigId getPreferredBootDisplayMode(PhysicalDisplayId) override;
+    std::optional<hal::HWConfigId> getPreferredBootDisplayMode(PhysicalDisplayId) override;
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 7af1237..645d4d1 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -2160,101 +2160,71 @@
     return getCroppedBufferSize(getDrawingState());
 }
 
-void Layer::fillInputFrameInfo(WindowInfo& info, const ui::Transform& displayTransform) {
-    // Transform layer size to screen space and inset it by surface insets.
-    // If this is a portal window, set the touchableRegion to the layerBounds.
-    Rect layerBounds = info.portalToDisplayId == ADISPLAY_ID_NONE
-            ? getInputBounds()
-            : info.touchableRegion.getBounds();
-    if (!layerBounds.isValid()) {
-        layerBounds = getInputBounds();
-    }
-
-    if (!layerBounds.isValid()) {
-        // If the layer bounds is empty, set the frame to empty and clear the transform
-        info.frameLeft = 0;
-        info.frameTop = 0;
-        info.frameRight = 0;
-        info.frameBottom = 0;
-        info.transform.reset();
-        info.touchableRegion = Region();
+void Layer::fillInputFrameInfo(WindowInfo& info, const ui::Transform& screenToDisplay) {
+    Rect tmpBounds = getInputBounds();
+    if (!tmpBounds.isValid()) {
         info.flags = WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::NOT_FOCUSABLE;
-        return;
+        info.focusable = false;
+        info.touchableRegion.clear();
+        // A layer could have invalid input bounds and still expect to receive touch input if it has
+        // replaceTouchableRegionWithCrop. For that case, the input transform needs to be calculated
+        // correctly to determine the coordinate space for input events. Use an empty rect so that
+        // the layer will receive input in its own layer space.
+        tmpBounds = Rect::EMPTY_RECT;
     }
 
-    const ui::Transform layerTransform = getInputTransform();
-    // Transform that takes window coordinates to non-rotated display coordinates
-    ui::Transform t = displayTransform * layerTransform;
-    int32_t xSurfaceInset = info.surfaceInset;
-    int32_t ySurfaceInset = info.surfaceInset;
-    // Bring screenBounds into non-unrotated space
-    Rect screenBounds = displayTransform.transform(Rect{mScreenBounds});
+    // InputDispatcher works in the display device's coordinate space. Here, we calculate the
+    // frame and transform used for the layer, which determines the bounds and the coordinate space
+    // within which the layer will receive input.
+    //
+    // The coordinate space within which each of the bounds are specified is explicitly documented
+    // in the variable name. For example "inputBoundsInLayer" is specified in layer space. A
+    // Transform converts one coordinate space to another, which is apparent in its naming. For
+    // example, "layerToDisplay" transforms layer space to display space.
+    //
+    // Coordinate space definitions:
+    //   - display: The display device's coordinate space. Correlates to pixels on the display.
+    //   - screen: The post-rotation coordinate space for the display, a.k.a. logical display space.
+    //   - layer: The coordinate space of this layer.
+    //   - input: The coordinate space in which this layer will receive input events. This could be
+    //            different than layer space if a surfaceInset is used, which changes the origin
+    //            of the input space.
+    const FloatRect inputBoundsInLayer = tmpBounds.toFloatRect();
 
-    const float xScale = t.getScaleX();
-    const float yScale = t.getScaleY();
-    if (xScale != 1.0f || yScale != 1.0f) {
-        xSurfaceInset = std::round(xSurfaceInset * xScale);
-        ySurfaceInset = std::round(ySurfaceInset * yScale);
-    }
+    // Clamp surface inset to the input bounds.
+    const auto surfaceInset = static_cast<float>(info.surfaceInset);
+    const float xSurfaceInset =
+            std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getWidth() / 2.f));
+    const float ySurfaceInset =
+            std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getHeight() / 2.f));
 
-    // Transform the layer bounds from layer coordinate space to display coordinate space.
-    Rect transformedLayerBounds = t.transform(layerBounds);
+    // Apply the insets to the input bounds.
+    const FloatRect insetBoundsInLayer(inputBoundsInLayer.left + xSurfaceInset,
+                                       inputBoundsInLayer.top + ySurfaceInset,
+                                       inputBoundsInLayer.right - xSurfaceInset,
+                                       inputBoundsInLayer.bottom - ySurfaceInset);
 
-    // clamp inset to layer bounds
-    xSurfaceInset = (xSurfaceInset >= 0)
-            ? std::min(xSurfaceInset, transformedLayerBounds.getWidth() / 2)
-            : 0;
-    ySurfaceInset = (ySurfaceInset >= 0)
-            ? std::min(ySurfaceInset, transformedLayerBounds.getHeight() / 2)
-            : 0;
+    // Crop the input bounds to ensure it is within the parent's bounds.
+    const FloatRect croppedInsetBoundsInLayer = mBounds.intersect(insetBoundsInLayer);
 
-    // inset while protecting from overflow TODO(b/161235021): What is going wrong
-    // in the overflow scenario?
-    {
-    int32_t tmp;
-    if (!__builtin_add_overflow(transformedLayerBounds.left, xSurfaceInset, &tmp))
-        transformedLayerBounds.left = tmp;
-    if (!__builtin_sub_overflow(transformedLayerBounds.right, xSurfaceInset, &tmp))
-        transformedLayerBounds.right = tmp;
-    if (!__builtin_add_overflow(transformedLayerBounds.top, ySurfaceInset, &tmp))
-        transformedLayerBounds.top = tmp;
-    if (!__builtin_sub_overflow(transformedLayerBounds.bottom, ySurfaceInset, &tmp))
-        transformedLayerBounds.bottom = tmp;
-    }
+    const ui::Transform layerToScreen = getInputTransform();
+    const ui::Transform layerToDisplay = screenToDisplay * layerToScreen;
 
-    // Compute the correct transform to send to input. This will allow it to transform the
-    // input coordinates from display space into window space. Therefore, it needs to use the
-    // final layer frame to create the inverse transform. Since surface insets are added later,
-    // along with the overflow, the best way to ensure we get the correct transform is to use
-    // the final frame calculated.
-    // 1. Take the original transform set on the window and get the inverse transform. This is
-    //    used to get the final bounds in display space (ignorning the transform). Apply the
-    //    inverse transform on the layerBounds to get the untransformed frame (in layer space)
-    // 2. Take the top and left of the untransformed frame to get the real position on screen.
-    //    Apply the layer transform on top/left so it includes any scale or rotation. These will
-    //    be the new translation values for the transform.
-    // 3. Update the translation of the original transform to the new translation values.
-    // 4. Send the inverse transform to input so the coordinates can be transformed back into
-    //    window space.
-    ui::Transform inverseTransform = t.inverse();
-    Rect nonTransformedBounds = inverseTransform.transform(transformedLayerBounds);
-    vec2 translation = t.transform(nonTransformedBounds.left, nonTransformedBounds.top);
-    ui::Transform inputTransform(t);
-    inputTransform.set(translation.x, translation.y);
-    info.transform = inputTransform.inverse();
+    const Rect roundedFrameInDisplay{layerToDisplay.transform(croppedInsetBoundsInLayer)};
+    info.frameLeft = roundedFrameInDisplay.left;
+    info.frameTop = roundedFrameInDisplay.top;
+    info.frameRight = roundedFrameInDisplay.right;
+    info.frameBottom = roundedFrameInDisplay.bottom;
 
-    // We need to send the layer bounds cropped to the screenbounds since the layer can be cropped.
-    // The frame should be the area the user sees on screen since it's used for occlusion
-    // detection.
-    transformedLayerBounds.intersect(screenBounds, &transformedLayerBounds);
-    info.frameLeft = transformedLayerBounds.left;
-    info.frameTop = transformedLayerBounds.top;
-    info.frameRight = transformedLayerBounds.right;
-    info.frameBottom = transformedLayerBounds.bottom;
+    ui::Transform inputToLayer;
+    inputToLayer.set(insetBoundsInLayer.left, insetBoundsInLayer.top);
+    const ui::Transform inputToDisplay = layerToDisplay * inputToLayer;
 
-    // Position the touchable region relative to frame screen location and restrict it to frame
-    // bounds.
-    info.touchableRegion = inputTransform.transform(info.touchableRegion);
+    // InputDispatcher expects a display-to-input transform.
+    info.transform = inputToDisplay.inverse();
+
+    // The touchable region is specified in the input coordinate space. Change it to display space.
+    info.touchableRegion = inputToDisplay.transform(info.touchableRegion);
 }
 
 void Layer::fillTouchOcclusionMode(WindowInfo& info) {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 991256b..605a27e 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -959,6 +959,19 @@
     bool usingRelativeZ(LayerVector::StateSet) const;
 
     virtual ui::Transform getInputTransform() const;
+    /**
+     * Get the bounds in layer space within which this layer can receive input.
+     *
+     * These bounds are used to:
+     * - Determine the input frame for the layer to be used for occlusion detection; and
+     * - Determine the coordinate space within which the layer will receive input. The top-left of
+     *   this rect will be the origin of the coordinate space that the input events sent to the
+     *   layer will be in (prior to accounting for surface insets).
+     *
+     * The layer can still receive touch input if these bounds are invalid if
+     * "replaceTouchableRegionWithCrop" is specified. In this case, the layer will receive input
+     * in this layer's space, regardless of the specified crop layer.
+     */
     virtual Rect getInputBounds() const;
 
     // constant
@@ -1080,7 +1093,7 @@
     void fillTouchOcclusionMode(gui::WindowInfo& info);
 
     // Fills in the frame and transform info for the gui::WindowInfo.
-    void fillInputFrameInfo(gui::WindowInfo&, const ui::Transform& displayTransform);
+    void fillInputFrameInfo(gui::WindowInfo&, const ui::Transform& screenToDisplay);
 
     // Cached properties computed from drawing state
     // Effective transform taking into account parent transforms and any parent scaling, which is
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 73a5f58..3459a8f 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1109,6 +1109,8 @@
         return type == hal::ContentType::GAME;
     });
 
+    info->preferredBootDisplayMode = display->getPreferredBootModeId();
+
     return NO_ERROR;
 }
 
@@ -1454,20 +1456,6 @@
     return future.get();
 }
 
-status_t SurfaceFlinger::getPreferredBootDisplayMode(const sp<IBinder>& displayToken,
-                                                     ui::DisplayModeId* id) {
-    auto future = mScheduler->schedule([=]() MAIN_THREAD mutable -> status_t {
-        if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
-            *id = getHwComposer().getPreferredBootDisplayMode(*displayId);
-            return NO_ERROR;
-        } else {
-            ALOGE("%s: Invalid display token %p", __FUNCTION__, displayToken.get());
-            return BAD_VALUE;
-        }
-    });
-    return future.get();
-}
-
 void SurfaceFlinger::setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) {
     const char* const whence = __func__;
     static_cast<void>(mScheduler->schedule([=]() MAIN_THREAD {
@@ -2535,6 +2523,7 @@
     mTimeStats->recordDisplayEventConnectionCount(sfConnections + appConnections);
 
     if (isDisplayConnected && !display->isPoweredOn()) {
+        getRenderEngine().cleanupPostRender();
         return;
     }
 
@@ -5407,7 +5396,6 @@
         case GET_BOOT_DISPLAY_MODE_SUPPORT:
         case SET_BOOT_DISPLAY_MODE:
         case CLEAR_BOOT_DISPLAY_MODE:
-        case GET_PREFERRED_BOOT_DISPLAY_MODE:
         case GET_AUTO_LOW_LATENCY_MODE_SUPPORT:
         case SET_AUTO_LOW_LATENCY_MODE:
         case GET_GAME_CONTENT_TYPE_SUPPORT:
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 0c990d8..c6a4d85 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -537,8 +537,6 @@
     status_t getBootDisplayModeSupport(bool* outSupport) const override;
     status_t setBootDisplayMode(const sp<IBinder>& displayToken, ui::DisplayModeId id) override;
     status_t clearBootDisplayMode(const sp<IBinder>& displayToken) override;
-    status_t getPreferredBootDisplayMode(const sp<IBinder>& displayToken,
-                                         ui::DisplayModeId* id) override;
     void setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) override;
     void setGameContentType(const sp<IBinder>& displayToken, bool on) override;
     void setPowerMode(const sp<IBinder>& displayToken, int mode) override;
diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp
new file mode 100644
index 0000000..0ead163
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/Android.bp
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2021 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.
+ *
+ */
+
+cc_defaults {
+    name: "surfaceflinger_fuzz_defaults",
+    include_dirs: [
+        "frameworks/native/services/surfaceflinger/tests/unittests",
+    ],
+    static_libs: [
+        "android.hardware.graphics.composer@2.1-resources",
+        "libgmock",
+        "libgui_mocks",
+        "libgmock_ndk",
+        "libgmock_main",
+        "libgtest_ndk_c++",
+        "libgmock_main_ndk",
+        "librenderengine_mocks",
+        "perfetto_trace_protos",
+        "libcompositionengine_mocks",
+        "perfetto_trace_protos",
+    ],
+    shared_libs: [
+        "libprotoutil",
+        "libstatssocket",
+        "libstatspull",
+        "libtimestats",
+        "libtimestats_proto",
+        "libprotobuf-cpp-full",
+        "android.hardware.graphics.mapper@2.0",
+        "android.hardware.graphics.mapper@3.0",
+        "android.hardware.graphics.mapper@4.0",
+    ],
+    srcs: [
+        ":libsurfaceflinger_sources",
+        ":libsurfaceflinger_mock_sources",
+    ],
+    defaults: [
+        "libsurfaceflinger_defaults",
+    ],
+    header_libs: [
+        "libui_fuzzableDataspaces_headers",
+        "libsurfaceflinger_headers",
+        "libui_headers",
+    ],
+    cflags: [
+        "-Wno-unused-result",
+        "-Wno-conversion",
+        "-Wno-sign-compare",
+    ],
+    fuzz_config: {
+        cc: [
+            "android-media-fuzzing-reports@google.com",
+        ],
+        componentid: 155276,
+    },
+}
+
+cc_fuzz {
+    name: "surfaceflinger_fuzzer",
+    defaults: [
+        "surfaceflinger_fuzz_defaults",
+    ],
+    srcs: [
+        "surfaceflinger_fuzzer.cpp",
+    ],
+}
+
+cc_fuzz {
+    name: "surfaceflinger_displayhardware_fuzzer",
+    defaults: [
+        "surfaceflinger_fuzz_defaults",
+    ],
+    srcs: [
+        "surfaceflinger_displayhardware_fuzzer.cpp",
+    ],
+    header_libs: [
+        "android.hardware.graphics.composer@2.4-command-buffer",
+        "android.hardware.graphics.composer@2.4-hal",
+    ],
+}
diff --git a/services/surfaceflinger/fuzzer/README.md b/services/surfaceflinger/fuzzer/README.md
new file mode 100644
index 0000000..4ecf770
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/README.md
@@ -0,0 +1,53 @@
+# Fuzzers for SurfaceFlinger
+## Table of contents
++ [SurfaceFlinger](#SurfaceFlinger)
++ [DisplayHardware](#DisplayHardware)
+
+# <a name="SurfaceFlinger"></a> Fuzzer for SurfaceFlinger
+
+SurfaceFlinger supports the following data sources:
+1. Pixel Formats (parameter name: `defaultCompositionPixelFormat`)
+2. Data Spaces (parameter name: `defaultCompositionDataspace`)
+3. Rotations (parameter name: `internalDisplayOrientation`)
+3. Surface composer tags (parameter name: `onTransact`)
+
+You can find the possible values in the fuzzer's source code.
+
+#### Steps to run
+1. Build the fuzzer
+```
+  $ mm -j$(nproc) surfaceflinger_fuzzer
+```
+2. To run on device
+```
+  $ adb sync data
+  $ adb shell /data/fuzz/arm64/surfaceflinger_fuzzer/surfaceflinger_fuzzer
+```
+
+# <a name="DisplayHardware"></a> Fuzzer for DisplayHardware
+
+DisplayHardware supports the following parameters:
+1. Hal Capability (parameter name: `hasCapability`)
+2. Hal BlendMode (parameter name: `setBlendMode`)
+3. Hal Composition (parameter name: `setCompositionType`)
+4. Hal Display Capability (parameter name: `hasDisplayCapability`)
+5. Composition Types (parameter name: `prepareFrame`)
+6. Color Modes (parameter name: `setActiveColorMode`)
+7. Render Intents (parameter name: `setActiveColorMode`)
+8. Power Modes (parameter name: `setPowerMode`)
+9. Content Types (parameter name: `setContentType`)
+10. Data Space (parameter name: `setDataspace`)
+11. Transforms (parameter name: `setLayerTransform`)
+
+You can find the possible values in the fuzzer's source code.
+
+#### Steps to run
+1. Build the fuzzer
+```
+  $ mm -j$(nproc) surfaceflinger_displayhardware_fuzzer
+```
+2. Run on device
+```
+  $ adb sync data
+  $ adb shell /data/fuzz/arm64/surfaceflinger_displayhardware_fuzzer/surfaceflinger_displayhardware_fuzzer
+```
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
new file mode 100644
index 0000000..816d2f1
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
@@ -0,0 +1,657 @@
+/*
+ * Copyright 2021 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 <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <compositionengine/impl/OutputCompositionState.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <gui/BLASTBufferQueue.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/IProducerListener.h>
+#include <gui/LayerDebugInfo.h>
+#include <gui/SurfaceComposerClient.h>
+#include <hidl/ServiceManagement.h>
+#include <hwbinder/ProcessState.h>
+#include <ui/DisplayIdentification.h>
+
+#include "DisplayHardware/AidlComposerHal.h"
+#include "DisplayHardware/DisplayMode.h"
+#include "DisplayHardware/FramebufferSurface.h"
+#include "DisplayHardware/HWComposer.h"
+#include "DisplayHardware/PowerAdvisor.h"
+#include "DisplayHardware/VirtualDisplaySurface.h"
+#include "SurfaceFlinger.h"
+#include "surfaceflinger_displayhardware_fuzzer_utils.h"
+
+#include <FuzzableDataspaces.h>
+
+namespace android::fuzz {
+
+using namespace android::hardware::graphics::common;
+using namespace android::hardware::graphics::composer;
+namespace hal = android::hardware::graphics::composer::hal;
+using Config = hal::V2_1::Config;
+using Display = hal::V2_1::Display;
+using RenderIntent = V1_1::RenderIntent;
+using IComposerClient = hal::V2_4::IComposerClient;
+using VsyncPeriodChangeTimeline = hal::V2_4::VsyncPeriodChangeTimeline;
+using PerFrameMetadata = IComposerClient::PerFrameMetadata;
+using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob;
+using Vsync = IComposerClient::Vsync;
+
+static constexpr hal::Transform kTransforms[] = {hal::Transform::FLIP_H, hal::Transform::FLIP_V,
+                                                 hal::Transform::ROT_90, hal::Transform::ROT_180,
+                                                 hal::Transform::ROT_270};
+
+static constexpr hal::Capability kCapability[] = {hal::Capability::INVALID,
+                                                  hal::Capability::SIDEBAND_STREAM,
+                                                  hal::Capability::SKIP_CLIENT_COLOR_TRANSFORM,
+                                                  hal::Capability::PRESENT_FENCE_IS_NOT_RELIABLE};
+
+static constexpr hal::BlendMode kBlendModes[] = {hal::BlendMode::INVALID, hal::BlendMode::NONE,
+                                                 hal::BlendMode::PREMULTIPLIED,
+                                                 hal::BlendMode::COVERAGE};
+
+static constexpr Composition kCompositions[] = {Composition::INVALID, Composition::CLIENT,
+                                                Composition::DEVICE,  Composition::SOLID_COLOR,
+                                                Composition::CURSOR,  Composition::SIDEBAND};
+
+static constexpr DisplayCapability kDisplayCapability[] =
+        {DisplayCapability::INVALID,
+         DisplayCapability::SKIP_CLIENT_COLOR_TRANSFORM,
+         DisplayCapability::DOZE,
+         DisplayCapability::BRIGHTNESS,
+         DisplayCapability::PROTECTED_CONTENTS,
+         DisplayCapability::AUTO_LOW_LATENCY_MODE};
+
+static constexpr VirtualDisplaySurface::CompositionType kCompositionTypes[] =
+        {VirtualDisplaySurface::CompositionType::Unknown,
+         VirtualDisplaySurface::CompositionType::Gpu, VirtualDisplaySurface::CompositionType::Hwc,
+         VirtualDisplaySurface::CompositionType::Mixed};
+
+static constexpr ui::RenderIntent kRenderIntents[] = {ui::RenderIntent::COLORIMETRIC,
+                                                      ui::RenderIntent::ENHANCE,
+                                                      ui::RenderIntent::TONE_MAP_COLORIMETRIC,
+                                                      ui::RenderIntent::TONE_MAP_ENHANCE};
+
+static constexpr hal::PowerMode kPowerModes[] = {hal::PowerMode::OFF, hal::PowerMode::DOZE,
+                                                 hal::PowerMode::DOZE_SUSPEND, hal::PowerMode::ON,
+                                                 hal::PowerMode::ON_SUSPEND};
+
+static constexpr hal::ContentType kContentTypes[] = {hal::ContentType::NONE,
+                                                     hal::ContentType::GRAPHICS,
+                                                     hal::ContentType::PHOTO,
+                                                     hal::ContentType::CINEMA,
+                                                     hal::ContentType::GAME};
+
+const unsigned char kInternalEdid[] =
+        "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x00\x00\x00\x00"
+        "\x00\x15\x01\x03\x80\x1a\x10\x78\x0a\xd3\xe5\x95\x5c\x60\x90\x27"
+        "\x19\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
+        "\x01\x01\x01\x01\x01\x01\x9e\x1b\x00\xa0\x50\x20\x12\x30\x10\x30"
+        "\x13\x00\x05\xa3\x10\x00\x00\x19\x00\x00\x00\x0f\x00\x00\x00\x00"
+        "\x00\x00\x00\x00\x00\x23\x87\x02\x64\x00\x00\x00\x00\xfe\x00\x53"
+        "\x41\x4d\x53\x55\x4e\x47\x0a\x20\x20\x20\x20\x20\x00\x00\x00\xfe"
+        "\x00\x31\x32\x31\x41\x54\x31\x31\x2d\x38\x30\x31\x0a\x20\x00\x45";
+
+static constexpr hal::HWConfigId kActiveConfig = 0;
+
+class DisplayHardwareFuzzer {
+public:
+    DisplayHardwareFuzzer(const uint8_t* data, size_t size) : mFdp(data, size) {
+        mPhysicalDisplayId = SurfaceComposerClient::getInternalDisplayId().value();
+    };
+    void process();
+
+private:
+    void invokeComposer();
+    void invokeDisplayIdentification();
+    void invokeLayer(HWC2::Layer* layer);
+    void setSidebandStream(HWC2::Layer* layer);
+    void setCursorPosition(HWC2::Layer* layer);
+    void setBuffer(HWC2::Layer* layer);
+    void setSurfaceDamage(HWC2::Layer* layer);
+    void setDisplayFrame(HWC2::Layer* layer);
+    void setVisibleRegion(HWC2::Layer* layer);
+    void setLayerGenericMetadata(HWC2::Layer* layer);
+    void invokeFrameBufferSurface();
+    void invokeVirtualDisplaySurface();
+    void invokeAidlComposer();
+    Display createVirtualDisplay(Hwc2::AidlComposer*);
+    void validateDisplay(Hwc2::AidlComposer*, Display);
+    void presentOrValidateDisplay(Hwc2::AidlComposer*, Display);
+    void setOutputBuffer(Hwc2::AidlComposer*, Display);
+    void setLayerSidebandStream(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
+    void invokeComposerHal2_2(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
+    void invokeComposerHal2_3(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
+    void invokeComposerHal2_4(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
+    void getDisplayVsyncPeriod();
+    void setActiveModeWithConstraints();
+    void getDisplayIdentificationData();
+    void dumpHwc();
+    void getDisplayedContentSamplingAttributes(HalDisplayId);
+    void getDeviceCompositionChanges(HalDisplayId);
+    void getHdrCapabilities(HalDisplayId);
+    void getDisplayedContentSample(HalDisplayId);
+    void getSupportedContentTypes();
+    ui::Size getFuzzedSize();
+    mat4 getFuzzedMatrix();
+
+    DisplayIdGenerator<HalVirtualDisplayId> mGenerator;
+    FuzzedDataProvider mFdp;
+    PhysicalDisplayId mPhysicalDisplayId;
+    android::impl::HWComposer mHwc{std::make_unique<Hwc2::mock::Composer>()};
+};
+
+void DisplayHardwareFuzzer::validateDisplay(Hwc2::AidlComposer* composer, Display display) {
+    uint32_t outNumTypes, outNumRequests;
+    composer->validateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), &outNumTypes,
+                              &outNumRequests);
+}
+
+void DisplayHardwareFuzzer::presentOrValidateDisplay(Hwc2::AidlComposer* composer,
+                                                     Display display) {
+    int32_t outPresentFence;
+    uint32_t outNumTypes, outNumRequests, state;
+    composer->presentOrValidateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), &outNumTypes,
+                                       &outNumRequests, &outPresentFence, &state);
+}
+
+void DisplayHardwareFuzzer::setOutputBuffer(Hwc2::AidlComposer* composer, Display display) {
+    const native_handle_t buffer{};
+    composer->setOutputBuffer(display, &buffer, mFdp.ConsumeIntegral<int32_t>() /*releaseFence*/);
+}
+
+void DisplayHardwareFuzzer::setLayerSidebandStream(Hwc2::AidlComposer* composer, Display display,
+                                                   Hwc2::V2_4::hal::Layer outLayer) {
+    const native_handle_t stream{};
+    composer->setLayerSidebandStream(display, outLayer, &stream);
+}
+
+Display DisplayHardwareFuzzer::createVirtualDisplay(Hwc2::AidlComposer* composer) {
+    namespace types = hardware::graphics::common;
+    using types::V1_2::PixelFormat;
+    PixelFormat format{};
+    Display display;
+    composer->createVirtualDisplay(mFdp.ConsumeIntegral<uint32_t>() /*width*/,
+                                   mFdp.ConsumeIntegral<uint32_t>() /*height*/, &format, &display);
+    return display;
+}
+
+void DisplayHardwareFuzzer::getDisplayVsyncPeriod() {
+    nsecs_t outVsyncPeriod;
+    mHwc.getDisplayVsyncPeriod(mPhysicalDisplayId, &outVsyncPeriod);
+}
+
+void DisplayHardwareFuzzer::setActiveModeWithConstraints() {
+    hal::VsyncPeriodChangeTimeline outTimeline;
+    mHwc.setActiveModeWithConstraints(mPhysicalDisplayId, kActiveConfig, {} /*constraints*/,
+                                      &outTimeline);
+}
+
+void DisplayHardwareFuzzer::getDisplayIdentificationData() {
+    uint8_t outPort;
+    DisplayIdentificationData outData;
+    mHwc.getDisplayIdentificationData(kHwDisplayId, &outPort, &outData);
+}
+
+void DisplayHardwareFuzzer::dumpHwc() {
+    std::string string = mFdp.ConsumeRandomLengthString().c_str();
+    mHwc.dump(string);
+}
+
+void DisplayHardwareFuzzer::getDeviceCompositionChanges(HalDisplayId halDisplayID) {
+    std::optional<impl::HWComposer::DeviceRequestedChanges> outChanges;
+    mHwc.getDeviceCompositionChanges(halDisplayID,
+                                     mFdp.ConsumeBool() /*frameUsesClientComposition*/,
+                                     std::chrono::steady_clock::now(), FenceTime::NO_FENCE,
+                                     mFdp.ConsumeIntegral<nsecs_t>(), &outChanges);
+}
+
+void DisplayHardwareFuzzer::getDisplayedContentSamplingAttributes(HalDisplayId halDisplayID) {
+    uint8_t outComponentMask;
+    ui::Dataspace dataSpace;
+    ui::PixelFormat pixelFormat;
+    mHwc.getDisplayedContentSamplingAttributes(halDisplayID, &pixelFormat, &dataSpace,
+                                               &outComponentMask);
+}
+
+void DisplayHardwareFuzzer::getHdrCapabilities(HalDisplayId halDisplayID) {
+    HdrCapabilities outCapabilities;
+    mHwc.getHdrCapabilities(halDisplayID, &outCapabilities);
+}
+
+void DisplayHardwareFuzzer::getDisplayedContentSample(HalDisplayId halDisplayID) {
+    DisplayedFrameStats outStats;
+    mHwc.getDisplayedContentSample(halDisplayID, mFdp.ConsumeIntegral<uint64_t>() /* maxFrames*/,
+                                   mFdp.ConsumeIntegral<uint64_t>() /*timestamps*/, &outStats);
+}
+
+void DisplayHardwareFuzzer::getSupportedContentTypes() {
+    std::vector<hal::ContentType> contentType{};
+    mHwc.getSupportedContentTypes(mPhysicalDisplayId, &contentType);
+}
+
+void DisplayHardwareFuzzer::invokeAidlComposer() {
+    hardware::ProcessState::self()->startThreadPool();
+    ProcessState::self()->startThreadPool();
+
+    if (!Hwc2::AidlComposer::isDeclared("default")) {
+        return;
+    }
+
+    Hwc2::AidlComposer composer("default");
+
+    android::hardware::graphics::composer::hal::TestHWC2ComposerCallback composerCallback{};
+    composer.registerCallback(composerCallback);
+
+    Display display = createVirtualDisplay(&composer);
+
+    composer.acceptDisplayChanges(display);
+
+    Hwc2::V2_4::hal::Layer outLayer;
+    composer.createLayer(display, &outLayer);
+
+    int32_t outPresentFence;
+    composer.presentDisplay(display, &outPresentFence);
+
+    composer.setActiveConfig(display, Config{});
+
+    composer.setClientTarget(display, mFdp.ConsumeIntegral<uint32_t>(), sp<GraphicBuffer>(),
+                             mFdp.ConsumeIntegral<int32_t>(), mFdp.PickValueInArray(kDataspaces),
+                             {});
+
+    composer.setColorMode(display, mFdp.PickValueInArray(kColormodes),
+                          mFdp.PickValueInArray(kRenderIntents));
+
+    setOutputBuffer(&composer, display);
+
+    composer.setPowerMode(display, mFdp.PickValueInArray(kPowerModes));
+    composer.setVsyncEnabled(display, mFdp.ConsumeBool() ? Vsync::ENABLE : Vsync::DISABLE);
+
+    composer.setClientTargetSlotCount(display);
+
+    validateDisplay(&composer, display);
+
+    presentOrValidateDisplay(&composer, display);
+
+    composer.setCursorPosition(display, outLayer, mFdp.ConsumeIntegral<uint8_t>() /*x*/,
+                               mFdp.ConsumeIntegral<uint8_t>() /*y*/);
+
+    composer.setLayerBuffer(display, outLayer, mFdp.ConsumeIntegral<uint32_t>() /*slot*/,
+                            sp<GraphicBuffer>(), mFdp.ConsumeIntegral<int32_t>() /*acquireFence*/);
+
+    composer.setLayerSurfaceDamage(display, outLayer, {} /*damage*/);
+
+    composer.setLayerBlendMode(display, outLayer, mFdp.PickValueInArray(kBlendModes));
+
+    composer.setLayerColor(display, outLayer,
+                           {mFdp.ConsumeFloatingPoint<float>() /*red*/,
+                            mFdp.ConsumeFloatingPoint<float>() /*green*/,
+                            mFdp.ConsumeFloatingPoint<float>() /*blue*/,
+                            mFdp.ConsumeFloatingPoint<float>() /*alpha*/});
+    composer.setLayerCompositionType(display, outLayer, mFdp.PickValueInArray(kCompositions));
+    composer.setLayerDataspace(display, outLayer, mFdp.PickValueInArray(kDataspaces));
+    composer.setLayerDisplayFrame(display, outLayer, {} /*frame*/);
+    composer.setLayerPlaneAlpha(display, outLayer, mFdp.ConsumeFloatingPoint<float>());
+
+    setLayerSidebandStream(&composer, display, outLayer);
+
+    composer.setLayerSourceCrop(display, outLayer, {} /*crop*/);
+
+    composer.setLayerTransform(display, outLayer, mFdp.PickValueInArray(kTransforms));
+
+    composer.setLayerVisibleRegion(display, outLayer, std::vector<IComposerClient::Rect>{});
+    composer.setLayerZOrder(display, outLayer, mFdp.ConsumeIntegral<uint32_t>());
+
+    invokeComposerHal2_2(&composer, display, outLayer);
+    invokeComposerHal2_3(&composer, display, outLayer);
+    invokeComposerHal2_4(&composer, display, outLayer);
+
+    composer.executeCommands();
+    composer.resetCommands();
+
+    composer.destroyLayer(display, outLayer);
+    composer.destroyVirtualDisplay(display);
+}
+
+void DisplayHardwareFuzzer::invokeComposerHal2_2(Hwc2::AidlComposer* composer, Display display,
+                                                 Hwc2::V2_4::hal::Layer outLayer) {
+    const std::vector<PerFrameMetadata> perFrameMetadatas;
+    composer->setLayerPerFrameMetadata(display, outLayer, perFrameMetadatas);
+
+    composer->getPerFrameMetadataKeys(display);
+    std::vector<RenderIntent> outRenderIntents;
+
+    composer->getRenderIntents(display, mFdp.PickValueInArray(kColormodes), &outRenderIntents);
+    mat4 outMatrix;
+    composer->getDataspaceSaturationMatrix(mFdp.PickValueInArray(kDataspaces), &outMatrix);
+}
+
+void DisplayHardwareFuzzer::invokeComposerHal2_3(Hwc2::AidlComposer* composer, Display display,
+                                                 Hwc2::V2_4::hal::Layer outLayer) {
+    composer->setDisplayContentSamplingEnabled(display, mFdp.ConsumeBool() /*enabled*/,
+                                               mFdp.ConsumeIntegral<uint8_t>() /*componentMask*/,
+                                               mFdp.ConsumeIntegral<uint64_t>() /*maxFrames*/);
+
+    DisplayedFrameStats outStats;
+    composer->getDisplayedContentSample(display, mFdp.ConsumeIntegral<uint64_t>() /*maxFrames*/,
+                                        mFdp.ConsumeIntegral<uint64_t>() /*timestamp*/, &outStats);
+
+    composer->setLayerPerFrameMetadataBlobs(display, outLayer, std::vector<PerFrameMetadataBlob>{});
+
+    composer->setDisplayBrightness(display, mFdp.ConsumeFloatingPoint<float>(),
+                                   Hwc2::Composer::DisplayBrightnessOptions{
+                                           .applyImmediately = mFdp.ConsumeIntegral<bool>()});
+}
+
+void DisplayHardwareFuzzer::invokeComposerHal2_4(Hwc2::AidlComposer* composer, Display display,
+                                                 Hwc2::V2_4::hal::Layer outLayer) {
+    VsyncPeriodChangeTimeline outTimeline;
+    composer->setActiveConfigWithConstraints(display, Config{},
+                                             IComposerClient::VsyncPeriodChangeConstraints{},
+                                             &outTimeline);
+
+    composer->setAutoLowLatencyMode(display, mFdp.ConsumeBool());
+
+    composer->setContentType(display, mFdp.PickValueInArray(kContentTypes));
+
+    std::vector<uint8_t> value;
+    value.push_back(mFdp.ConsumeIntegral<uint8_t>());
+    composer->setLayerGenericMetadata(display, outLayer, mFdp.ConsumeRandomLengthString() /*key*/,
+                                      mFdp.ConsumeBool() /*mandatory*/, value);
+}
+
+ui::Size DisplayHardwareFuzzer::getFuzzedSize() {
+    ui::Size size{mFdp.ConsumeIntegral<int32_t>() /*width*/,
+                  mFdp.ConsumeIntegral<int32_t>() /*height*/};
+    return size;
+}
+
+mat4 DisplayHardwareFuzzer::getFuzzedMatrix() {
+    mat4 matrix{mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>()};
+    return matrix;
+}
+
+void DisplayHardwareFuzzer::setCursorPosition(HWC2::Layer* layer) {
+    layer->setCursorPosition(mFdp.ConsumeIntegral<int32_t>() /*x*/,
+                             mFdp.ConsumeIntegral<int32_t>() /*y*/);
+}
+
+void DisplayHardwareFuzzer::setBuffer(HWC2::Layer* layer) {
+    layer->setBuffer(mFdp.ConsumeIntegral<uint32_t>() /*slot*/, sp<GraphicBuffer>(),
+                     sp<Fence>::make());
+}
+
+void DisplayHardwareFuzzer::setSurfaceDamage(HWC2::Layer* layer) {
+    Rect rhs{mFdp.ConsumeIntegral<uint32_t>() /*width*/,
+             mFdp.ConsumeIntegral<uint32_t>() /*height*/};
+    const Region damage{rhs};
+    layer->setSurfaceDamage(damage);
+}
+
+void DisplayHardwareFuzzer::setVisibleRegion(HWC2::Layer* layer) {
+    uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
+    uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
+    Rect rect{width, height};
+    const Region region{rect};
+    layer->setVisibleRegion(region);
+}
+
+void DisplayHardwareFuzzer::setDisplayFrame(HWC2::Layer* layer) {
+    uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
+    uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
+    const Rect frame{width, height};
+    layer->setDisplayFrame(frame);
+}
+
+void DisplayHardwareFuzzer::setLayerGenericMetadata(HWC2::Layer* layer) {
+    std::vector<uint8_t> value;
+    value.push_back(mFdp.ConsumeIntegral<uint8_t>());
+    layer->setLayerGenericMetadata(mFdp.ConsumeRandomLengthString().c_str() /*name*/,
+                                   mFdp.ConsumeBool() /*mandatory*/, value);
+}
+
+void DisplayHardwareFuzzer::setSidebandStream(HWC2::Layer* layer) {
+    const native_handle_t stream{};
+    layer->setSidebandStream(&stream);
+}
+
+void DisplayHardwareFuzzer::invokeLayer(HWC2::Layer* layer) {
+    setCursorPosition(layer);
+    setBuffer(layer);
+    setSurfaceDamage(layer);
+
+    layer->setBlendMode(mFdp.PickValueInArray(kBlendModes));
+    layer->setColor({mFdp.ConsumeFloatingPoint<float>() /*red*/,
+                     mFdp.ConsumeFloatingPoint<float>() /*green*/,
+                     mFdp.ConsumeFloatingPoint<float>() /*blue*/,
+                     mFdp.ConsumeFloatingPoint<float>() /*alpha*/});
+    layer->setCompositionType(mFdp.PickValueInArray(kCompositions));
+    layer->setDataspace(mFdp.PickValueInArray(kDataspaces));
+
+    layer->setPerFrameMetadata(mFdp.ConsumeIntegral<int32_t>(), getFuzzedHdrMetadata(&mFdp));
+    setDisplayFrame(layer);
+
+    layer->setPlaneAlpha(mFdp.ConsumeFloatingPoint<float>());
+
+    setSidebandStream(layer);
+
+    layer->setSourceCrop(getFuzzedFloatRect(&mFdp));
+    layer->setTransform(mFdp.PickValueInArray(kTransforms));
+
+    setVisibleRegion(layer);
+
+    layer->setZOrder(mFdp.ConsumeIntegral<uint32_t>());
+
+    layer->setColorTransform(getFuzzedMatrix());
+
+    setLayerGenericMetadata(layer);
+}
+
+void DisplayHardwareFuzzer::invokeFrameBufferSurface() {
+    sp<IGraphicBufferProducer> bqProducer = sp<mock::GraphicBufferProducer>::make();
+    sp<IGraphicBufferConsumer> bqConsumer;
+    BufferQueue::createBufferQueue(&bqProducer, &bqConsumer);
+
+    sp<FramebufferSurface> surface =
+            new FramebufferSurface(mHwc, mPhysicalDisplayId, bqConsumer, getFuzzedSize() /*size*/,
+                                   getFuzzedSize() /*maxSize*/);
+    surface->beginFrame(mFdp.ConsumeBool());
+
+    surface->prepareFrame(mFdp.PickValueInArray(kCompositionTypes));
+    surface->advanceFrame();
+    surface->onFrameCommitted();
+    String8 result = String8(mFdp.ConsumeRandomLengthString().c_str());
+    surface->dumpAsString(result);
+    surface->resizeBuffers(getFuzzedSize());
+    surface->getClientTargetAcquireFence();
+}
+
+void DisplayHardwareFuzzer::invokeVirtualDisplaySurface() {
+    DisplayIdGenerator<HalVirtualDisplayId> mGenerator;
+    VirtualDisplayId VirtualDisplayId = mGenerator.generateId().value();
+
+    sp<SurfaceComposerClient> mClient = new SurfaceComposerClient();
+    sp<SurfaceControl> mSurfaceControl =
+            mClient->createSurface(String8("TestSurface"), 100, 100, PIXEL_FORMAT_RGBA_8888,
+                                   ISurfaceComposerClient::eFXSurfaceBufferState,
+                                   /*parent*/ nullptr);
+
+    sp<BLASTBufferQueue> mBlastBufferQueueAdapter =
+            new BLASTBufferQueue("TestBLASTBufferQueue", mSurfaceControl, 100, 100,
+                                 PIXEL_FORMAT_RGBA_8888);
+
+    sp<IGraphicBufferProducer> sink = mBlastBufferQueueAdapter->getIGraphicBufferProducer();
+    sp<IGraphicBufferProducer> bqProducer = mBlastBufferQueueAdapter->getIGraphicBufferProducer();
+    sp<IGraphicBufferConsumer> bqConsumer;
+    BufferQueue::createBufferQueue(&bqProducer, &bqConsumer);
+    BufferQueue::createBufferQueue(&sink, &bqConsumer);
+
+    sp<VirtualDisplaySurface> surface =
+            new VirtualDisplaySurface(mHwc, VirtualDisplayId, sink, bqProducer, bqConsumer,
+                                      mFdp.ConsumeRandomLengthString().c_str() /*name*/);
+
+    surface->beginFrame(mFdp.ConsumeBool());
+    surface->prepareFrame(mFdp.PickValueInArray(kCompositionTypes));
+    surface->resizeBuffers(getFuzzedSize());
+    surface->getClientTargetAcquireFence();
+    surface->advanceFrame();
+    surface->onFrameCommitted();
+    String8 result = String8(mFdp.ConsumeRandomLengthString().c_str());
+    surface->dumpAsString(result);
+}
+
+void DisplayHardwareFuzzer::invokeComposer() {
+    HalVirtualDisplayId halVirtualDisplayId = mGenerator.generateId().value();
+    HalDisplayId halDisplayID = HalDisplayId{halVirtualDisplayId};
+
+    android::hardware::graphics::composer::hal::TestHWC2ComposerCallback composerCallback{};
+    mHwc.setCallback(composerCallback);
+
+    ui::PixelFormat pixelFormat{};
+    if (!mHwc.allocateVirtualDisplay(halVirtualDisplayId, getFuzzedSize(), &pixelFormat)) {
+        return;
+    }
+
+    getDisplayIdentificationData();
+
+    mHwc.hasDisplayCapability(halDisplayID, mFdp.PickValueInArray(kDisplayCapability));
+
+    mHwc.allocatePhysicalDisplay(kHwDisplayId, mPhysicalDisplayId);
+
+    static auto hwcLayer = mHwc.createLayer(halDisplayID);
+    HWC2::Layer* layer = hwcLayer.get();
+    invokeLayer(layer);
+
+    getDeviceCompositionChanges(halDisplayID);
+
+    mHwc.setClientTarget(halDisplayID, mFdp.ConsumeIntegral<uint32_t>(), Fence::NO_FENCE,
+                         sp<GraphicBuffer>::make(), mFdp.PickValueInArray(kDataspaces));
+
+    mHwc.presentAndGetReleaseFences(halDisplayID, std::chrono::steady_clock::now(),
+                                    FenceTime::NO_FENCE);
+
+    mHwc.setPowerMode(mPhysicalDisplayId, mFdp.PickValueInArray(kPowerModes));
+
+    mHwc.setColorTransform(halDisplayID, getFuzzedMatrix());
+
+    mHwc.getPresentFence(halDisplayID);
+
+    mHwc.getLayerReleaseFence(halDisplayID, layer);
+
+    mHwc.setOutputBuffer(halVirtualDisplayId, sp<Fence>::make().get(), sp<GraphicBuffer>::make());
+
+    mHwc.clearReleaseFences(halDisplayID);
+
+    getHdrCapabilities(halDisplayID);
+
+    mHwc.getSupportedPerFrameMetadata(halDisplayID);
+
+    mHwc.getRenderIntents(halDisplayID, ui::ColorMode());
+
+    mHwc.getDataspaceSaturationMatrix(halDisplayID, ui::Dataspace());
+
+    getDisplayedContentSamplingAttributes(halDisplayID);
+
+    mHwc.setDisplayContentSamplingEnabled(halDisplayID, mFdp.ConsumeBool() /*enabled*/,
+                                          mFdp.ConsumeIntegral<uint8_t>() /*componentMask*/,
+                                          mFdp.ConsumeIntegral<uint64_t>() /*maxFrames*/);
+
+    getDisplayedContentSample(halDisplayID);
+
+    mHwc.setDisplayBrightness(mPhysicalDisplayId, mFdp.ConsumeFloatingPoint<float>(),
+                              Hwc2::Composer::DisplayBrightnessOptions{
+                                      .applyImmediately = mFdp.ConsumeIntegral<bool>()});
+
+    mHwc.onHotplug(kHwDisplayId, hal::Connection::CONNECTED);
+    mHwc.updatesDeviceProductInfoOnHotplugReconnect();
+
+    mHwc.onVsync(kHwDisplayId, mFdp.ConsumeIntegral<int64_t>());
+    mHwc.setVsyncEnabled(mPhysicalDisplayId,
+                         mFdp.ConsumeBool() ? hal::Vsync::ENABLE : hal::Vsync::DISABLE);
+
+    mHwc.isConnected(mPhysicalDisplayId);
+    mHwc.getModes(mPhysicalDisplayId);
+    mHwc.getActiveMode(mPhysicalDisplayId);
+    mHwc.getColorModes(mPhysicalDisplayId);
+    mHwc.hasCapability(mFdp.PickValueInArray(kCapability));
+
+    mHwc.setActiveColorMode(mPhysicalDisplayId, mFdp.PickValueInArray(kColormodes),
+                            mFdp.PickValueInArray(kRenderIntents));
+
+    mHwc.getDisplayConnectionType(mPhysicalDisplayId);
+    mHwc.isVsyncPeriodSwitchSupported(mPhysicalDisplayId);
+
+    getDisplayVsyncPeriod();
+
+    setActiveModeWithConstraints();
+
+    mHwc.setAutoLowLatencyMode(mPhysicalDisplayId, mFdp.ConsumeBool());
+
+    getSupportedContentTypes();
+
+    mHwc.setContentType(mPhysicalDisplayId, mFdp.PickValueInArray(kContentTypes));
+
+    dumpHwc();
+
+    mHwc.toPhysicalDisplayId(kHwDisplayId);
+    mHwc.fromPhysicalDisplayId(mPhysicalDisplayId);
+    mHwc.disconnectDisplay(halDisplayID);
+
+    static hal::HWDisplayId displayId = mFdp.ConsumeIntegral<hal::HWDisplayId>();
+    mHwc.onHotplug(displayId,
+                   mFdp.ConsumeBool() ? hal::Connection::DISCONNECTED : hal::Connection::CONNECTED);
+}
+
+template <size_t N>
+DisplayIdentificationData asDisplayIdentificationData(const unsigned char (&bytes)[N]) {
+    return DisplayIdentificationData(bytes, bytes + N - 1);
+}
+
+void DisplayHardwareFuzzer::invokeDisplayIdentification() {
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kInternalEdid);
+    isEdid(data);
+    parseEdid(data);
+    parseDisplayIdentificationData(mFdp.ConsumeIntegral<uint8_t>(), data);
+    getPnpId(getVirtualDisplayId(mFdp.ConsumeIntegral<uint32_t>()));
+    getPnpId(mFdp.ConsumeIntegral<uint8_t>());
+}
+
+void DisplayHardwareFuzzer::process() {
+    invokeComposer();
+    invokeAidlComposer();
+    invokeDisplayIdentification();
+    invokeFrameBufferSurface();
+    invokeVirtualDisplaySurface();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    DisplayHardwareFuzzer displayHardwareFuzzer(data, size);
+    displayHardwareFuzzer.process();
+    return 0;
+}
+
+} // namespace android::fuzz
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
new file mode 100644
index 0000000..6a6e3db
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2021 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 <utils/Condition.h>
+#include <chrono>
+#include <vector>
+
+#include <android/hardware/graphics/composer/2.4/IComposer.h>
+#include <composer-hal/2.1/ComposerClient.h>
+#include <composer-hal/2.2/ComposerClient.h>
+#include <composer-hal/2.3/ComposerClient.h>
+#include <composer-hal/2.4/ComposerClient.h>
+
+#include "DisplayHardware/HWC2.h"
+#include "surfaceflinger_fuzzers_utils.h"
+
+namespace {
+class LayerImpl;
+class Frame;
+class DelayedEventGenerator;
+} // namespace
+
+namespace android {
+class SurfaceComposerClient;
+} // namespace android
+
+namespace android::hardware::graphics::composer::hal {
+
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::HWC2::ComposerCallback;
+
+class ComposerCallbackBridge : public IComposerCallback {
+public:
+    ComposerCallbackBridge(ComposerCallback* callback, bool vsyncSwitchingSupported)
+          : mCallback(callback), mVsyncSwitchingSupported(vsyncSwitchingSupported) {}
+
+    Return<void> onHotplug(HWDisplayId display, Connection connection) override {
+        mCallback->onComposerHalHotplug(display, connection);
+        return Void();
+    }
+
+    Return<void> onRefresh(HWDisplayId display) override {
+        mCallback->onComposerHalRefresh(display);
+        return Void();
+    }
+
+    Return<void> onVsync(HWDisplayId display, int64_t timestamp) override {
+        if (!mVsyncSwitchingSupported) {
+            mCallback->onComposerHalVsync(display, timestamp, std::nullopt);
+        }
+        return Void();
+    }
+
+    Return<void> onVsync_2_4(HWDisplayId display, int64_t timestamp,
+                             VsyncPeriodNanos vsyncPeriodNanos) override {
+        if (mVsyncSwitchingSupported) {
+            mCallback->onComposerHalVsync(display, timestamp, vsyncPeriodNanos);
+        }
+        return Void();
+    }
+
+    Return<void> onVsyncPeriodTimingChanged(HWDisplayId display,
+                                            const VsyncPeriodChangeTimeline& timeline) override {
+        mCallback->onComposerHalVsyncPeriodTimingChanged(display, timeline);
+        return Void();
+    }
+
+    Return<void> onSeamlessPossible(HWDisplayId display) override {
+        mCallback->onComposerHalSeamlessPossible(display);
+        return Void();
+    }
+
+private:
+    ComposerCallback* const mCallback;
+    const bool mVsyncSwitchingSupported;
+};
+
+struct TestHWC2ComposerCallback : public HWC2::ComposerCallback {
+    virtual ~TestHWC2ComposerCallback() = default;
+    void onComposerHalHotplug(HWDisplayId, Connection){};
+    void onComposerHalRefresh(HWDisplayId) {}
+    void onComposerHalVsync(HWDisplayId, int64_t, std::optional<VsyncPeriodNanos>) {}
+    void onComposerHalVsyncPeriodTimingChanged(HWDisplayId, const VsyncPeriodChangeTimeline&) {}
+    void onComposerHalSeamlessPossible(HWDisplayId) {}
+    void onComposerHalVsyncIdle(HWDisplayId) {}
+};
+
+} // namespace android::hardware::graphics::composer::hal
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
new file mode 100644
index 0000000..4f89cd9
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2021 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 <FuzzableDataspaces.h>
+#include <binder/IServiceManager.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <ui/DisplayStatInfo.h>
+#include "surfaceflinger_fuzzers_utils.h"
+
+namespace android::fuzz {
+
+static constexpr LatchUnsignaledConfig kLatchUnsignaledConfig[] = {
+        LatchUnsignaledConfig::Always,
+        LatchUnsignaledConfig::Auto,
+        LatchUnsignaledConfig::Disabled,
+};
+
+static constexpr ui::PixelFormat kPixelFormats[] = {ui::PixelFormat::RGBA_8888,
+                                                    ui::PixelFormat::RGBX_8888,
+                                                    ui::PixelFormat::RGB_888,
+                                                    ui::PixelFormat::RGB_565,
+                                                    ui::PixelFormat::BGRA_8888,
+                                                    ui::PixelFormat::YCBCR_422_SP,
+                                                    ui::PixelFormat::YCRCB_420_SP,
+                                                    ui::PixelFormat::YCBCR_422_I,
+                                                    ui::PixelFormat::RGBA_FP16,
+                                                    ui::PixelFormat::RAW16,
+                                                    ui::PixelFormat::BLOB,
+                                                    ui::PixelFormat::IMPLEMENTATION_DEFINED,
+                                                    ui::PixelFormat::YCBCR_420_888,
+                                                    ui::PixelFormat::RAW_OPAQUE,
+                                                    ui::PixelFormat::RAW10,
+                                                    ui::PixelFormat::RAW12,
+                                                    ui::PixelFormat::RGBA_1010102,
+                                                    ui::PixelFormat::Y8,
+                                                    ui::PixelFormat::Y16,
+                                                    ui::PixelFormat::YV12,
+                                                    ui::PixelFormat::DEPTH_16,
+                                                    ui::PixelFormat::DEPTH_24,
+                                                    ui::PixelFormat::DEPTH_24_STENCIL_8,
+                                                    ui::PixelFormat::DEPTH_32F,
+                                                    ui::PixelFormat::DEPTH_32F_STENCIL_8,
+                                                    ui::PixelFormat::STENCIL_8,
+                                                    ui::PixelFormat::YCBCR_P010,
+                                                    ui::PixelFormat::HSV_888};
+
+static constexpr ui::Rotation kRotations[] = {ui::Rotation::Rotation0, ui::Rotation::Rotation90,
+                                              ui::Rotation::Rotation180, ui::Rotation::Rotation270};
+
+static constexpr BnSurfaceComposer::ISurfaceComposerTag kSurfaceComposerTags[]{
+        BnSurfaceComposer::BOOT_FINISHED,
+        BnSurfaceComposer::CREATE_CONNECTION,
+        BnSurfaceComposer::GET_STATIC_DISPLAY_INFO,
+        BnSurfaceComposer::CREATE_DISPLAY_EVENT_CONNECTION,
+        BnSurfaceComposer::CREATE_DISPLAY,
+        BnSurfaceComposer::DESTROY_DISPLAY,
+        BnSurfaceComposer::GET_PHYSICAL_DISPLAY_TOKEN,
+        BnSurfaceComposer::SET_TRANSACTION_STATE,
+        BnSurfaceComposer::AUTHENTICATE_SURFACE,
+        BnSurfaceComposer::GET_SUPPORTED_FRAME_TIMESTAMPS,
+        BnSurfaceComposer::GET_DISPLAY_MODES,
+        BnSurfaceComposer::GET_ACTIVE_DISPLAY_MODE,
+        BnSurfaceComposer::GET_DISPLAY_STATE,
+        BnSurfaceComposer::CAPTURE_DISPLAY,
+        BnSurfaceComposer::CAPTURE_LAYERS,
+        BnSurfaceComposer::CLEAR_ANIMATION_FRAME_STATS,
+        BnSurfaceComposer::GET_ANIMATION_FRAME_STATS,
+        BnSurfaceComposer::SET_POWER_MODE,
+        BnSurfaceComposer::GET_DISPLAY_STATS,
+        BnSurfaceComposer::GET_HDR_CAPABILITIES,
+        BnSurfaceComposer::GET_DISPLAY_COLOR_MODES,
+        BnSurfaceComposer::GET_ACTIVE_COLOR_MODE,
+        BnSurfaceComposer::SET_ACTIVE_COLOR_MODE,
+        BnSurfaceComposer::ENABLE_VSYNC_INJECTIONS,
+        BnSurfaceComposer::INJECT_VSYNC,
+        BnSurfaceComposer::GET_LAYER_DEBUG_INFO,
+        BnSurfaceComposer::GET_COMPOSITION_PREFERENCE,
+        BnSurfaceComposer::GET_COLOR_MANAGEMENT,
+        BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES,
+        BnSurfaceComposer::SET_DISPLAY_CONTENT_SAMPLING_ENABLED,
+        BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLE,
+        BnSurfaceComposer::GET_PROTECTED_CONTENT_SUPPORT,
+        BnSurfaceComposer::IS_WIDE_COLOR_DISPLAY,
+        BnSurfaceComposer::GET_DISPLAY_NATIVE_PRIMARIES,
+        BnSurfaceComposer::GET_PHYSICAL_DISPLAY_IDS,
+        BnSurfaceComposer::ADD_REGION_SAMPLING_LISTENER,
+        BnSurfaceComposer::REMOVE_REGION_SAMPLING_LISTENER,
+        BnSurfaceComposer::SET_DESIRED_DISPLAY_MODE_SPECS,
+        BnSurfaceComposer::GET_DESIRED_DISPLAY_MODE_SPECS,
+        BnSurfaceComposer::GET_DISPLAY_BRIGHTNESS_SUPPORT,
+        BnSurfaceComposer::SET_DISPLAY_BRIGHTNESS,
+        BnSurfaceComposer::CAPTURE_DISPLAY_BY_ID,
+        BnSurfaceComposer::NOTIFY_POWER_BOOST,
+        BnSurfaceComposer::SET_GLOBAL_SHADOW_SETTINGS,
+        BnSurfaceComposer::GET_AUTO_LOW_LATENCY_MODE_SUPPORT,
+        BnSurfaceComposer::SET_AUTO_LOW_LATENCY_MODE,
+        BnSurfaceComposer::GET_GAME_CONTENT_TYPE_SUPPORT,
+        BnSurfaceComposer::SET_GAME_CONTENT_TYPE,
+        BnSurfaceComposer::SET_FRAME_RATE,
+        BnSurfaceComposer::ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN,
+        BnSurfaceComposer::SET_FRAME_TIMELINE_INFO,
+        BnSurfaceComposer::ADD_TRANSACTION_TRACE_LISTENER,
+        BnSurfaceComposer::GET_GPU_CONTEXT_PRIORITY,
+        BnSurfaceComposer::GET_MAX_ACQUIRED_BUFFER_COUNT,
+        BnSurfaceComposer::GET_DYNAMIC_DISPLAY_INFO,
+        BnSurfaceComposer::ADD_FPS_LISTENER,
+        BnSurfaceComposer::REMOVE_FPS_LISTENER,
+        BnSurfaceComposer::OVERRIDE_HDR_TYPES,
+        BnSurfaceComposer::ADD_HDR_LAYER_INFO_LISTENER,
+        BnSurfaceComposer::REMOVE_HDR_LAYER_INFO_LISTENER,
+        BnSurfaceComposer::ON_PULL_ATOM,
+        BnSurfaceComposer::ADD_TUNNEL_MODE_ENABLED_LISTENER,
+        BnSurfaceComposer::REMOVE_TUNNEL_MODE_ENABLED_LISTENER,
+        BnSurfaceComposer::ADD_WINDOW_INFOS_LISTENER,
+        BnSurfaceComposer::REMOVE_WINDOW_INFOS_LISTENER,
+};
+
+static constexpr uint32_t kMinCode = 1000;
+static constexpr uint32_t kMaxCode = 1050;
+
+class SurfaceFlingerFuzzer {
+public:
+    SurfaceFlingerFuzzer(const uint8_t *data, size_t size) : mFdp(data, size) {
+        mFlinger = mTestableFlinger.flinger();
+    };
+    void process(const uint8_t *data, size_t size);
+
+private:
+    void setUp();
+    void invokeFlinger();
+    void setTransactionState();
+    void setInternalDisplayPrimaries();
+    void setDisplayStateLocked();
+    void onTransact(const uint8_t *data, size_t size);
+
+    FuzzedDataProvider mFdp;
+    TestableSurfaceFlinger mTestableFlinger;
+    sp<SurfaceFlinger> mFlinger = nullptr;
+};
+
+void SurfaceFlingerFuzzer::invokeFlinger() {
+    mFlinger->setSchedFifo(mFdp.ConsumeBool());
+    mFlinger->setSchedAttr(mFdp.ConsumeBool());
+    mFlinger->getServiceName();
+    mFlinger->hasSyncFramework = mFdp.ConsumeBool();
+    mFlinger->dispSyncPresentTimeOffset = mFdp.ConsumeIntegral<int64_t>();
+    mFlinger->useHwcForRgbToYuv = mFdp.ConsumeBool();
+    mFlinger->maxFrameBufferAcquiredBuffers = mFdp.ConsumeIntegral<int64_t>();
+    mFlinger->maxGraphicsWidth = mFdp.ConsumeIntegral<uint32_t>();
+    mFlinger->maxGraphicsHeight = mFdp.ConsumeIntegral<uint32_t>();
+    mFlinger->hasWideColorDisplay = mFdp.ConsumeBool();
+    mFlinger->internalDisplayOrientation = mFdp.PickValueInArray(kRotations);
+    mFlinger->useContextPriority = mFdp.ConsumeBool();
+
+    mFlinger->defaultCompositionDataspace = mFdp.PickValueInArray(kDataspaces);
+    mFlinger->defaultCompositionPixelFormat = mFdp.PickValueInArray(kPixelFormats);
+    mFlinger->wideColorGamutCompositionDataspace = mFdp.PickValueInArray(kDataspaces);
+    mFlinger->wideColorGamutCompositionPixelFormat = mFdp.PickValueInArray(kPixelFormats);
+
+    mFlinger->enableLatchUnsignaledConfig = mFdp.PickValueInArray(kLatchUnsignaledConfig);
+
+    mFlinger->scheduleComposite(mFdp.ConsumeBool()
+                                        ? scheduler::ISchedulerCallback::FrameHint::kActive
+                                        : scheduler::ISchedulerCallback::FrameHint::kNone);
+
+    mFlinger->scheduleRepaint();
+    mFlinger->scheduleSample();
+
+    uint32_t texture = mFlinger->getNewTexture();
+    mFlinger->deleteTextureAsync(texture);
+
+    sp<IBinder> handle = defaultServiceManager()->checkService(
+            String16(mFdp.ConsumeRandomLengthString().c_str()));
+    mFlinger->fromHandle(handle);
+    mFlinger->windowInfosReported();
+    mFlinger->disableExpensiveRendering();
+}
+
+void SurfaceFlingerFuzzer::setInternalDisplayPrimaries() {
+    ui::DisplayPrimaries primaries;
+    primaries.red.X = mFdp.ConsumeFloatingPoint<float>();
+    primaries.red.Y = mFdp.ConsumeFloatingPoint<float>();
+    primaries.red.Z = mFdp.ConsumeFloatingPoint<float>();
+    primaries.green.X = mFdp.ConsumeFloatingPoint<float>();
+    primaries.green.Y = mFdp.ConsumeFloatingPoint<float>();
+    primaries.green.Z = mFdp.ConsumeFloatingPoint<float>();
+    primaries.blue.X = mFdp.ConsumeFloatingPoint<float>();
+    primaries.blue.Y = mFdp.ConsumeFloatingPoint<float>();
+    primaries.blue.Z = mFdp.ConsumeFloatingPoint<float>();
+    primaries.white.X = mFdp.ConsumeFloatingPoint<float>();
+    primaries.white.Y = mFdp.ConsumeFloatingPoint<float>();
+    primaries.white.Z = mFdp.ConsumeFloatingPoint<float>();
+    mTestableFlinger.setInternalDisplayPrimaries(primaries);
+}
+
+void SurfaceFlingerFuzzer::setTransactionState() {
+    Vector<ComposerState> states;
+    Vector<DisplayState> displays;
+    ComposerState composerState;
+    composerState.state.what = layer_state_t::eLayerChanged;
+    composerState.state.surface = nullptr;
+    states.add(composerState);
+    uint32_t flags = mFdp.ConsumeIntegral<uint32_t>();
+    const sp<IBinder> applyToken = nullptr;
+    int64_t desiredPresentTime = mFdp.ConsumeIntegral<int64_t>();
+    bool isAutoTimestamp = mFdp.ConsumeBool();
+    bool hasListenerCallbacks = mFdp.ConsumeBool();
+    std::vector<ListenerCallbacks> listenerCallbacks{};
+    uint64_t transactionId = mFdp.ConsumeIntegral<uint64_t>();
+
+    mTestableFlinger.setTransactionState(FrameTimelineInfo{}, states, displays, flags, applyToken,
+                                         InputWindowCommands{}, desiredPresentTime, isAutoTimestamp,
+                                         {}, hasListenerCallbacks, listenerCallbacks,
+                                         transactionId);
+}
+
+void SurfaceFlingerFuzzer::setDisplayStateLocked() {
+    DisplayState state{};
+    mTestableFlinger.setDisplayStateLocked(state);
+}
+
+void SurfaceFlingerFuzzer::onTransact(const uint8_t *data, size_t size) {
+    Parcel fuzzedData, reply;
+    fuzzedData.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
+    fuzzedData.setData(data, size);
+    fuzzedData.setDataPosition(0);
+    uint32_t code = mFdp.ConsumeBool() ? mFdp.PickValueInArray(kSurfaceComposerTags)
+                                       : mFdp.ConsumeIntegralInRange<uint32_t>(kMinCode, kMaxCode);
+    mTestableFlinger.onTransact(code, fuzzedData, &reply, 0);
+}
+
+void SurfaceFlingerFuzzer::setUp() {
+    mTestableFlinger.setupScheduler(std::make_unique<android::mock::VsyncController>(),
+                                    std::make_unique<android::mock::VSyncTracker>(),
+                                    std::make_unique<android::mock::EventThread>(),
+                                    std::make_unique<android::mock::EventThread>());
+
+    mTestableFlinger.setupTimeStats(std::make_unique<android::mock::TimeStats>());
+
+    std::unique_ptr<android::renderengine::RenderEngine> renderEngine =
+            std::make_unique<android::renderengine::mock::RenderEngine>();
+    mTestableFlinger.setupRenderEngine(std::move(renderEngine));
+    mTestableFlinger.setupComposer(std::make_unique<android::Hwc2::mock::Composer>());
+}
+
+void SurfaceFlingerFuzzer::process(const uint8_t *data, size_t size) {
+    setUp();
+
+    invokeFlinger();
+
+    mTestableFlinger.fuzzSurfaceFlinger(data, size);
+
+    mTestableFlinger.setCreateBufferQueueFunction(
+            surfaceflinger::test::Factory::CreateBufferQueueFunction());
+    mTestableFlinger.setCreateNativeWindowSurface(
+            surfaceflinger::test::Factory::CreateNativeWindowSurfaceFunction());
+
+    setInternalDisplayPrimaries();
+
+    mTestableFlinger.enableHalVirtualDisplays(mFdp.ConsumeBool());
+
+    mTestableFlinger.commitTransactionsLocked(mFdp.ConsumeIntegral<uint32_t>());
+
+    mTestableFlinger.notifyPowerBoost(mFdp.ConsumeIntegral<int32_t>());
+
+    setDisplayStateLocked();
+
+    setTransactionState();
+    mTestableFlinger.flushTransactionQueues();
+
+    onTransact(data, size);
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    android::fuzz::SurfaceFlingerFuzzer surfaceFlingerFuzzer(data, size);
+    surfaceFlingerFuzzer.process(data, size);
+    return 0;
+}
+
+} // namespace android::fuzz
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
new file mode 100644
index 0000000..0a458c2
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -0,0 +1,803 @@
+/*
+ * Copyright 2021 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 <compositionengine/Display.h>
+#include <compositionengine/LayerFECompositionState.h>
+#include <compositionengine/OutputLayer.h>
+#include <compositionengine/impl/CompositionEngine.h>
+#include <compositionengine/impl/Display.h>
+#include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <gui/LayerDebugInfo.h>
+#include <gui/ScreenCaptureResults.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/mock/GraphicBufferProducer.h>
+#include <ui/DisplayStatInfo.h>
+#include <ui/DynamicDisplayInfo.h>
+
+#include "BufferQueueLayer.h"
+#include "BufferStateLayer.h"
+#include "ContainerLayer.h"
+#include "DisplayDevice.h"
+#include "DisplayHardware/ComposerHal.h"
+#include "EffectLayer.h"
+#include "FrameTimeline/FrameTimeline.h"
+#include "FrameTracer/FrameTracer.h"
+#include "Layer.h"
+#include "NativeWindowSurface.h"
+#include "Scheduler/EventThread.h"
+#include "Scheduler/MessageQueue.h"
+#include "Scheduler/RefreshRateConfigs.h"
+#include "Scheduler/VSyncTracker.h"
+#include "Scheduler/VsyncConfiguration.h"
+#include "Scheduler/VsyncController.h"
+#include "Scheduler/VsyncModulator.h"
+#include "StartPropertySetThread.h"
+#include "SurfaceFlinger.h"
+#include "SurfaceFlingerDefaultFactory.h"
+#include "SurfaceInterceptor.h"
+#include "TimeStats/TimeStats.h"
+
+#include "renderengine/mock/RenderEngine.h"
+#include "scheduler/TimeKeeper.h"
+#include "tests/unittests/mock/DisplayHardware/MockComposer.h"
+#include "tests/unittests/mock/DisplayHardware/MockHWC2.h"
+#include "tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h"
+#include "tests/unittests/mock/MockEventThread.h"
+#include "tests/unittests/mock/MockFrameTimeline.h"
+#include "tests/unittests/mock/MockFrameTracer.h"
+#include "tests/unittests/mock/MockNativeWindowSurface.h"
+#include "tests/unittests/mock/MockSurfaceInterceptor.h"
+#include "tests/unittests/mock/MockTimeStats.h"
+#include "tests/unittests/mock/MockVSyncTracker.h"
+#include "tests/unittests/mock/MockVsyncController.h"
+
+namespace android {
+namespace Hwc2 {
+
+class Composer;
+
+namespace types = hardware::graphics::common;
+
+namespace V2_1 = hardware::graphics::composer::V2_1;
+namespace V2_2 = hardware::graphics::composer::V2_2;
+namespace V2_3 = hardware::graphics::composer::V2_3;
+namespace V2_4 = hardware::graphics::composer::V2_4;
+
+using types::V1_0::ColorTransform;
+using types::V1_0::Transform;
+using types::V1_1::RenderIntent;
+using types::V1_2::ColorMode;
+using types::V1_2::Dataspace;
+using types::V1_2::Hdr;
+using types::V1_2::PixelFormat;
+
+using V2_1::Config;
+using V2_1::Display;
+using V2_1::Error;
+using V2_1::Layer;
+using V2_4::CommandReaderBase;
+using V2_4::CommandWriterBase;
+using V2_4::IComposer;
+using V2_4::IComposerCallback;
+using V2_4::IComposerClient;
+using V2_4::VsyncPeriodChangeTimeline;
+using V2_4::VsyncPeriodNanos;
+using DisplayCapability = IComposerClient::DisplayCapability;
+using PerFrameMetadata = IComposerClient::PerFrameMetadata;
+using PerFrameMetadataKey = IComposerClient::PerFrameMetadataKey;
+using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob;
+}; // namespace Hwc2
+
+static constexpr hal::HWDisplayId kHwDisplayId = 1000;
+
+static constexpr ui::Hdr kHdrTypes[] = {ui::Hdr::DOLBY_VISION, ui::Hdr::HDR10, ui::Hdr::HLG,
+                                        ui::Hdr::HDR10_PLUS};
+
+static constexpr ui::ColorMode kColormodes[] = {ui::ColorMode::NATIVE,
+                                                ui::ColorMode::STANDARD_BT601_625,
+                                                ui::ColorMode::STANDARD_BT601_625_UNADJUSTED,
+                                                ui::ColorMode::STANDARD_BT601_525,
+                                                ui::ColorMode::STANDARD_BT601_525_UNADJUSTED,
+                                                ui::ColorMode::STANDARD_BT709,
+                                                ui::ColorMode::DCI_P3,
+                                                ui::ColorMode::SRGB,
+                                                ui::ColorMode::ADOBE_RGB,
+                                                ui::ColorMode::DISPLAY_P3,
+                                                ui::ColorMode::BT2020,
+                                                ui::ColorMode::BT2100_PQ,
+                                                ui::ColorMode::BT2100_HLG,
+                                                ui::ColorMode::DISPLAY_BT2020};
+
+FloatRect getFuzzedFloatRect(FuzzedDataProvider *fdp) {
+    return FloatRect(fdp->ConsumeFloatingPoint<float>() /*left*/,
+                     fdp->ConsumeFloatingPoint<float>() /*right*/,
+                     fdp->ConsumeFloatingPoint<float>() /*top*/,
+                     fdp->ConsumeFloatingPoint<float>() /*bottom*/);
+}
+
+HdrMetadata getFuzzedHdrMetadata(FuzzedDataProvider *fdp) {
+    HdrMetadata hdrMetadata;
+    if (fdp->ConsumeBool()) {
+        hdrMetadata.cta8613.maxContentLightLevel = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.cta8613.maxFrameAverageLightLevel = fdp->ConsumeFloatingPoint<float>();
+
+        hdrMetadata.validTypes |= HdrMetadata::CTA861_3;
+    } else {
+        hdrMetadata.smpte2086.displayPrimaryRed.x = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryRed.y = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryGreen.x = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryGreen.y = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryBlue.x = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryBlue.y = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.whitePoint.x = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.whitePoint.y = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.minLuminance = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.maxLuminance = fdp->ConsumeFloatingPoint<float>();
+
+        hdrMetadata.validTypes |= HdrMetadata::SMPTE2086;
+    }
+    return hdrMetadata;
+}
+
+class EventThread;
+
+namespace hal = android::hardware::graphics::composer::hal;
+
+struct FakePhaseOffsets : scheduler::VsyncConfiguration {
+    static constexpr nsecs_t FAKE_PHASE_OFFSET_NS = 0;
+    static constexpr auto FAKE_DURATION_OFFSET_NS = std::chrono::nanoseconds(0);
+
+    VsyncConfigSet getConfigsForRefreshRate(Fps) const override { return getCurrentConfigs(); }
+
+    VsyncConfigSet getCurrentConfigs() const override {
+        return {{FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
+                 FAKE_DURATION_OFFSET_NS},
+                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
+                 FAKE_DURATION_OFFSET_NS},
+                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
+                 FAKE_DURATION_OFFSET_NS},
+                FAKE_DURATION_OFFSET_NS};
+    }
+
+    void reset() override {}
+    void setRefreshRateFps(Fps) override {}
+    void dump(std::string &) const override {}
+};
+namespace scheduler {
+class TestableScheduler : public Scheduler, private ICompositor {
+public:
+    TestableScheduler(const std::shared_ptr<scheduler::RefreshRateConfigs> &refreshRateConfigs,
+                      ISchedulerCallback &callback)
+          : TestableScheduler(std::make_unique<android::mock::VsyncController>(),
+                              std::make_unique<android::mock::VSyncTracker>(), refreshRateConfigs,
+                              callback) {}
+
+    void scheduleFrame(){};
+    void postMessage(sp<MessageHandler> &&){};
+
+    TestableScheduler(std::unique_ptr<VsyncController> controller,
+                      std::unique_ptr<VSyncTracker> tracker,
+                      std::shared_ptr<RefreshRateConfigs> configs, ISchedulerCallback &callback)
+          : Scheduler(*this, callback, Feature::kContentDetection) {
+        mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
+        setRefreshRateConfigs(std::move(configs));
+    }
+
+    ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
+        return Scheduler::createConnection(std::move(eventThread));
+    }
+
+    auto &mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
+    auto &mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
+
+    auto &mutableLayerHistory() { return mLayerHistory; }
+
+    auto refreshRateConfigs() { return holdRefreshRateConfigs(); }
+
+    void replaceTouchTimer(int64_t millis) {
+        if (mTouchTimer) {
+            mTouchTimer.reset();
+        }
+        mTouchTimer.emplace(
+                "Testable Touch timer", std::chrono::milliseconds(millis),
+                [this] { touchTimerCallback(TimerState::Reset); },
+                [this] { touchTimerCallback(TimerState::Expired); });
+        mTouchTimer->start();
+    }
+
+    bool isTouchActive() {
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+        return mPolicy.touch == Scheduler::TouchState::Active;
+    }
+
+    void dispatchCachedReportedMode() {
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+        return Scheduler::dispatchCachedReportedMode();
+    }
+
+    void clearCachedReportedMode() {
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+        mPolicy.cachedModeChangedParams.reset();
+    }
+
+    void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
+        return Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
+    }
+
+private:
+    // ICompositor overrides:
+    bool commit(nsecs_t, int64_t, nsecs_t) override { return false; }
+    void composite(nsecs_t) override {}
+    void sample() override {}
+};
+}; // namespace scheduler
+
+namespace surfaceflinger::test {
+
+class Factory final : public surfaceflinger::Factory {
+public:
+    ~Factory() = default;
+
+    std::unique_ptr<HWComposer> createHWComposer(const std::string &) override { return nullptr; }
+
+    std::unique_ptr<MessageQueue> createMessageQueue(ICompositor &compositor) {
+        return std::make_unique<android::impl::MessageQueue>(compositor);
+    }
+
+    std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
+            Fps /*currentRefreshRate*/) override {
+        return std::make_unique<FakePhaseOffsets>();
+    }
+
+    std::unique_ptr<scheduler::Scheduler> createScheduler(
+            const std::shared_ptr<scheduler::RefreshRateConfigs> &,
+            scheduler::ISchedulerCallback &) {
+        return nullptr;
+    }
+
+    sp<SurfaceInterceptor> createSurfaceInterceptor() override {
+        return new android::impl::SurfaceInterceptor();
+    }
+
+    sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override {
+        return new StartPropertySetThread(timestampPropertyValue);
+    }
+
+    sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs &creationArgs) override {
+        return new DisplayDevice(creationArgs);
+    }
+
+    sp<GraphicBuffer> createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format,
+                                          uint32_t layerCount, uint64_t usage,
+                                          std::string requestorName) override {
+        return new GraphicBuffer(width, height, format, layerCount, usage, requestorName);
+    }
+
+    void createBufferQueue(sp<IGraphicBufferProducer> *outProducer,
+                           sp<IGraphicBufferConsumer> *outConsumer,
+                           bool consumerIsSurfaceFlinger) override {
+        if (!mCreateBufferQueue) {
+            BufferQueue::createBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger);
+            return;
+        }
+        mCreateBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger);
+    }
+
+    sp<IGraphicBufferProducer> createMonitoredProducer(const sp<IGraphicBufferProducer> &producer,
+                                                       const sp<SurfaceFlinger> &flinger,
+                                                       const wp<Layer> &layer) override {
+        return new MonitoredProducer(producer, flinger, layer);
+    }
+
+    sp<BufferLayerConsumer> createBufferLayerConsumer(const sp<IGraphicBufferConsumer> &consumer,
+                                                      renderengine::RenderEngine &renderEngine,
+                                                      uint32_t textureName, Layer *layer) override {
+        return new BufferLayerConsumer(consumer, renderEngine, textureName, layer);
+    }
+
+    std::unique_ptr<surfaceflinger::NativeWindowSurface> createNativeWindowSurface(
+            const sp<IGraphicBufferProducer> &producer) override {
+        if (!mCreateNativeWindowSurface) return nullptr;
+        return mCreateNativeWindowSurface(producer);
+    }
+
+    std::unique_ptr<compositionengine::CompositionEngine> createCompositionEngine() override {
+        return compositionengine::impl::createCompositionEngine();
+    }
+
+    sp<BufferQueueLayer> createBufferQueueLayer(const LayerCreationArgs &) override {
+        return nullptr;
+    }
+
+    sp<BufferStateLayer> createBufferStateLayer(const LayerCreationArgs &) override {
+        return nullptr;
+    }
+
+    sp<EffectLayer> createEffectLayer(const LayerCreationArgs &args) override {
+        return new EffectLayer(args);
+    }
+
+    sp<ContainerLayer> createContainerLayer(const LayerCreationArgs &args) override {
+        return new ContainerLayer(args);
+    }
+
+    std::unique_ptr<FrameTracer> createFrameTracer() override {
+        return std::make_unique<android::mock::FrameTracer>();
+    }
+
+    std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
+            std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid = 0) override {
+        return std::make_unique<android::mock::FrameTimeline>(timeStats, surfaceFlingerPid);
+    }
+
+    using CreateBufferQueueFunction =
+            std::function<void(sp<IGraphicBufferProducer> * /* outProducer */,
+                               sp<IGraphicBufferConsumer> * /* outConsumer */,
+                               bool /* consumerIsSurfaceFlinger */)>;
+    CreateBufferQueueFunction mCreateBufferQueue;
+
+    using CreateNativeWindowSurfaceFunction =
+            std::function<std::unique_ptr<surfaceflinger::NativeWindowSurface>(
+                    const sp<IGraphicBufferProducer> &)>;
+    CreateNativeWindowSurfaceFunction mCreateNativeWindowSurface;
+
+    using CreateCompositionEngineFunction =
+            std::function<std::unique_ptr<compositionengine::CompositionEngine>()>;
+    CreateCompositionEngineFunction mCreateCompositionEngine;
+};
+
+} // namespace surfaceflinger::test
+
+// TODO(b/189053744) : Create a common test/mock library for surfaceflinger
+class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback {
+public:
+    using HotplugEvent = SurfaceFlinger::HotplugEvent;
+
+    SurfaceFlinger *flinger() { return mFlinger.get(); }
+    scheduler::TestableScheduler *scheduler() { return mScheduler; }
+
+    // Allow reading display state without locking, as if called on the SF main thread.
+    auto onInitializeDisplays() NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->onInitializeDisplays();
+    }
+
+    void scheduleComposite(FrameHint){};
+
+    void setGlobalShadowSettings(FuzzedDataProvider *fdp) {
+        const half4 ambientColor{fdp->ConsumeFloatingPoint<float>(),
+                                 fdp->ConsumeFloatingPoint<float>(),
+                                 fdp->ConsumeFloatingPoint<float>(),
+                                 fdp->ConsumeFloatingPoint<float>()};
+        const half4 spotColor{fdp->ConsumeFloatingPoint<float>(),
+                              fdp->ConsumeFloatingPoint<float>(),
+                              fdp->ConsumeFloatingPoint<float>(),
+                              fdp->ConsumeFloatingPoint<float>()};
+        float lightPosY = fdp->ConsumeFloatingPoint<float>();
+        float lightPosZ = fdp->ConsumeFloatingPoint<float>();
+        float lightRadius = fdp->ConsumeFloatingPoint<float>();
+        mFlinger->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ,
+                                          lightRadius);
+    }
+
+    void onPullAtom(FuzzedDataProvider *fdp) {
+        const int32_t atomId = fdp->ConsumeIntegral<uint8_t>();
+        std::string pulledData = fdp->ConsumeRandomLengthString().c_str();
+        bool success = fdp->ConsumeBool();
+        mFlinger->onPullAtom(atomId, &pulledData, &success);
+    }
+
+    void fuzzDumpsysAndDebug(FuzzedDataProvider *fdp) {
+        std::string result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->appendSfConfigString(result);
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->listLayersLocked(result);
+
+        using DumpArgs = Vector<String16>;
+        DumpArgs dumpArgs;
+        dumpArgs.push_back(String16(fdp->ConsumeRandomLengthString().c_str()));
+        mFlinger->clearStatsLocked(dumpArgs, result);
+
+        mFlinger->dumpTimeStats(dumpArgs, fdp->ConsumeBool(), result);
+        mFlinger->logFrameStats();
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpFrameTimeline(dumpArgs, result);
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpStaticScreenStats(result);
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpFrameEventsLocked(result);
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpRawDisplayIdentificationData(dumpArgs, result);
+
+        LayersProto layersProto = mFlinger->dumpDrawingStateProto(fdp->ConsumeIntegral<uint32_t>());
+        mFlinger->dumpOffscreenLayersProto(layersProto);
+        LayersTraceProto layersTraceProto{};
+        mFlinger->dumpDisplayProto(layersTraceProto);
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpHwc(result);
+
+        mFlinger->calculateColorMatrix(fdp->ConsumeFloatingPoint<float>());
+        mFlinger->updateColorMatrixLocked();
+        mFlinger->CheckTransactCodeCredentials(fdp->ConsumeIntegral<uint32_t>());
+
+        const CountDownLatch transactionCommittedSignal(fdp->ConsumeIntegral<uint32_t>());
+        mFlinger->waitForSynchronousTransaction(transactionCommittedSignal);
+        mFlinger->signalSynchronousTransactions(fdp->ConsumeIntegral<uint32_t>());
+    }
+
+    void getCompositionPreference() {
+        ui::Dataspace outDataspace;
+        ui::PixelFormat outPixelFormat;
+        ui::Dataspace outWideColorGamutDataspace;
+        ui::PixelFormat outWideColorGamutPixelFormat;
+        mFlinger->getCompositionPreference(&outDataspace, &outPixelFormat,
+                                           &outWideColorGamutDataspace,
+                                           &outWideColorGamutPixelFormat);
+    }
+
+    void overrideHdrTypes(sp<IBinder> &display, FuzzedDataProvider *fdp) {
+        std::vector<ui::Hdr> hdrTypes;
+        hdrTypes.push_back(fdp->PickValueInArray(kHdrTypes));
+        mFlinger->overrideHdrTypes(display, hdrTypes);
+    }
+
+    void getDisplayedContentSample(sp<IBinder> &display, FuzzedDataProvider *fdp) {
+        DisplayedFrameStats outDisplayedFrameStats;
+        mFlinger->getDisplayedContentSample(display, fdp->ConsumeIntegral<uint64_t>(),
+                                            fdp->ConsumeIntegral<uint64_t>(),
+                                            &outDisplayedFrameStats);
+    }
+
+    void getDisplayStats(sp<IBinder> &display) {
+        android::DisplayStatInfo stats;
+        mFlinger->getDisplayStats(display, &stats);
+    }
+
+    void getDisplayState(sp<IBinder> &display) {
+        ui::DisplayState displayState;
+        mFlinger->getDisplayState(display, &displayState);
+    }
+
+    void getStaticDisplayInfo(sp<IBinder> &display) {
+        ui::StaticDisplayInfo staticDisplayInfo;
+        mFlinger->getStaticDisplayInfo(display, &staticDisplayInfo);
+    }
+
+    void getDynamicDisplayInfo(sp<IBinder> &display) {
+        android::ui::DynamicDisplayInfo dynamicDisplayInfo;
+        mFlinger->getDynamicDisplayInfo(display, &dynamicDisplayInfo);
+    }
+    void getDisplayNativePrimaries(sp<IBinder> &display) {
+        android::ui::DisplayPrimaries displayPrimaries;
+        mFlinger->getDisplayNativePrimaries(display, displayPrimaries);
+    }
+
+    void getDesiredDisplayModeSpecs(sp<IBinder> &display) {
+        ui::DisplayModeId outDefaultMode;
+        bool outAllowGroupSwitching;
+        float outPrimaryRefreshRateMin;
+        float outPrimaryRefreshRateMax;
+        float outAppRequestRefreshRateMin;
+        float outAppRequestRefreshRateMax;
+        mFlinger->getDesiredDisplayModeSpecs(display, &outDefaultMode, &outAllowGroupSwitching,
+                                             &outPrimaryRefreshRateMin, &outPrimaryRefreshRateMax,
+                                             &outAppRequestRefreshRateMin,
+                                             &outAppRequestRefreshRateMax);
+    }
+
+    void setVsyncConfig(FuzzedDataProvider *fdp) {
+        const scheduler::VsyncModulator::VsyncConfig vsyncConfig{};
+        mFlinger->setVsyncConfig(vsyncConfig, fdp->ConsumeIntegral<nsecs_t>());
+    }
+
+    void updateCompositorTiming(FuzzedDataProvider *fdp) {
+        std::shared_ptr<FenceTime> presentFenceTime = FenceTime::NO_FENCE;
+        mFlinger->updateCompositorTiming({}, fdp->ConsumeIntegral<nsecs_t>(), presentFenceTime);
+    }
+
+    void getCompositorTiming() {
+        CompositorTiming compositorTiming;
+        mFlinger->getCompositorTiming(&compositorTiming);
+    }
+
+    sp<IBinder> fuzzBoot(FuzzedDataProvider *fdp) {
+        mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(fdp->ConsumeBool());
+        mFlinger->createConnection();
+
+        DisplayIdGenerator<HalVirtualDisplayId> kGenerator;
+        HalVirtualDisplayId halVirtualDisplayId = kGenerator.generateId().value();
+
+        ui::Size uiSize{fdp->ConsumeIntegral<int32_t>(), fdp->ConsumeIntegral<int32_t>()};
+        ui::PixelFormat pixelFormat{};
+        mFlinger->getHwComposer().allocateVirtualDisplay(halVirtualDisplayId, uiSize, &pixelFormat);
+
+        PhysicalDisplayId physicalDisplayId = SurfaceComposerClient::getInternalDisplayId().value();
+        mFlinger->getHwComposer().allocatePhysicalDisplay(kHwDisplayId, physicalDisplayId);
+
+        sp<IBinder> display =
+                mFlinger->createDisplay(String8(fdp->ConsumeRandomLengthString().c_str()),
+                                        fdp->ConsumeBool());
+
+        onInitializeDisplays();
+        mFlinger->getPhysicalDisplayToken(physicalDisplayId);
+
+        mFlinger->mStartPropertySetThread =
+                mFlinger->getFactory().createStartPropertySetThread(fdp->ConsumeBool());
+
+        mFlinger->bootFinished();
+
+        return display;
+    }
+
+    void fuzzSurfaceFlinger(const uint8_t *data, size_t size) {
+        FuzzedDataProvider mFdp(data, size);
+
+        sp<IBinder> display = fuzzBoot(&mFdp);
+
+        sp<IGraphicBufferProducer> bufferProducer = sp<mock::GraphicBufferProducer>::make();
+        mFlinger->authenticateSurfaceTexture(bufferProducer.get());
+
+        mFlinger->createDisplayEventConnection();
+
+        getDisplayStats(display);
+        getDisplayState(display);
+        getStaticDisplayInfo(display);
+        getDynamicDisplayInfo(display);
+        getDisplayNativePrimaries(display);
+
+        mFlinger->setAutoLowLatencyMode(display, mFdp.ConsumeBool());
+        mFlinger->setGameContentType(display, mFdp.ConsumeBool());
+        mFlinger->setPowerMode(display, mFdp.ConsumeIntegral<int>());
+        mFlinger->clearAnimationFrameStats();
+
+        overrideHdrTypes(display, &mFdp);
+
+        onPullAtom(&mFdp);
+
+        mFlinger->injectVSync(mFdp.ConsumeIntegral<nsecs_t>());
+
+        getCompositionPreference();
+        getDisplayedContentSample(display, &mFdp);
+        getDesiredDisplayModeSpecs(display);
+
+        bool outSupport;
+        mFlinger->getDisplayBrightnessSupport(display, &outSupport);
+
+        mFlinger->notifyPowerBoost(mFdp.ConsumeIntegral<int32_t>());
+
+        setGlobalShadowSettings(&mFdp);
+
+        mFlinger->binderDied(display);
+        mFlinger->onFirstRef();
+
+        mFlinger->commitTransactions();
+        mFlinger->updateInputFlinger();
+        mFlinger->updateCursorAsync();
+
+        setVsyncConfig(&mFdp);
+
+        mFlinger->flushTransactionQueues(0);
+
+        mFlinger->setTransactionFlags(mFdp.ConsumeIntegral<uint32_t>());
+        mFlinger->clearTransactionFlags(mFdp.ConsumeIntegral<uint32_t>());
+        mFlinger->commitOffscreenLayers();
+
+        mFlinger->frameIsEarly(mFdp.ConsumeIntegral<nsecs_t>(), mFdp.ConsumeIntegral<int64_t>());
+        mFlinger->computeLayerBounds();
+        mFlinger->startBootAnim();
+
+        mFlinger->readPersistentProperties();
+
+        mFlinger->exceedsMaxRenderTargetSize(mFdp.ConsumeIntegral<uint32_t>(),
+                                             mFdp.ConsumeIntegral<uint32_t>());
+
+        mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mFdp.ConsumeIntegral<uid_t>());
+
+        mFlinger->postComposition();
+
+        getCompositorTiming();
+
+        updateCompositorTiming(&mFdp);
+
+        mFlinger->setCompositorTimingSnapped({}, mFdp.ConsumeIntegral<nsecs_t>());
+        mFlinger->postFrame();
+        mFlinger->calculateExpectedPresentTime({});
+
+        mFlinger->enableHalVirtualDisplays(mFdp.ConsumeBool());
+
+        fuzzDumpsysAndDebug(&mFdp);
+
+        mFlinger->destroyDisplay(display);
+    }
+
+    void setupRenderEngine(std::unique_ptr<renderengine::RenderEngine> renderEngine) {
+        mFlinger->mCompositionEngine->setRenderEngine(std::move(renderEngine));
+    }
+
+    void setupComposer(std::unique_ptr<Hwc2::Composer> composer) {
+        mFlinger->mCompositionEngine->setHwComposer(
+                std::make_unique<impl::HWComposer>(std::move(composer)));
+    }
+
+    void setupTimeStats(const std::shared_ptr<TimeStats> &timeStats) {
+        mFlinger->mCompositionEngine->setTimeStats(timeStats);
+    }
+
+    // The ISchedulerCallback argument can be nullptr for a no-op implementation.
+    void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
+                        std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
+                        std::unique_ptr<EventThread> appEventThread,
+                        std::unique_ptr<EventThread> sfEventThread,
+                        scheduler::ISchedulerCallback *callback = nullptr,
+                        bool hasMultipleModes = false) {
+        DisplayModes modes{DisplayMode::Builder(0)
+                                   .setId(DisplayModeId(0))
+                                   .setPhysicalDisplayId(PhysicalDisplayId::fromPort(0))
+                                   .setVsyncPeriod(16'666'667)
+                                   .setGroup(0)
+                                   .build()};
+
+        if (hasMultipleModes) {
+            modes.emplace_back(DisplayMode::Builder(1)
+                                       .setId(DisplayModeId(1))
+                                       .setPhysicalDisplayId(PhysicalDisplayId::fromPort(0))
+                                       .setVsyncPeriod(11'111'111)
+                                       .setGroup(0)
+                                       .build());
+        }
+
+        const auto currMode = DisplayModeId(0);
+        mRefreshRateConfigs = std::make_shared<scheduler::RefreshRateConfigs>(modes, currMode);
+        const auto currFps = mRefreshRateConfigs->getCurrentRefreshRate().getFps();
+        mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(currFps);
+        mFlinger->mVsyncModulator = sp<scheduler::VsyncModulator>::make(
+                mFlinger->mVsyncConfiguration->getCurrentConfigs());
+        mFlinger->mRefreshRateStats =
+                std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, currFps,
+                                                              /*powerMode=*/hal::PowerMode::OFF);
+
+        mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
+                                                      std::move(vsyncTracker), mRefreshRateConfigs,
+                                                      *(callback ?: this));
+
+        mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
+        mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
+        resetScheduler(mScheduler);
+    }
+
+    void resetScheduler(scheduler::Scheduler *scheduler) { mFlinger->mScheduler.reset(scheduler); }
+
+    scheduler::TestableScheduler &mutableScheduler() const { return *mScheduler; }
+
+    using CreateBufferQueueFunction = surfaceflinger::test::Factory::CreateBufferQueueFunction;
+    void setCreateBufferQueueFunction(CreateBufferQueueFunction f) {
+        mFactory.mCreateBufferQueue = f;
+    }
+
+    using CreateNativeWindowSurfaceFunction =
+            surfaceflinger::test::Factory::CreateNativeWindowSurfaceFunction;
+    void setCreateNativeWindowSurface(CreateNativeWindowSurfaceFunction f) {
+        mFactory.mCreateNativeWindowSurface = f;
+    }
+
+    void setInternalDisplayPrimaries(const ui::DisplayPrimaries &primaries) {
+        memcpy(&mFlinger->mInternalDisplayPrimaries, &primaries, sizeof(ui::DisplayPrimaries));
+    }
+
+    static auto &mutableLayerDrawingState(const sp<Layer> &layer) { return layer->mDrawingState; }
+
+    auto &mutableStateLock() { return mFlinger->mStateLock; }
+
+    static auto findOutputLayerForDisplay(const sp<Layer> &layer,
+                                          const sp<const DisplayDevice> &display) {
+        return layer->findOutputLayerForDisplay(display.get());
+    }
+
+    /* ------------------------------------------------------------------------
+     * Forwarding for functions being tested
+     */
+
+    void enableHalVirtualDisplays(bool enable) { mFlinger->enableHalVirtualDisplays(enable); }
+
+    auto commitTransactionsLocked(uint32_t transactionFlags) {
+        Mutex::Autolock lock(mFlinger->mStateLock);
+        return mFlinger->commitTransactionsLocked(transactionFlags);
+    }
+
+    auto setDisplayStateLocked(const DisplayState &s) {
+        Mutex::Autolock lock(mFlinger->mStateLock);
+        return mFlinger->setDisplayStateLocked(s);
+    }
+
+    auto notifyPowerBoost(int32_t boostId) { return mFlinger->notifyPowerBoost(boostId); }
+
+    // Allow reading display state without locking, as if called on the SF main thread.
+    auto setPowerModeInternal(const sp<DisplayDevice> &display,
+                              hal::PowerMode mode) NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->setPowerModeInternal(display, mode);
+    }
+
+    auto onMessageReceived(int32_t /*what*/) { return 0; }
+
+    auto &getTransactionQueue() { return mFlinger->mTransactionQueue; }
+    auto &getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; }
+
+    auto setTransactionState(
+            const FrameTimelineInfo &frameTimelineInfo, const Vector<ComposerState> &states,
+            const Vector<DisplayState> &displays, uint32_t flags, const sp<IBinder> &applyToken,
+            const InputWindowCommands &inputWindowCommands, int64_t desiredPresentTime,
+            bool isAutoTimestamp, const client_cache_t &uncacheBuffer, bool hasListenerCallbacks,
+            std::vector<ListenerCallbacks> &listenerCallbacks, uint64_t transactionId) {
+        return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
+                                             inputWindowCommands, desiredPresentTime,
+                                             isAutoTimestamp, uncacheBuffer, hasListenerCallbacks,
+                                             listenerCallbacks, transactionId);
+    }
+
+    auto flushTransactionQueues() { return mFlinger->flushTransactionQueues(0); };
+
+    auto onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
+        return mFlinger->onTransact(code, data, reply, flags);
+    }
+
+    auto getGPUContextPriority() { return mFlinger->getGPUContextPriority(); }
+
+    auto calculateMaxAcquiredBufferCount(Fps refreshRate,
+                                         std::chrono::nanoseconds presentLatency) const {
+        return SurfaceFlinger::calculateMaxAcquiredBufferCount(refreshRate, presentLatency);
+    }
+
+    /* Read-write access to private data to set up preconditions and assert
+     * post-conditions.
+     */
+
+    auto &mutableCurrentState() { return mFlinger->mCurrentState; }
+    auto &mutableDisplays() { return mFlinger->mDisplays; }
+    auto &mutableDrawingState() { return mFlinger->mDrawingState; }
+    auto &mutableInterceptor() { return mFlinger->mInterceptor; }
+
+    auto fromHandle(const sp<IBinder> &handle) { return mFlinger->fromHandle(handle); }
+
+    ~TestableSurfaceFlinger() {
+        mutableDisplays().clear();
+        mutableCurrentState().displays.clear();
+        mutableDrawingState().displays.clear();
+        mutableInterceptor().clear();
+        mFlinger->mScheduler.reset();
+        mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr<HWComposer>());
+        mFlinger->mCompositionEngine->setRenderEngine(
+                std::unique_ptr<renderengine::RenderEngine>());
+    }
+
+private:
+    void scheduleRefresh(FrameHint) {}
+    void setVsyncEnabled(bool) override {}
+    void changeRefreshRate(const RefreshRate &, DisplayModeEvent) override {}
+    void kernelTimerChanged(bool) override {}
+    void triggerOnFrameRateOverridesChanged() {}
+
+    surfaceflinger::test::Factory mFactory;
+    sp<SurfaceFlinger> mFlinger = new SurfaceFlinger(mFactory, SurfaceFlinger::SkipInitialization);
+    scheduler::TestableScheduler *mScheduler = nullptr;
+    std::shared_ptr<scheduler::RefreshRateConfigs> mRefreshRateConfigs;
+};
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 261e106..0765d5b 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -136,7 +136,6 @@
                  V2_4::Error(Display, Config, const IComposerClient::VsyncPeriodChangeConstraints&,
                              VsyncPeriodChangeTimeline*));
     MOCK_METHOD2(setAutoLowLatencyMode, V2_4::Error(Display, bool));
-    MOCK_METHOD2(getBootDisplayConfigSupport, Error(Display, bool*));
     MOCK_METHOD2(setBootDisplayConfig, Error(Display, Config));
     MOCK_METHOD1(clearBootDisplayConfig, Error(Display));
     MOCK_METHOD2(getPreferredBootDisplayConfig, Error(Display, Config*));