Merge "Rename small dirty detection flag" into main
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index fd2b27f..620c23c 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -359,6 +359,38 @@
 }
 
 cc_library_static {
+    name: "libbinder_rpc_no_blob",
+    vendor_available: true,
+    defaults: [
+        "libbinder_common_defaults",
+        "libbinder_android_defaults",
+        "libbinder_kernel_defaults",
+    ],
+    cflags: [
+        "-DBINDER_DISABLE_BLOB",
+    ],
+    visibility: [
+        ":__subpackages__",
+    ],
+}
+
+cc_library_static {
+    name: "libbinder_rpc_no_native_handle",
+    vendor_available: true,
+    defaults: [
+        "libbinder_common_defaults",
+        "libbinder_android_defaults",
+        "libbinder_kernel_defaults",
+    ],
+    cflags: [
+        "-DBINDER_DISABLE_NATIVE_HANDLE",
+    ],
+    visibility: [
+        ":__subpackages__",
+    ],
+}
+
+cc_library_static {
     name: "libbinder_rpc_single_threaded",
     defaults: [
         "libbinder_common_defaults",
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index b78b0b3..a3ff7d2 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -40,7 +40,9 @@
 #include <binder/TextOutput.h>
 
 #include <android-base/scopeguard.h>
+#ifndef BINDER_DISABLE_BLOB
 #include <cutils/ashmem.h>
+#endif
 #include <utils/Flattenable.h>
 #include <utils/Log.h>
 #include <utils/String16.h>
@@ -1475,6 +1477,7 @@
     return writeParcelable(*parcelable);
 }
 
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
 status_t Parcel::writeNativeHandle(const native_handle* handle)
 {
     if (!handle || handle->version != sizeof(native_handle))
@@ -1497,6 +1500,7 @@
     err = write(handle->data + handle->numFds, sizeof(int)*handle->numInts);
     return err;
 }
+#endif
 
 status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership) {
     if (auto* rpcFields = maybeRpcFields()) {
@@ -1589,6 +1593,12 @@
 
 status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
 {
+#ifdef BINDER_DISABLE_BLOB
+    (void)len;
+    (void)mutableCopy;
+    (void)outBlob;
+    return INVALID_OPERATION;
+#else
     if (len > INT32_MAX) {
         // don't accept size_t values which may have come from an
         // inadvertent conversion from a negative int.
@@ -1640,6 +1650,7 @@
     }
     ::close(fd);
     return status;
+#endif
 }
 
 status_t Parcel::writeDupImmutableBlobFileDescriptor(int fd)
@@ -2271,6 +2282,7 @@
     return status.exceptionCode();
 }
 
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
 native_handle* Parcel::readNativeHandle() const
 {
     int numFds, numInts;
@@ -2303,6 +2315,7 @@
     }
     return h;
 }
+#endif
 
 int Parcel::readFileDescriptor() const {
     if (const auto* rpcFields = maybeRpcFields()) {
@@ -2423,6 +2436,11 @@
 
 status_t Parcel::readBlob(size_t len, ReadableBlob* outBlob) const
 {
+#ifdef BINDER_DISABLE_BLOB
+    (void)len;
+    (void)outBlob;
+    return INVALID_OPERATION;
+#else
     int32_t blobType;
     status_t status = readInt32(&blobType);
     if (status) return status;
@@ -2456,6 +2474,7 @@
 
     outBlob->init(fd, ptr, len, isMutable);
     return NO_ERROR;
+#endif
 }
 
 status_t Parcel::read(FlattenableHelperInterface& val) const
@@ -3204,6 +3223,7 @@
     }
 
     size_t openAshmemSize = 0;
+#ifndef BINDER_DISABLE_BLOB
     for (size_t i = 0; i < kernelFields->mObjectsSize; i++) {
         const flat_binder_object* flat =
                 reinterpret_cast<const flat_binder_object*>(mData + kernelFields->mObjects[i]);
@@ -3218,6 +3238,7 @@
             }
         }
     }
+#endif
     return openAshmemSize;
 }
 #endif // BINDER_WITH_KERNEL_IPC
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index b94267c..98d12bb 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -26,7 +26,9 @@
 #include <vector>
 
 #include <android-base/unique_fd.h>
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
 #include <cutils/native_handle.h>
+#endif
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 #include <utils/String16.h>
@@ -324,11 +326,13 @@
     template<typename T>
     status_t            writeVectorSize(const std::unique_ptr<std::vector<T>>& val) __attribute__((deprecated("use std::optional version instead")));
 
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
     // Place a native_handle into the parcel (the native_handle's file-
     // descriptors are dup'ed, so it is safe to delete the native_handle
     // when this function returns).
     // Doesn't take ownership of the native_handle.
     status_t            writeNativeHandle(const native_handle* handle);
+#endif
 
     // Place a file descriptor into the parcel.  The given fd must remain
     // valid for the lifetime of the parcel.
@@ -559,13 +563,14 @@
     // response headers rather than doing it by hand.
     int32_t             readExceptionCode() const;
 
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
     // Retrieve native_handle from the parcel. This returns a copy of the
     // parcel's native_handle (the caller takes ownership). The caller
-    // must free the native_handle with native_handle_close() and 
+    // must free the native_handle with native_handle_close() and
     // native_handle_delete().
     native_handle*     readNativeHandle() const;
+#endif
 
-    
     // Retrieve a file descriptor from the parcel.  This returns the raw fd
     // in the parcel, which you do not own -- use dup() to get your own copy.
     int                 readFileDescriptor() const;
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 8841fe6..ed870b6 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -144,6 +144,7 @@
 #[doc(hidden)]
 pub mod unstable_api {
     pub use crate::binder::AsNative;
+    pub use crate::error::status_result;
     pub use crate::proxy::unstable_api::new_spibinder;
     pub use crate::sys::AIBinder;
     pub use crate::sys::AParcel;
diff --git a/libs/nativewindow/rust/Android.bp b/libs/nativewindow/rust/Android.bp
index dc1575c..90d0a8e 100644
--- a/libs/nativewindow/rust/Android.bp
+++ b/libs/nativewindow/rust/Android.bp
@@ -19,7 +19,7 @@
 }
 
 rust_bindgen {
-    name: "libnativewindow_bindgen",
+    name: "libnativewindow_bindgen_internal",
     crate_name: "nativewindow_bindgen",
     wrapper_src: "sys/nativewindow_bindings.h",
     source_stem: "bindings",
@@ -28,13 +28,21 @@
         "--bitfield-enum=AHardwareBuffer_UsageFlags",
 
         "--allowlist-file=.*/nativewindow/include/.*\\.h",
+        "--blocklist-type",
+        "AParcel",
+        "--raw-line",
+        "use binder::unstable_api::AParcel;",
 
         "--with-derive-eq",
         "--with-derive-partialeq",
     ],
     shared_libs: [
+        "libbinder_ndk",
         "libnativewindow",
     ],
+    rustlibs: [
+        "libbinder_rs",
+    ],
 
     // Currently necessary for host builds
     // TODO(b/31559095): bionic on host should define this
@@ -44,12 +52,40 @@
         },
     },
     min_sdk_version: "VanillaIceCream",
+    vendor_available: true,
+}
+
+rust_library {
+    name: "libnativewindow_bindgen",
+    crate_name: "nativewindow_bindgen",
+    srcs: [":libnativewindow_bindgen_internal"],
+    shared_libs: [
+        "libbinder_ndk",
+        "libnativewindow",
+    ],
+    rustlibs: [
+        "libbinder_rs",
+    ],
+    lints: "none",
+    clippy_lints: "none",
+    // Currently necessary for host builds
+    // TODO(b/31559095): bionic on host should define this
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    min_sdk_version: "VanillaIceCream",
+    vendor_available: true,
 }
 
 rust_test {
     name: "libnativewindow_bindgen_test",
-    srcs: [":libnativewindow_bindgen"],
+    srcs: [":libnativewindow_bindgen_internal"],
     crate_name: "nativewindow_bindgen_test",
+    rustlibs: [
+        "libbinder_rs",
+    ],
     test_suites: ["general-tests"],
     auto_gen_config: true,
     clippy_lints: "none",
@@ -60,6 +96,7 @@
     name: "libnativewindow_defaults",
     srcs: ["src/lib.rs"],
     rustlibs: [
+        "libbinder_rs",
         "libnativewindow_bindgen",
     ],
 }
@@ -77,6 +114,7 @@
         },
     },
     min_sdk_version: "VanillaIceCream",
+    vendor_available: true,
 }
 
 rust_test {
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index 6eb3bbc..6f86c4a 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -16,13 +16,22 @@
 
 extern crate nativewindow_bindgen as ffi;
 
-pub use ffi::{AHardwareBuffer, AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+pub use ffi::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
 
+use binder::{
+    binder_impl::{
+        BorrowedParcel, Deserialize, DeserializeArray, DeserializeOption, Serialize,
+        SerializeArray, SerializeOption, NON_NULL_PARCELABLE_FLAG, NULL_PARCELABLE_FLAG,
+    },
+    unstable_api::{status_result, AsNative},
+    StatusCode,
+};
+use ffi::{AHardwareBuffer, AHardwareBuffer_readFromParcel, AHardwareBuffer_writeToParcel};
 use std::fmt::{self, Debug, Formatter};
 use std::mem::ManuallyDrop;
-use std::ptr::{self, NonNull};
+use std::ptr::{self, null_mut, NonNull};
 
-/// Wrapper around an opaque C AHardwareBuffer.
+/// Wrapper around an opaque C `AHardwareBuffer`.
 #[derive(PartialEq, Eq)]
 pub struct HardwareBuffer(NonNull<AHardwareBuffer>);
 
@@ -120,8 +129,11 @@
     /// Available since API level 31.
     pub fn id(&self) -> u64 {
         let mut out_id = 0;
-        // SAFETY: Neither pointers can be null.
-        let status = unsafe { ffi::AHardwareBuffer_getId(self.0.as_ref(), &mut out_id) };
+        // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+        // because it must have been allocated by `AHardwareBuffer_allocate`,
+        // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+        // released it. The id pointer must be valid because it comes from a reference.
+        let status = unsafe { ffi::AHardwareBuffer_getId(self.0.as_ptr(), &mut out_id) };
         assert_eq!(status, 0, "id() failed for AHardwareBuffer with error code: {status}");
 
         out_id
@@ -176,9 +188,10 @@
 
 impl Drop for HardwareBuffer {
     fn drop(&mut self) {
-        // SAFETY: self.0 will never be null. AHardwareBuffers allocated from within Rust will have
-        // a refcount of one, and there is a safety warning on taking an AHardwareBuffer from a raw
-        // pointer requiring callers to ensure the refcount is managed appropriately.
+        // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+        // because it must have been allocated by `AHardwareBuffer_allocate`,
+        // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+        // released it.
         unsafe { ffi::AHardwareBuffer_release(self.0.as_ptr()) }
     }
 }
@@ -197,6 +210,82 @@
     }
 }
 
+impl Serialize for HardwareBuffer {
+    fn serialize(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
+        SerializeOption::serialize_option(Some(self), parcel)
+    }
+}
+
+impl SerializeOption for HardwareBuffer {
+    fn serialize_option(
+        this: Option<&Self>,
+        parcel: &mut BorrowedParcel,
+    ) -> Result<(), StatusCode> {
+        if let Some(this) = this {
+            parcel.write(&NON_NULL_PARCELABLE_FLAG)?;
+
+            let status =
+            // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+            // because it must have been allocated by `AHardwareBuffer_allocate`,
+            // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+            // released it.
+                unsafe { AHardwareBuffer_writeToParcel(this.0.as_ptr(), parcel.as_native_mut()) };
+            status_result(status)
+        } else {
+            parcel.write(&NULL_PARCELABLE_FLAG)
+        }
+    }
+}
+
+impl Deserialize for HardwareBuffer {
+    type UninitType = Option<Self>;
+
+    fn uninit() -> Option<Self> {
+        None
+    }
+
+    fn from_init(value: Self) -> Option<Self> {
+        Some(value)
+    }
+
+    fn deserialize(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
+        DeserializeOption::deserialize_option(parcel)
+            .transpose()
+            .unwrap_or(Err(StatusCode::UNEXPECTED_NULL))
+    }
+}
+
+impl DeserializeOption for HardwareBuffer {
+    fn deserialize_option(parcel: &BorrowedParcel) -> Result<Option<Self>, StatusCode> {
+        let present: i32 = parcel.read()?;
+        match present {
+            NULL_PARCELABLE_FLAG => Ok(None),
+            NON_NULL_PARCELABLE_FLAG => {
+                let mut buffer = null_mut();
+
+                let status =
+                // SAFETY: Both pointers must be valid because they are obtained from references.
+                // `AHardwareBuffer_readFromParcel` doesn't store them or do anything else special
+                // with them. If it returns success then it will have allocated a new
+                // `AHardwareBuffer` and incremented the reference count, so we can use it until we
+                // release it.
+                    unsafe { AHardwareBuffer_readFromParcel(parcel.as_native(), &mut buffer) };
+
+                status_result(status)?;
+
+                Ok(Some(Self(NonNull::new(buffer).expect(
+                    "AHardwareBuffer_readFromParcel returned success but didn't allocate buffer",
+                ))))
+            }
+            _ => Err(StatusCode::BAD_VALUE),
+        }
+    }
+}
+
+impl SerializeArray for HardwareBuffer {}
+
+impl DeserializeArray for HardwareBuffer {}
+
 // SAFETY: The underlying *AHardwareBuffers can be moved between threads.
 unsafe impl Send for HardwareBuffer {}
 
diff --git a/libs/nativewindow/rust/sys/nativewindow_bindings.h b/libs/nativewindow/rust/sys/nativewindow_bindings.h
index e652aee..4525a42 100644
--- a/libs/nativewindow/rust/sys/nativewindow_bindings.h
+++ b/libs/nativewindow/rust/sys/nativewindow_bindings.h
@@ -16,5 +16,6 @@
 
 #include <android/data_space.h>
 #include <android/hardware_buffer.h>
+#include <android/hardware_buffer_aidl.h>
 #include <android/hdr_metadata.h>
 #include <android/native_window.h>
diff --git a/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp b/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
index f24a4f1..5bf6560 100644
--- a/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
+++ b/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
@@ -18,7 +18,6 @@
 
 #include "SkiaMemoryReporter.h"
 
-#include <SkString.h>
 #include <android-base/stringprintf.h>
 #include <log/log_main.h>
 
@@ -142,7 +141,7 @@
                 TraceValue traceValue = convertUnits(result->second);
                 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
                 StringAppendF(&log, "  %s: %.2f %s (%d %s)\n", categoryItem->first.c_str(),
-                              traceValue.value, traceValue.units, traceValue.count, entry);
+                              traceValue.value, traceValue.units.c_str(), traceValue.count, entry);
             }
             if (mItemize) {
                 for (const auto& individualItem : resultsMap) {
@@ -153,7 +152,7 @@
                         auto result = individualItem.second.find("size");
                         TraceValue size = convertUnits(result->second);
                         StringAppendF(&log, "    %s: size[%.2f %s]", individualItem.first.c_str(),
-                                      size.value, size.units);
+                                      size.value, size.units.c_str());
                         if (!wrappedResources) {
                             for (const auto& itemValues : individualItem.second) {
                                 if (strcmp("size", itemValues.first) == 0) {
@@ -162,10 +161,10 @@
                                 TraceValue traceValue = convertUnits(itemValues.second);
                                 if (traceValue.value == 0.0f) {
                                     StringAppendF(&log, " %s[%s]", itemValues.first,
-                                                  traceValue.units);
+                                                  traceValue.units.c_str());
                                 } else {
                                     StringAppendF(&log, " %s[%.2f %s]", itemValues.first,
-                                                  traceValue.value, traceValue.units);
+                                                  traceValue.value, traceValue.units.c_str());
                                 }
                             }
                         }
@@ -184,16 +183,16 @@
     TraceValue total = convertUnits(mTotalSize);
     TraceValue purgeable = convertUnits(mPurgeableSize);
     StringAppendF(&log, " %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
-                  total.value, total.units, purgeable.value, purgeable.units);
+                  total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
 }
 
 SkiaMemoryReporter::TraceValue SkiaMemoryReporter::convertUnits(const TraceValue& value) {
     TraceValue output(value);
-    if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+    if (SkString("bytes") == output.units && output.value >= 1024) {
         output.value = output.value / 1024.0f;
         output.units = "KB";
     }
-    if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+    if (SkString("KB") == output.units && output.value >= 1024) {
         output.value = output.value / 1024.0f;
         output.units = "MB";
     }
diff --git a/libs/renderengine/skia/debug/SkiaMemoryReporter.h b/libs/renderengine/skia/debug/SkiaMemoryReporter.h
index dbbd65b..da91674 100644
--- a/libs/renderengine/skia/debug/SkiaMemoryReporter.h
+++ b/libs/renderengine/skia/debug/SkiaMemoryReporter.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <SkString.h>
 #include <SkTraceMemoryDump.h>
 
 #include <string>
@@ -75,7 +76,7 @@
         TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
         TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
 
-        const char* units;
+        SkString units;
         float value;
         int count;
     };
@@ -104,4 +105,4 @@
 
 } /* namespace skia */
 } /* namespace renderengine */
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 45649dd..7dfbf94 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -634,11 +634,10 @@
                                                     const MotionEntry& entry) {
     std::vector<TouchedWindow> out;
     const int32_t maskedAction = MotionEvent::getActionMasked(entry.action);
-    if (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER &&
-        maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE &&
-        maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
-        // Not a hover event - don't need to do anything
-        return out;
+
+    if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
+        // ACTION_SCROLL events should not affect the hovering pointer dispatch
+        return {};
     }
 
     // We should consider all hovering pointers here. But for now, just use the first one
@@ -1315,8 +1314,9 @@
         if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
             std::bitset<MAX_POINTER_ID + 1> pointerIds;
             pointerIds.set(pointerId);
-            addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE, pointerIds,
-                                  /*firstDownTimeInTarget=*/std::nullopt, outsideTargets);
+            addPointerWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
+                                         pointerIds,
+                                         /*firstDownTimeInTarget=*/std::nullopt, outsideTargets);
         }
     }
     return outsideTargets;
@@ -1821,7 +1821,7 @@
     std::vector<InputTarget> inputTargets;
     addWindowTargetLocked(focusedWindow,
                           InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS,
-                          /*pointerIds=*/{}, getDownTime(*entry), inputTargets);
+                          getDownTime(*entry), inputTargets);
 
     // Add monitor channels from event's or focused display.
     addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
@@ -1905,7 +1905,6 @@
     // Identify targets.
     std::vector<InputTarget> inputTargets;
 
-    bool conflictingPointerActions = false;
     InputEventInjectionResult injectionResult;
     if (isPointerEvent) {
         // Pointer event.  (eg. touchscreen)
@@ -1917,8 +1916,7 @@
         }
 
         inputTargets =
-                findTouchedWindowTargetsLocked(currentTime, *entry, &conflictingPointerActions,
-                                               /*byref*/ injectionResult);
+                findTouchedWindowTargetsLocked(currentTime, *entry, /*byref*/ injectionResult);
         LOG_ALWAYS_FATAL_IF(injectionResult != InputEventInjectionResult::SUCCEEDED &&
                             !inputTargets.empty());
     } else {
@@ -1930,7 +1928,7 @@
             addWindowTargetLocked(focusedWindow,
                                   InputTarget::Flags::FOREGROUND |
                                           InputTarget::Flags::DISPATCH_AS_IS,
-                                  /*pointerIds=*/{}, getDownTime(*entry), inputTargets);
+                                  getDownTime(*entry), inputTargets);
         }
     }
     if (injectionResult == InputEventInjectionResult::PENDING) {
@@ -1954,11 +1952,6 @@
     addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
 
     // Dispatch the motion.
-    if (conflictingPointerActions) {
-        CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                   "conflicting pointer actions");
-        synthesizeCancelationEventsForAllConnectionsLocked(options);
-    }
     dispatchEventLocked(currentTime, entry, inputTargets);
     return true;
 }
@@ -2258,7 +2251,7 @@
 }
 
 std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
-        nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
+        nsecs_t currentTime, const MotionEntry& entry,
         InputEventInjectionResult& outInjectionResult) {
     ATRACE_CALL();
 
@@ -2283,20 +2276,13 @@
     }
 
     bool isSplit = shouldSplitTouch(tempTouchState, entry);
-    bool switchedDevice = false;
-    if (oldState != nullptr) {
-        std::set<int32_t> oldActiveDevices = oldState->getActiveDeviceIds();
-        const bool anotherDeviceIsActive =
-                oldActiveDevices.count(entry.deviceId) == 0 && !oldActiveDevices.empty();
-        switchedDevice |= anotherDeviceIsActive;
-    }
 
     const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE ||
                                 maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
                                 maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
     // A DOWN could be generated from POINTER_DOWN if the initial pointers did not land into any
     // touchable windows.
-    const bool wasDown = oldState != nullptr && oldState->isDown();
+    const bool wasDown = oldState != nullptr && oldState->isDown(entry.deviceId);
     const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) ||
             (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown);
     const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL ||
@@ -2304,34 +2290,19 @@
             maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE;
     const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
 
-    // If pointers are already down, let's finish the current gesture and ignore the new events
-    // from another device. However, if the new event is a down event, let's cancel the current
-    // touch and let the new one take over.
-    if (switchedDevice && wasDown && !isDown) {
-        LOG(INFO) << "Dropping event because a pointer for another device "
-                  << " is already down in display " << displayId << ": " << entry.getDescription();
-        // TODO(b/211379801): test multiple simultaneous input streams.
-        outInjectionResult = InputEventInjectionResult::FAILED;
-        return {}; // wrong device
+    if (newGesture) {
+        isSplit = false;
     }
 
-    if (newGesture) {
-        // If a new gesture is starting, clear the touch state completely.
-        tempTouchState.reset();
-        isSplit = false;
-    } else if (switchedDevice && maskedAction == AMOTION_EVENT_ACTION_MOVE) {
-        ALOGI("Dropping move event because a pointer for a different device is already active "
-              "in display %" PRId32,
-              displayId);
-        // TODO(b/211379801): test multiple simultaneous input streams.
-        outInjectionResult = InputEventInjectionResult::FAILED;
-        return {}; // wrong device
+    if (isDown && tempTouchState.hasHoveringPointers(entry.deviceId)) {
+        // Compatibility behaviour: ACTION_DOWN causes HOVER_EXIT to get generated.
+        tempTouchState.clearHoveringPointers(entry.deviceId);
     }
 
     if (isHoverAction) {
         // For hover actions, we will treat 'tempTouchState' as a new state, so let's erase
         // all of the existing hovering pointers and recompute.
-        tempTouchState.clearHoveringPointers();
+        tempTouchState.clearHoveringPointers(entry.deviceId);
     }
 
     if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
@@ -2402,8 +2373,7 @@
                 continue;
             }
 
-            if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
-                maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+            if (isHoverAction) {
                 // The "windowHandle" is the target of this hovering pointer.
                 tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId);
             }
@@ -2426,31 +2396,24 @@
             }
 
             // Update the temporary touch state.
-            std::bitset<MAX_POINTER_ID + 1> pointerIds;
+
             if (!isHoverAction) {
+                std::bitset<MAX_POINTER_ID + 1> pointerIds;
                 pointerIds.set(pointerId);
-            }
-
-            const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
-                    maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
-
-            // TODO(b/211379801): Currently, even if pointerIds are empty (hover case), we would
-            // still add a window to the touch state. We should avoid doing that, but some of the
-            // later checks ("at least one foreground window") rely on this in order to dispatch
-            // the event properly, so that needs to be updated, possibly by looking at InputTargets.
-            tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, entry.deviceId, pointerIds,
-                                             isDownOrPointerDown
-                                                     ? std::make_optional(entry.eventTime)
-                                                     : std::nullopt);
-
-            // If this is the pointer going down and the touched window has a wallpaper
-            // then also add the touched wallpaper windows so they are locked in for the duration
-            // of the touch gesture.
-            // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
-            // engine only supports touch events.  We would need to add a mechanism similar
-            // to View.onGenericMotionEvent to enable wallpapers to handle these events.
-            if (isDownOrPointerDown) {
-                if (targetFlags.test(InputTarget::Flags::FOREGROUND) &&
+                const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
+                        maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
+                tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, entry.deviceId,
+                                                 pointerIds,
+                                                 isDownOrPointerDown
+                                                         ? std::make_optional(entry.eventTime)
+                                                         : std::nullopt);
+                // If this is the pointer going down and the touched window has a wallpaper
+                // then also add the touched wallpaper windows so they are locked in for the
+                // duration of the touch gesture. We do not collect wallpapers during HOVER_MOVE or
+                // SCROLL because the wallpaper engine only supports touch events.  We would need to
+                // add a mechanism similar to View.onGenericMotionEvent to enable wallpapers to
+                // handle these events.
+                if (isDownOrPointerDown && targetFlags.test(InputTarget::Flags::FOREGROUND) &&
                     windowHandle->getInfo()->inputConfig.test(
                             gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
                     sp<WindowInfoHandle> wallpaper = findWallpaperWindowBelow(windowHandle);
@@ -2488,8 +2451,10 @@
         /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
 
         // If the pointer is not currently down, then ignore the event.
-        if (!tempTouchState.isDown() && maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
-            LOG(INFO) << "Dropping event because the pointer is not down or we previously "
+        if (!tempTouchState.isDown(entry.deviceId) &&
+            maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
+            LOG(INFO) << "Dropping event because the pointer for device " << entry.deviceId
+                      << " is not down or we previously "
                          "dropped the pointer down event in display "
                       << displayId << ": " << entry.getDescription();
             outInjectionResult = InputEventInjectionResult::FAILED;
@@ -2538,7 +2503,7 @@
 
             if (newTouchedWindowHandle != nullptr &&
                 !haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) {
-                ALOGD("Touch is slipping out of window %s into window %s in display %" PRId32,
+                ALOGI("Touch is slipping out of window %s into window %s in display %" PRId32,
                       oldTouchedWindowHandle->getName().c_str(),
                       newTouchedWindowHandle->getName().c_str(), displayId);
 
@@ -2549,9 +2514,11 @@
 
                 const TouchedWindow& touchedWindow =
                         tempTouchState.getTouchedWindow(oldTouchedWindowHandle);
-                addWindowTargetLocked(oldTouchedWindowHandle,
-                                      InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, pointerIds,
-                                      touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
+                addPointerWindowTargetLocked(oldTouchedWindowHandle,
+                                             InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
+                                             pointerIds,
+                                             touchedWindow.getDownTimeInTarget(entry.deviceId),
+                                             targets);
 
                 // Make a slippery entrance into the new window.
                 if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
@@ -2664,16 +2631,14 @@
 
     // Output targets from the touch state.
     for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
-        if (!touchedWindow.hasTouchingPointers(entry.deviceId) &&
-            !touchedWindow.hasHoveringPointers(entry.deviceId)) {
-            // Windows with hovering pointers are getting persisted inside TouchState.
-            // Do not send this event to those windows.
+        std::bitset<MAX_POINTER_ID + 1> touchingPointers =
+                touchedWindow.getTouchingPointers(entry.deviceId);
+        if (touchingPointers.none()) {
             continue;
         }
-
-        addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
-                              touchedWindow.getTouchingPointers(entry.deviceId),
-                              touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
+        addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
+                                     touchingPointers,
+                                     touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
     }
 
     // During targeted injection, only allow owned targets to receive events
@@ -2706,37 +2671,30 @@
     }
 
     outInjectionResult = InputEventInjectionResult::SUCCEEDED;
-    // Drop the outside or hover touch windows since we will not care about them
-    // in the next iteration.
-    tempTouchState.filterNonAsIsTouchWindows();
 
-    // Update final pieces of touch state if the injector had permission.
-    if (switchedDevice) {
-        if (DEBUG_FOCUS) {
-            ALOGD("Conflicting pointer actions: Switched to a different device.");
+    for (TouchedWindow& touchedWindow : tempTouchState.windows) {
+        // Targets that we entered in a slippery way will now become AS-IS targets
+        if (touchedWindow.targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) {
+            touchedWindow.targetFlags.clear(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER);
+            touchedWindow.targetFlags |= InputTarget::Flags::DISPATCH_AS_IS;
         }
-        *outConflictingPointerActions = true;
     }
 
+    // Update final pieces of touch state if the injector had permission.
     if (isHoverAction) {
-        // Started hovering, therefore no longer down.
-        if (oldState && oldState->isDown()) {
-            ALOGD_IF(DEBUG_FOCUS,
-                     "Conflicting pointer actions: Hover received while pointer was down.");
-            *outConflictingPointerActions = true;
+        if (oldState && oldState->isDown(entry.deviceId)) {
+            // Started hovering, but the device is already down: reject the hover event
+            LOG(ERROR) << "Got hover event " << entry.getDescription()
+                       << " but the device is already down " << oldState->dump();
+            outInjectionResult = InputEventInjectionResult::FAILED;
+            return {};
         }
     } else if (maskedAction == AMOTION_EVENT_ACTION_UP) {
         // Pointer went up.
         tempTouchState.removeTouchingPointer(entry.deviceId, entry.pointerProperties[0].id);
     } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
         // All pointers up or canceled.
-        tempTouchState.reset();
-    } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
-        // First pointer went down.
-        if (oldState && (oldState->isDown() || oldState->hasHoveringPointers())) {
-            ALOGD("Conflicting pointer actions: Down received while already down or hovering.");
-            *outConflictingPointerActions = true;
-        }
+        tempTouchState.removeAllPointersForDevice(entry.deviceId);
     } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
         // One pointer went up.
         const int32_t pointerIndex = MotionEvent::getActionIndex(action);
@@ -2885,7 +2843,6 @@
 
 void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
                                             ftl::Flags<InputTarget::Flags> targetFlags,
-                                            std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                             std::optional<nsecs_t> firstDownTimeInTarget,
                                             std::vector<InputTarget>& inputTargets) const {
     std::vector<InputTarget>::iterator it =
@@ -2907,8 +2864,54 @@
         it = inputTargets.end() - 1;
     }
 
-    ALOG_ASSERT(it->flags == targetFlags);
-    ALOG_ASSERT(it->globalScaleFactor == windowInfo->globalScaleFactor);
+    LOG_ALWAYS_FATAL_IF(it->flags != targetFlags);
+    LOG_ALWAYS_FATAL_IF(it->globalScaleFactor != windowInfo->globalScaleFactor);
+}
+
+void InputDispatcher::addPointerWindowTargetLocked(
+        const sp<android::gui::WindowInfoHandle>& windowHandle,
+        ftl::Flags<InputTarget::Flags> targetFlags, std::bitset<MAX_POINTER_ID + 1> pointerIds,
+        std::optional<nsecs_t> firstDownTimeInTarget, std::vector<InputTarget>& inputTargets) const
+        REQUIRES(mLock) {
+    if (pointerIds.none()) {
+        for (const auto& target : inputTargets) {
+            LOG(INFO) << "Target: " << target;
+        }
+        LOG(FATAL) << "No pointers specified for " << windowHandle->getName();
+        return;
+    }
+    std::vector<InputTarget>::iterator it =
+            std::find_if(inputTargets.begin(), inputTargets.end(),
+                         [&windowHandle](const InputTarget& inputTarget) {
+                             return inputTarget.inputChannel->getConnectionToken() ==
+                                     windowHandle->getToken();
+                         });
+
+    // This is a hack, because the actual entry could potentially be an ACTION_DOWN event that
+    // causes a HOVER_EXIT to be generated. That means that the same entry of ACTION_DOWN would
+    // have DISPATCH_AS_HOVER_EXIT and DISPATCH_AS_IS. And therefore, we have to create separate
+    // input targets for hovering pointers and for touching pointers.
+    // If we picked an existing input target above, but it's for HOVER_EXIT - let's use a new
+    // target instead.
+    if (it != inputTargets.end() && it->flags.test(InputTarget::Flags::DISPATCH_AS_HOVER_EXIT)) {
+        // Force the code below to create a new input target
+        it = inputTargets.end();
+    }
+
+    const WindowInfo* windowInfo = windowHandle->getInfo();
+
+    if (it == inputTargets.end()) {
+        std::optional<InputTarget> target =
+                createInputTargetLocked(windowHandle, targetFlags, firstDownTimeInTarget);
+        if (!target) {
+            return;
+        }
+        inputTargets.push_back(*target);
+        it = inputTargets.end() - 1;
+    }
+
+    LOG_ALWAYS_FATAL_IF(it->flags != targetFlags);
+    LOG_ALWAYS_FATAL_IF(it->globalScaleFactor != windowInfo->globalScaleFactor);
 
     it->addPointers(pointerIds, windowInfo->transform);
 }
@@ -3366,6 +3369,27 @@
                 dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
             }
 
+            // Check if we need to cancel any of the ongoing gestures. We don't support multiple
+            // devices being active at the same time in the same window, so if a new device is
+            // active, cancel the gesture from the old device.
+
+            std::unique_ptr<EventEntry> cancelEvent =
+                    connection->inputState
+                            .cancelConflictingInputStream(motionEntry,
+                                                          dispatchEntry->resolvedAction);
+            if (cancelEvent != nullptr) {
+                LOG(INFO) << "Canceling pointers for device " << motionEntry.deviceId << " in "
+                          << connection->getInputChannelName() << " with event "
+                          << cancelEvent->getDescription();
+                std::unique_ptr<DispatchEntry> cancelDispatchEntry =
+                        createDispatchEntry(inputTarget, std::move(cancelEvent),
+                                            InputTarget::Flags::DISPATCH_AS_IS);
+
+                // Send these cancel events to the queue before sending the event from the new
+                // device.
+                connection->outboundQueue.emplace_back(std::move(cancelDispatchEntry));
+            }
+
             if (!connection->inputState.trackMotion(motionEntry, dispatchEntry->resolvedAction,
                                                     dispatchEntry->resolvedFlags)) {
                 LOG(WARNING) << "channel " << connection->getInputChannelName()
@@ -3975,7 +3999,7 @@
                         : std::nullopt;
                 if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) {
                     addWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS,
-                                          /*pointerIds=*/{}, keyEntry.downTime, targets);
+                                          keyEntry.downTime, targets);
                 } else {
                     targets.emplace_back(fallbackTarget);
                 }
@@ -3994,8 +4018,8 @@
                          pointerIndex++) {
                         pointerIds.set(motionEntry.pointerProperties[pointerIndex].id);
                     }
-                    addWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS, pointerIds,
-                                          motionEntry.downTime, targets);
+                    addPointerWindowTargetLocked(window, InputTarget::Flags::DISPATCH_AS_IS,
+                                                 pointerIds, motionEntry.downTime, targets);
                 } else {
                     targets.emplace_back(fallbackTarget);
                     const auto it = mDisplayInfos.find(motionEntry.displayId);
@@ -4069,8 +4093,8 @@
                          pointerIndex++) {
                         pointerIds.set(motionEntry.pointerProperties[pointerIndex].id);
                     }
-                    addWindowTargetLocked(windowHandle, targetFlags, pointerIds,
-                                          motionEntry.downTime, targets);
+                    addPointerWindowTargetLocked(windowHandle, targetFlags, pointerIds,
+                                                 motionEntry.downTime, targets);
                 } else {
                     targets.emplace_back(InputTarget{.inputChannel = connection->inputChannel,
                                                      .flags = targetFlags});
@@ -5139,10 +5163,8 @@
         for (size_t i = 0; i < state.windows.size();) {
             TouchedWindow& touchedWindow = state.windows[i];
             if (getWindowHandleLocked(touchedWindow.windowHandle) == nullptr) {
-                if (DEBUG_FOCUS) {
-                    ALOGD("Touched window was removed: %s in display %" PRId32,
-                          touchedWindow.windowHandle->getName().c_str(), displayId);
-                }
+                LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName()
+                          << " in display %" << displayId;
                 std::shared_ptr<InputChannel> touchedInputChannel =
                         getInputChannelLocked(touchedWindow.windowHandle->getToken());
                 if (touchedInputChannel != nullptr) {
@@ -5973,9 +5995,8 @@
         return BAD_VALUE;
     }
     std::set<int32_t> deviceIds = windowPtr->getTouchingDeviceIds();
-    if (deviceIds.size() != 1) {
-        LOG(WARNING) << "Can't pilfer. Currently touching devices: " << dumpSet(deviceIds)
-                     << " in window: " << windowPtr->dump();
+    if (deviceIds.empty()) {
+        LOG(WARNING) << "Can't pilfer: no touching devices in window: " << windowPtr->dump();
         return BAD_VALUE;
     }
 
@@ -6818,10 +6839,11 @@
 
     if (oldWallpaper != nullptr) {
         const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper);
-        addWindowTargetLocked(oldWallpaper,
-                              oldTouchedWindow.targetFlags |
-                                      InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
-                              pointerIds, oldTouchedWindow.getDownTimeInTarget(deviceId), targets);
+        addPointerWindowTargetLocked(oldWallpaper,
+                                     oldTouchedWindow.targetFlags |
+                                             InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
+                                     pointerIds, oldTouchedWindow.getDownTimeInTarget(deviceId),
+                                     targets);
         state.removeTouchingPointerFromWindow(deviceId, pointerId, oldWallpaper);
     }
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 0020301..a1127a0 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -524,7 +524,7 @@
             nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
             android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
     std::vector<InputTarget> findTouchedWindowTargetsLocked(
-            nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
+            nsecs_t currentTime, const MotionEntry& entry,
             android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
     std::vector<Monitor> selectResponsiveMonitorsLocked(
             const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
@@ -535,9 +535,13 @@
             std::optional<nsecs_t> firstDownTimeInTarget) const REQUIRES(mLock);
     void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                ftl::Flags<InputTarget::Flags> targetFlags,
-                               std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                std::optional<nsecs_t> firstDownTimeInTarget,
                                std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
+    void addPointerWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
+                                      ftl::Flags<InputTarget::Flags> targetFlags,
+                                      std::bitset<MAX_POINTER_ID + 1> pointerIds,
+                                      std::optional<nsecs_t> firstDownTimeInTarget,
+                                      std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
     void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
             REQUIRES(mLock);
     void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index b348808..09b5186 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -24,6 +24,19 @@
 
 namespace android::inputdispatcher {
 
+namespace {
+bool isHoverAction(int32_t action) {
+    switch (MotionEvent::getActionMasked(action)) {
+        case AMOTION_EVENT_ACTION_HOVER_ENTER:
+        case AMOTION_EVENT_ACTION_HOVER_MOVE:
+        case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+            return true;
+        }
+    }
+    return false;
+}
+} // namespace
+
 InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {}
 
 InputState::~InputState() {}
@@ -89,6 +102,28 @@
  *  false if the incoming event should be dropped.
  */
 bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t flags) {
+    // Don't track non-pointer events
+    if (!isFromSource(entry.source, AINPUT_SOURCE_CLASS_POINTER)) {
+        // This is a focus-dispatched event; we don't track its state.
+        return true;
+    }
+
+    if (!mMotionMementos.empty()) {
+        const MotionMemento& lastMemento = mMotionMementos.back();
+        if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
+            !isStylusEvent(entry.source, entry.pointerProperties)) {
+            // We already have a stylus stream, and the new event is not from stylus.
+            if (!lastMemento.hovering) {
+                // If stylus is currently down, reject the new event unconditionally.
+                return false;
+            }
+        }
+        if (!lastMemento.hovering && isHoverAction(action)) {
+            // Reject hovers if already down
+            return false;
+        }
+    }
+
     int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
     switch (actionMasked) {
         case AMOTION_EVENT_ACTION_UP:
@@ -266,6 +301,136 @@
     return pointerProperties.size();
 }
 
+bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry,
+                                            int32_t resolvedAction) const {
+    if (!isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER)) {
+        // This is a focus-dispatched event that should not affect the previous stream.
+        return false;
+    }
+
+    // New MotionEntry pointer event is coming in.
+
+    // If this is a new gesture, and it's from a different device, then, in general, we will cancel
+    // the current gesture.
+    // However, because stylus should be preferred over touch, we need to treat some cases in a
+    // special way.
+    if (mMotionMementos.empty()) {
+        // There is no ongoing pointer gesture, so there is nothing to cancel
+        return false;
+    }
+
+    const MotionMemento& lastMemento = mMotionMementos.back();
+    const int32_t actionMasked = MotionEvent::getActionMasked(resolvedAction);
+
+    // For compatibility, only one input device can be active at a time in the same window.
+    if (lastMemento.deviceId == motionEntry.deviceId) {
+        // In general, the same device should produce self-consistent streams so nothing needs to
+        // be canceled. But there is one exception:
+        // Sometimes ACTION_DOWN is received without a corresponding HOVER_EXIT. To account for
+        // that, cancel the previous hovering stream
+        if (actionMasked == AMOTION_EVENT_ACTION_DOWN && lastMemento.hovering) {
+            return true;
+        }
+
+        // Use the previous stream cancellation logic to generate all HOVER_EXIT events.
+        // If this hover event was generated as a result of the pointer leaving the window,
+        // the HOVER_EXIT event should have the same coordinates as the previous
+        // HOVER_MOVE event in this stream. Ensure that all HOVER_EXITs have the same
+        // coordinates as the previous event by cancelling the stream here. With this approach, the
+        // HOVER_EXIT event is generated from the previous event.
+        if (actionMasked == AMOTION_EVENT_ACTION_HOVER_EXIT && lastMemento.hovering) {
+            return true;
+        }
+
+        // If the stream changes its source, just cancel the current gesture to be safe. It's
+        // possible that the app isn't handling source changes properly
+        if (motionEntry.source != lastMemento.source) {
+            LOG(INFO) << "Canceling stream: last source was "
+                      << inputEventSourceToString(lastMemento.source) << " and new event is "
+                      << motionEntry;
+            return true;
+        }
+
+        // If the injection is happening into two different displays, the same injected device id
+        // could be going into both. And at this time, if mirroring is active, the same connection
+        // would receive different events from each display. Since the TouchStates are per-display,
+        // it's unlikely that those two streams would be consistent with each other. Therefore,
+        // cancel the previous gesture if the display id changes.
+        if (motionEntry.displayId != lastMemento.displayId) {
+            LOG(INFO) << "Canceling stream: last displayId was "
+                      << inputEventSourceToString(lastMemento.displayId) << " and new event is "
+                      << motionEntry;
+            return true;
+        }
+
+        return false;
+    }
+
+    // We want stylus down to block touch and other source types, but stylus hover should not
+    // have such an effect.
+    if (isHoverAction(motionEntry.action) && !lastMemento.hovering) {
+        // New event is a hover. Keep the current non-hovering gesture instead
+        return false;
+    }
+
+    if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && !lastMemento.hovering) {
+        // We have non-hovering stylus already active.
+        if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
+            actionMasked == AMOTION_EVENT_ACTION_DOWN) {
+            // If this new event is a stylus from a different device going down, then cancel the old
+            // stylus and allow the new stylus to take over
+            return true;
+        }
+
+        // Keep the current stylus gesture.
+        return false;
+    }
+
+    // Cancel the current gesture if this is a start of a new gesture from a new device.
+    if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
+        actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
+        return true;
+    }
+    // By default, don't cancel any events.
+    return false;
+}
+
+std::unique_ptr<EventEntry> InputState::cancelConflictingInputStream(const MotionEntry& motionEntry,
+                                                                     int32_t resolvedAction) {
+    if (!shouldCancelPreviousStream(motionEntry, resolvedAction)) {
+        return {};
+    }
+
+    const MotionMemento& memento = mMotionMementos.back();
+
+    // Cancel the last device stream
+    std::unique_ptr<MotionEntry> cancelEntry =
+            createCancelEntryForMemento(memento, motionEntry.eventTime);
+
+    if (!trackMotion(*cancelEntry, cancelEntry->action, cancelEntry->flags)) {
+        LOG(FATAL) << "Generated inconsistent cancel event!";
+    }
+    return cancelEntry;
+}
+
+std::unique_ptr<MotionEntry> InputState::createCancelEntryForMemento(const MotionMemento& memento,
+                                                                     nsecs_t eventTime) const {
+    const int32_t action =
+            memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL;
+    int32_t flags = memento.flags;
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    return std::make_unique<MotionEntry>(mIdGenerator.nextId(), eventTime, memento.deviceId,
+                                         memento.source, memento.displayId, memento.policyFlags,
+                                         action, /*actionButton=*/0, flags, AMETA_NONE,
+                                         /*buttonState=*/0, MotionClassification::NONE,
+                                         AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
+                                         memento.yPrecision, memento.xCursorPosition,
+                                         memento.yCursorPosition, memento.downTime,
+                                         memento.pointerProperties, memento.pointerCoords);
+}
+
 std::vector<std::unique_ptr<EventEntry>> InputState::synthesizeCancelationEvents(
         nsecs_t currentTime, const CancelationOptions& options) {
     std::vector<std::unique_ptr<EventEntry>> events;
@@ -284,24 +449,7 @@
     for (const MotionMemento& memento : mMotionMementos) {
         if (shouldCancelMotion(memento, options)) {
             if (options.pointerIds == std::nullopt) {
-                const int32_t action = memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT
-                                                        : AMOTION_EVENT_ACTION_CANCEL;
-                int32_t flags = memento.flags;
-                if (action == AMOTION_EVENT_ACTION_CANCEL) {
-                    flags |= AMOTION_EVENT_FLAG_CANCELED;
-                }
-                events.push_back(
-                        std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
-                                                      memento.deviceId, memento.source,
-                                                      memento.displayId, memento.policyFlags,
-                                                      action, /*actionButton=*/0, flags, AMETA_NONE,
-                                                      /*buttonState=*/0, MotionClassification::NONE,
-                                                      AMOTION_EVENT_EDGE_FLAG_NONE,
-                                                      memento.xPrecision, memento.yPrecision,
-                                                      memento.xCursorPosition,
-                                                      memento.yCursorPosition, memento.downTime,
-                                                      memento.pointerProperties,
-                                                      memento.pointerCoords));
+                events.push_back(createCancelEntryForMemento(memento, currentTime));
             } else {
                 std::vector<std::unique_ptr<MotionEntry>> pointerCancelEvents =
                         synthesizeCancelationEventsForPointers(memento, options.pointerIds.value(),
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index 3adbba0..686c432 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -48,6 +48,10 @@
     // and should be skipped.
     bool trackMotion(const MotionEntry& entry, int32_t action, int32_t flags);
 
+    // Create cancel events for the previous stream if the current motionEntry requires it.
+    std::unique_ptr<EventEntry> cancelConflictingInputStream(const MotionEntry& motionEntry,
+                                                             int32_t resolvedAction);
+
     // Synthesizes cancelation events for the current state and resets the tracked state.
     std::vector<std::unique_ptr<EventEntry>> synthesizeCancelationEvents(
             nsecs_t currentTime, const CancelationOptions& options);
@@ -123,6 +127,9 @@
 
     static bool shouldCancelKey(const KeyMemento& memento, const CancelationOptions& options);
     static bool shouldCancelMotion(const MotionMemento& memento, const CancelationOptions& options);
+    bool shouldCancelPreviousStream(const MotionEntry& motionEntry, int32_t resolvedAction) const;
+    std::unique_ptr<MotionEntry> createCancelEntryForMemento(const MotionMemento& memento,
+                                                             nsecs_t eventTime) const;
 
     // Synthesizes pointer cancel events for a particular set of pointers.
     std::vector<std::unique_ptr<MotionEntry>> synthesizeCancelationEventsForPointers(
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 9dcf615..2ead171 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <gui/WindowInfo.h>
 
@@ -31,15 +32,6 @@
     *this = TouchState();
 }
 
-std::set<int32_t> TouchState::getActiveDeviceIds() const {
-    std::set<int32_t> out;
-    for (const TouchedWindow& w : windows) {
-        std::set<int32_t> deviceIds = w.getActiveDeviceIds();
-        out.insert(deviceIds.begin(), deviceIds.end());
-    }
-    return out;
-}
-
 bool TouchState::hasTouchingPointers(DeviceId deviceId) const {
     return std::any_of(windows.begin(), windows.end(), [&](const TouchedWindow& window) {
         return window.hasTouchingPointers(deviceId);
@@ -65,9 +57,9 @@
     }
 }
 
-void TouchState::clearHoveringPointers() {
+void TouchState::clearHoveringPointers(DeviceId deviceId) {
     for (TouchedWindow& touchedWindow : windows) {
-        touchedWindow.clearHoveringPointers();
+        touchedWindow.removeAllHoveringPointersForDevice(deviceId);
     }
     clearWindowsWithoutPointers();
 }
@@ -82,9 +74,16 @@
                                    ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
                                    std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
                                    std::optional<nsecs_t> firstDownTimeInTarget) {
+    if (touchingPointerIds.none()) {
+        LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName();
+        return;
+    }
     for (TouchedWindow& touchedWindow : windows) {
         // We do not compare windows by token here because two windows that share the same token
-        // may have a different transform
+        // may have a different transform. They will be combined later when we create InputTargets.
+        // At that point, per-pointer window transform will be considered.
+        // An alternative design choice here would have been to compare here by token, but to
+        // store per-pointer transform.
         if (touchedWindow.windowHandle == windowHandle) {
             touchedWindow.targetFlags |= targetFlags;
             if (targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) {
@@ -237,14 +236,16 @@
     return *it;
 }
 
-bool TouchState::isDown() const {
-    return std::any_of(windows.begin(), windows.end(),
-                       [](const TouchedWindow& window) { return window.hasTouchingPointers(); });
+bool TouchState::isDown(DeviceId deviceId) const {
+    return std::any_of(windows.begin(), windows.end(), [&deviceId](const TouchedWindow& window) {
+        return window.hasTouchingPointers(deviceId);
+    });
 }
 
-bool TouchState::hasHoveringPointers() const {
-    return std::any_of(windows.begin(), windows.end(),
-                       [](const TouchedWindow& window) { return window.hasHoveringPointers(); });
+bool TouchState::hasHoveringPointers(DeviceId deviceId) const {
+    return std::any_of(windows.begin(), windows.end(), [&deviceId](const TouchedWindow& window) {
+        return window.hasHoveringPointers(deviceId);
+    });
 }
 
 std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(DeviceId deviceId,
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index f016936..e79c73b 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -39,8 +39,6 @@
     void reset();
     void clearWindowsWithoutPointers();
 
-    std::set<DeviceId> getActiveDeviceIds() const;
-
     bool hasTouchingPointers(DeviceId deviceId) const;
     void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
     void removeTouchingPointerFromWindow(DeviceId deviceId, int32_t pointerId,
@@ -52,7 +50,7 @@
     void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                     DeviceId deviceId, int32_t hoveringPointerId);
     void removeHoveringPointer(DeviceId deviceId, int32_t hoveringPointerId);
-    void clearHoveringPointers();
+    void clearHoveringPointers(DeviceId deviceId);
 
     void removeAllPointersForDevice(DeviceId deviceId);
     void removeWindowByToken(const sp<IBinder>& token);
@@ -72,8 +70,8 @@
     const TouchedWindow& getTouchedWindow(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const;
     // Whether any of the windows are currently being touched
-    bool isDown() const;
-    bool hasHoveringPointers() const;
+    bool isDown(DeviceId deviceId) const;
+    bool hasHoveringPointers(DeviceId deviceId) const;
 
     std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer(
             DeviceId deviceId, int32_t pointerId) const;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index ff4b425..5367751 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -45,12 +45,16 @@
     return state.hoveringPointerIds.any();
 }
 
-void TouchedWindow::clearHoveringPointers() {
-    for (auto& [_, state] : mDeviceStates) {
-        state.hoveringPointerIds.reset();
+void TouchedWindow::clearHoveringPointers(DeviceId deviceId) {
+    auto stateIt = mDeviceStates.find(deviceId);
+    if (stateIt == mDeviceStates.end()) {
+        return;
     }
-
-    std::erase_if(mDeviceStates, [](const auto& pair) { return !pair.second.hasPointers(); });
+    DeviceState& state = stateIt->second;
+    state.hoveringPointerIds.reset();
+    if (!state.hasPointers()) {
+        mDeviceStates.erase(stateIt);
+    }
 }
 
 bool TouchedWindow::hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const {
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 3f760c0..6d2283e 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -71,7 +71,7 @@
 
     void removeAllTouchingPointersForDevice(DeviceId deviceId);
     void removeAllHoveringPointersForDevice(DeviceId deviceId);
-    void clearHoveringPointers();
+    void clearHoveringPointers(DeviceId deviceId);
     std::string dump() const;
 
 private:
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 34323c3..3c87f71 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -2409,9 +2409,10 @@
 using InputDispatcherMultiDeviceTest = InputDispatcherTest;
 
 /**
- * One window. Stylus down on the window. Next, touch from another device goes down.
+ * One window. Stylus down on the window. Next, touch from another device goes down. Ensure that
+ * touch is dropped, because stylus should be preferred over touch.
  */
-TEST_F(InputDispatcherMultiDeviceTest, StylusDownAndTouchDown) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2434,34 +2435,31 @@
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
                                       .build());
-    // Touch cancels stylus
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId),
-                                     WithCoords(100, 110)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId),
-                                     WithCoords(140, 145)));
 
     // Touch move
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
                                       .build());
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
-                                     WithCoords(141, 146)));
+    // Touch is ignored because stylus is already down
 
-    // Subsequent stylus movements are dropped
+    // Subsequent stylus movements are delivered correctly
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
                                       .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+
     window->assertNoEvents();
 }
 
 /**
  * One window and one spy window. Stylus down on the window. Next, touch from another device goes
- * down.
+ * down. Ensure that touch is dropped, because stylus should be preferred over touch.
  * Similar test as above, but with added SPY window.
  */
-TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyAndTouchDown) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2497,30 +2495,28 @@
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
                                       .build());
-    window->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
-    // Subsequent stylus movements are dropped
+
+    // Touch is ignored because stylus is already down
+
+    // Subsequent stylus movements are delivered correctly
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
                                       .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                        WithCoords(101, 111)));
 
     window->assertNoEvents();
     spyWindow->assertNoEvents();
 }
 
 /**
- * One window. Stylus hover on the window. Next, touch from another device goes down.
+ * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
+ * touch is not dropped, because stylus hover should be ignored.
  */
-TEST_F(InputDispatcherMultiDeviceTest, StylusHoverAndTouchDown) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2548,19 +2544,174 @@
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
                                       .build());
-    window->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus hover is canceled because touch is down
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT),
+                                     WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId),
+                                     WithCoords(140, 145)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
+                                     WithCoords(141, 146)));
+
     // Subsequent stylus movements are ignored
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
                                       .build());
+
+    // but subsequent touches continue to be delivered
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
+                                     WithCoords(142, 147)));
+}
+
+/**
+ * One window. Touch down on the window. Then, stylus hover on the window from another device.
+ * Ensure that touch is not canceled, because stylus hover should be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus hover on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    // Stylus hover movement is dropped
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+                                      .build());
+    // Subsequent touch movements are delivered correctly
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
+                                     WithCoords(142, 147)));
+}
+
+/**
+ * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that
+ * the latest stylus takes over. That is, old stylus should be canceled and the new stylus should
+ * become active.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t stylusDeviceId1 = 3;
+    constexpr int32_t stylusDeviceId2 = 5;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(99).y(100))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(101))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId1)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1)));
+
+    // Second stylus down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId2)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(9).y(10))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId2)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(11))
+                                      .build());
+
+    // First stylus is canceled, second one takes over.
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId1)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId2)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId2)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102))
+                                      .build());
+    // Subsequent stylus movements are delivered correctly
     window->assertNoEvents();
 }
 
 /**
+ * One window. Touch down on the window. Then, stylus down on the window from another device.
+ * Ensure that is canceled, because stylus down should be preferred over touch.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus down on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Subsequent stylus movements are delivered correctly
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+}
+
+/**
  * Two windows: a window on the left and a window on the right.
  * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains
  * down. Then, on the left window, also place second touch pointer down.
@@ -2617,8 +2768,8 @@
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .build());
-    leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(mouseDeviceId)));
+    leftWindow->assertNoEvents();
+
     rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
 
     // Second touch pointer down on left window
@@ -2627,6 +2778,11 @@
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100))
                                       .build());
+    // Since this is now a new splittable pointer going down on the left window, and it's coming
+    // from a different device, the current gesture in the left window (pointer down) should first
+    // be canceled.
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(mouseDeviceId)));
     leftWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
     // This MOVE event is not necessary (doesn't carry any new information), but it's there in the
@@ -2672,8 +2828,6 @@
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(300).y(100))
                                       .build());
-    leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
 
@@ -2683,18 +2837,14 @@
                                       .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(120))
                                       .build());
     leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
-    rightWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(mouseDeviceId)));
 
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(310).y(110))
                                       .build());
-    leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
     rightWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
 
     leftWindow->assertNoEvents();
     rightWindow->assertNoEvents();
@@ -2745,29 +2895,30 @@
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .build());
-    leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
+    leftWindow->assertNoEvents();
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
 
-    // Stylus movements continue, but are ignored because the touch went down more recently.
+    // Spy window does not receive touch events, because stylus events take precedence, and it
+    // already has an active stylus gesture.
+
+    // Stylus movements continue. They should be delivered to the left window and to the spy window
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
                                       .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
 
+    // Further MOVE events keep going to the right window only
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(310).y(110))
                                       .build());
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
 
     spyWindow->assertNoEvents();
     leftWindow->assertNoEvents();
@@ -2779,8 +2930,10 @@
  * both.
  * Check hover in left window and touch down in the right window.
  * At first, spy should receive hover, but the touch down should cancel hovering inside spy.
+ * At the same time, left and right should be getting independent streams of hovering and touch,
+ * respectively.
  */
-TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverAndTouchWithSpy) {
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlockedByTouchWithSpy) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> spyWindow =
@@ -2813,13 +2966,13 @@
     spyWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
 
-    // Touch down on the right window.
+    // Touch down on the right window. Spy doesn't receive this touch because it already has
+    // stylus hovering there.
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .build());
-    leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
+    leftWindow->assertNoEvents();
     spyWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
     spyWindow->consumeMotionEvent(
@@ -2827,11 +2980,13 @@
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
 
-    // Stylus movements continue, but are ignored because the touch is down.
+    // Stylus movements continue. They should be delivered to the left window only.
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
                                       .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
 
     // Touch movements continue. They should be delivered to the right window and to the spy
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
@@ -3054,7 +3209,7 @@
  * While the touch is down, new hover events from the stylus device should be ignored. After the
  * touch is gone, stylus hovering should start working again.
  */
-TEST_F(InputDispatcherMultiDeviceTest, StylusHoverAndTouchTap) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDroppedWhenTouchTap) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -3080,20 +3235,20 @@
                                         .deviceId(touchDeviceId)
                                         .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
+    // The touch device should cause hover to stop!
     window->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
     window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
 
-    // Continue hovering with stylus. Injection will fail because touch is already down.
-    ASSERT_EQ(InputEventInjectionResult::FAILED,
+    // Continue hovering with stylus.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_STYLUS)
                                         .deviceId(stylusDeviceId)
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60))
                                         .build()));
-    // No event should be sent. This event should be ignored because a pointer from another device
-    // is already down.
+    // Hovers are now ignored
 
     // Lift up the finger
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -3105,7 +3260,6 @@
                                         .build()));
     window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(touchDeviceId)));
 
-    // Now that the touch is gone, stylus hovering should start working again
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
@@ -7822,10 +7976,23 @@
 // should be ANR'd first.
 TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               FOCUSED_WINDOW_LOCATION))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
+                                                         .x(FOCUSED_WINDOW_LOCATION.x)
+                                                         .y(FOCUSED_WINDOW_LOCATION.y))
+                                        .build()));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
+                                                         .x(FOCUSED_WINDOW_LOCATION.x)
+                                                         .y(FOCUSED_WINDOW_LOCATION.y))
+                                        .build()));
     mFocusedWindow->consumeMotionDown();
+    mFocusedWindow->consumeMotionUp();
     mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
                                    ADISPLAY_ID_DEFAULT, /*flags=*/0);
     // We consumed all events, so no ANR
@@ -7833,17 +8000,20 @@
     mFakePolicy->assertNotifyAnrWasNotCalled();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               FOCUSED_WINDOW_LOCATION));
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                   AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER)
+                                                         .x(FOCUSED_WINDOW_LOCATION.x)
+                                                         .y(FOCUSED_WINDOW_LOCATION.y))
+                                        .build()));
     std::optional<uint32_t> unfocusedSequenceNum = mUnfocusedWindow->receiveEvent();
     ASSERT_TRUE(unfocusedSequenceNum);
 
     const std::chrono::duration timeout =
             mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow);
-    // Because we injected two DOWN events in a row, CANCEL is enqueued for the first event
-    // sequence to make it consistent
-    mFocusedWindow->consumeMotionCancel();
+
     mUnfocusedWindow->finishEvent(*unfocusedSequenceNum);
     mFocusedWindow->consumeMotionDown();
     // This cancel is generated because the connection was unresponsive
@@ -10356,13 +10526,12 @@
                     .build());
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
-    leftWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
-    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
-    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    spy->assertNoEvents();
 
     // Act: pilfer from spy. Spy is currently receiving touch events.
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
 
@@ -10376,7 +10545,7 @@
                     .deviceId(touchDeviceId)
                     .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(52))
                     .build());
-    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
 
     spy->assertNoEvents();
     leftWindow->assertNoEvents();
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index ce845d9..0e49b75 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -209,7 +209,7 @@
                  (barrierFrameNumber > bufferData->frameNumber))) {
                 ALOGE("Out of order buffers detected for %s producedId=%d frameNumber=%" PRIu64
                       " -> producedId=%d frameNumber=%" PRIu64,
-                      getDebugString().c_str(), bufferData->producerId, bufferData->frameNumber,
+                      getDebugString().c_str(), barrierProducerId, barrierFrameNumber,
                       bufferData->producerId, frameNumber);
                 TransactionTraceWriter::getInstance().invoke("out_of_order_buffers_",
                                                              /*overwrite=*/false);
@@ -401,6 +401,7 @@
     if (!handleAlive) debug << " !handle";
     if (z != 0) debug << " z=" << z;
     if (layerStack.id != 0) debug << " layerStack=" << layerStack.id;
+    debug << "}";
     return debug.str();
 }
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 73b1ca8..b742394 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -2932,11 +2932,6 @@
         }
     }
 
-    // Prevent tracing the same release multiple times.
-    if (mPreviousFrameNumber != mPreviousReleasedFrameNumber) {
-        mPreviousReleasedFrameNumber = mPreviousFrameNumber;
-    }
-
     if (ch != nullptr) {
         ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
         ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index f67da2a..26e4666 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -1235,9 +1235,6 @@
 
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
     sp<IBinder> mPreviousReleaseBufferEndpoint;
-    uint64_t mPreviousReleasedFrameNumber = 0;
-
-    uint64_t mPreviousBarrierFrameNumber = 0;
 
     bool mReleasePreviousBuffer = false;
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index e6d6866..bc6d375 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1232,7 +1232,7 @@
 }
 
 status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<display::DisplayToken>& displayToken,
-                                                   DisplayModeId modeId) {
+                                                   DisplayModeId modeId, Fps minFps, Fps maxFps) {
     ATRACE_CALL();
 
     if (!displayToken) {
@@ -1265,13 +1265,15 @@
         }
 
         const Fps fps = *fpsOpt;
+        const FpsRange physical = {fps, fps};
+        const FpsRange render = {minFps.isValid() ? minFps : fps, maxFps.isValid() ? maxFps : fps};
+        const FpsRanges ranges = {physical, render};
 
         // Keep the old switching type.
         const bool allowGroupSwitching =
                 display->refreshRateSelector().getCurrentPolicy().allowGroupSwitching;
 
-        const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId,
-                                                                          {fps, fps},
+        const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId, ranges, ranges,
                                                                           allowGroupSwitching};
 
         return setDesiredDisplayModeSpecsInternal(display, policy);
@@ -6932,6 +6934,12 @@
                 return NO_ERROR;
             }
             case 1035: {
+                // Parameters:
+                // - (required) i32 mode id.
+                // - (optional) i64 display id. Using default display if not provided.
+                // - (optional) f min render rate. Using mode's fps is not provided.
+                // - (optional) f max render rate. Using mode's fps is not provided.
+
                 const int modeId = data.readInt32();
 
                 const auto display = [&]() -> sp<IBinder> {
@@ -6948,8 +6956,21 @@
                     return nullptr;
                 }();
 
+                const auto getFps = [&] {
+                    float value;
+                    if (data.readFloat(&value) == NO_ERROR) {
+                        return Fps::fromValue(value);
+                    }
+
+                    return Fps();
+                };
+
+                const auto minFps = getFps();
+                const auto maxFps = getFps();
+
                 mDebugDisplayModeSetByBackdoor = false;
-                const status_t result = setActiveModeFromBackdoor(display, DisplayModeId{modeId});
+                const status_t result =
+                        setActiveModeFromBackdoor(display, DisplayModeId{modeId}, minFps, maxFps);
                 mDebugDisplayModeSetByBackdoor = result == NO_ERROR;
                 return result;
             }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 7500b96..96b67b8 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -684,7 +684,8 @@
     void setDesiredActiveMode(display::DisplayModeRequest&&, bool force = false)
             REQUIRES(mStateLock);
 
-    status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId);
+    status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps,
+                                       Fps maxFps);
 
     void initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext);
     void finalizeDisplayModeChange(DisplayDevice&) REQUIRES(mStateLock, kMainThreadContext);