[automerger skipped] Merge tm-qpr-dev-plus-aosp-without-vendor@9467136 am: 45c68f28ac -s ours

am skip reason: Merged-In Ic7e102201196285cba18e12e27c7cd4d40dcc8e4 with SHA-1 95f9197944 is already in history

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

Change-Id: I94f5a454792477c37d099fa356f800d509a376f1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.clang-format b/.clang-format
index 6725a1f..f63f670 100644
--- a/.clang-format
+++ b/.clang-format
@@ -12,3 +12,6 @@
 PenaltyBreakBeforeFirstCallParameter: 100000
 SpacesBeforeTrailingComments: 1
 IncludeBlocks: Preserve
+
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/libs/binder/BinderRecordReplay.cpp b/libs/binder/BinderRecordReplay.cpp
index 8ba18a8..58bb106 100644
--- a/libs/binder/BinderRecordReplay.cpp
+++ b/libs/binder/BinderRecordReplay.cpp
@@ -18,6 +18,7 @@
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <binder/BinderRecordReplay.h>
+#include <sys/mman.h>
 #include <algorithm>
 
 using android::Parcel;
@@ -42,34 +43,38 @@
 //
 // A RecordedTransaction is written to a file as a sequence of Chunks.
 //
-// A Chunk consists of a ChunkDescriptor, Data, and Padding.
+// A Chunk consists of a ChunkDescriptor, Data, Padding, and a Checksum.
 //
-// Data and Padding may each be zero-length as specified by the
-// ChunkDescriptor.
+// The ChunkDescriptor identifies the type of Data in the chunk, and the size
+// of the Data.
 //
-// The ChunkDescriptor identifies the type of data in the chunk, the size of
-// the data in bytes, and the number of zero-bytes padding to land on an
-// 8-byte boundary by the end of the Chunk.
+// The Data may be any uint32 number of bytes in length in [0-0xfffffff0].
+//
+// Padding is between [0-7] zero-bytes after the Data such that the Chunk ends
+// on an 8-byte boundary. The ChunkDescriptor's dataSize does not include the
+// size of Padding.
+//
+// The checksum is a 64-bit wide XOR of all previous data from the start of the
+// ChunkDescriptor to the end of Padding.
 //
 // ┌───────────────────────────┐
 // │Chunk                      │
+// │┌────────────────────────┐ │
+// ││ChunkDescriptor         │ │
+// ││┌───────────┬──────────┐│ │
+// │││chunkType  │dataSize  ├┼─┼─┐
+// │││uint32_t   │uint32_t  ││ │ │
+// ││└───────────┴──────────┘│ │ │
+// │└────────────────────────┘ │ │
+// │┌─────────────────────────┐│ │
+// ││Data                     ││ │
+// ││bytes * dataSize         │◀─┘
+// ││   ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤│
+// ││           Padding       ││
+// │└───┴─────────────────────┘│
 // │┌─────────────────────────┐│
-// ││ChunkDescriptor          ││
-// ││┌───────────┬───────────┐││
-// │││chunkType  │paddingSize│││
-// │││uint32_t   │uint32_t   ├┼┼───┐
-// ││├───────────┴───────────┤││   │
-// │││dataSize               │││   │
-// │││uint64_t               ├┼┼─┐ │
-// ││└───────────────────────┘││ │ │
-// │└─────────────────────────┘│ │ │
-// │┌─────────────────────────┐│ │ │
-// ││Data                     ││ │ │
-// ││bytes * dataSize         │◀─┘ │
-// │└─────────────────────────┘│   │
-// │┌─────────────────────────┐│   │
-// ││Padding                  ││   │
-// ││bytes * paddingSize      │◀───┘
+// ││checksum                 ││
+// ││uint64_t                 ││
 // │└─────────────────────────┘│
 // └───────────────────────────┘
 //
@@ -85,20 +90,20 @@
 // ║      End Chunk       ║
 // ╚══════════════════════╝
 //
-// On reading a RecordedTransaction, an unrecognized chunk is skipped using
-// the size information in the ChunkDescriptor. Chunks are read and either
-// assimilated or skipped until an End Chunk is encountered. This has three
-// notable implications:
+// On reading a RecordedTransaction, an unrecognized chunk is checksummed
+// then skipped according to size information in the ChunkDescriptor. Chunks
+// are read and either assimilated or skipped until an End Chunk is
+// encountered. This has three notable implications:
 //
 // 1. Older and newer implementations should be able to read one another's
 //    Transactions, though there will be loss of information.
-// 2. With the exception of the End Chunk, Chunks can appear in any
-//    order and even repeat, though this is not recommended.
+// 2. With the exception of the End Chunk, Chunks can appear in any order
+//    and even repeat, though this is not recommended.
 // 3. If any Chunk is repeated, old values will be overwritten by versions
 //    encountered later in the file.
 //
 // No effort is made to ensure the expected chunks are present. A single
-// End Chunk may therefore produce a empty, meaningless RecordedTransaction.
+// End Chunk may therefore produce an empty, meaningless RecordedTransaction.
 
 RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept {
     mHeader = t.mHeader;
@@ -121,12 +126,12 @@
                  0};
 
     if (t.mSent.setData(dataParcel.data(), dataParcel.dataSize()) != android::NO_ERROR) {
-        LOG(INFO) << "Failed to set sent parcel data.";
+        LOG(ERROR) << "Failed to set sent parcel data.";
         return std::nullopt;
     }
 
     if (t.mReply.setData(replyParcel.data(), replyParcel.dataSize()) != android::NO_ERROR) {
-        LOG(INFO) << "Failed to set reply parcel data.";
+        LOG(ERROR) << "Failed to set reply parcel data.";
         return std::nullopt;
     }
 
@@ -134,94 +139,111 @@
 }
 
 enum {
-    HEADER_CHUNK = 0x00000001,
-    DATA_PARCEL_CHUNK = 0x00000002,
-    REPLY_PARCEL_CHUNK = 0x00000003,
-    INVALID_CHUNK = 0x00fffffe,
+    HEADER_CHUNK = 1,
+    DATA_PARCEL_CHUNK = 2,
+    REPLY_PARCEL_CHUNK = 3,
     END_CHUNK = 0x00ffffff,
 };
 
 struct ChunkDescriptor {
     uint32_t chunkType = 0;
-    uint32_t padding = 0;
     uint32_t dataSize = 0;
-    uint32_t reserved = 0; // Future checksum
 };
+static_assert(sizeof(ChunkDescriptor) % 8 == 0);
 
-static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut) {
+constexpr uint32_t kMaxChunkDataSize = 0xfffffff0;
+typedef uint64_t transaction_checksum_t;
+
+static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut,
+                                             transaction_checksum_t* sum) {
     if (!android::base::ReadFully(fd, chunkOut, sizeof(ChunkDescriptor))) {
-        LOG(INFO) << "Failed to read Chunk Descriptor from fd " << fd.get();
+        LOG(ERROR) << "Failed to read Chunk Descriptor from fd " << fd.get();
         return android::UNKNOWN_ERROR;
     }
-    if (PADDING8(chunkOut->dataSize) != chunkOut->padding) {
-        chunkOut->chunkType = INVALID_CHUNK;
-        LOG(INFO) << "Chunk data and padding sizes do not align." << fd.get();
-        return android::BAD_VALUE;
-    }
+
+    *sum ^= *reinterpret_cast<transaction_checksum_t*>(chunkOut);
     return android::NO_ERROR;
 }
 
 std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd& fd) {
     RecordedTransaction t;
     ChunkDescriptor chunk;
-
+    const long pageSize = sysconf(_SC_PAGE_SIZE);
     do {
-        if (NO_ERROR != readChunkDescriptor(fd, &chunk)) {
-            LOG(INFO) << "Failed to read chunk descriptor.";
+        transaction_checksum_t checksum = 0;
+        if (NO_ERROR != readChunkDescriptor(fd, &chunk, &checksum)) {
+            LOG(ERROR) << "Failed to read chunk descriptor.";
             return std::nullopt;
         }
+        off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
+        off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize;
+        off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart;
+
+        if (chunk.dataSize > kMaxChunkDataSize) {
+            LOG(ERROR) << "Chunk data exceeds maximum size.";
+            return std::nullopt;
+        }
+
+        size_t chunkPayloadSize =
+                chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t);
+
+        if (PADDING8(chunkPayloadSize) != 0) {
+            LOG(ERROR) << "Invalid chunk size, not aligned " << chunkPayloadSize;
+            return std::nullopt;
+        }
+
+        transaction_checksum_t* payloadMap = reinterpret_cast<transaction_checksum_t*>(
+                mmap(NULL, chunkPayloadSize + mmapPayloadStartOffset, PROT_READ, MAP_SHARED,
+                     fd.get(), mmapPageAlignedStart));
+        payloadMap += mmapPayloadStartOffset /
+                sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap
+                                                // page-alignment
+        if (payloadMap == MAP_FAILED) {
+            LOG(ERROR) << "Memory mapping failed for fd " << fd.get() << ": " << errno << " "
+                       << strerror(errno);
+            return std::nullopt;
+        }
+        for (size_t checksumIndex = 0;
+             checksumIndex < chunkPayloadSize / sizeof(transaction_checksum_t); checksumIndex++) {
+            checksum ^= payloadMap[checksumIndex];
+        }
+        if (checksum != 0) {
+            LOG(ERROR) << "Checksum failed.";
+            return std::nullopt;
+        }
+        lseek(fd.get(), chunkPayloadSize, SEEK_CUR);
+
         switch (chunk.chunkType) {
             case HEADER_CHUNK: {
                 if (chunk.dataSize != static_cast<uint32_t>(sizeof(TransactionHeader))) {
-                    LOG(INFO) << "Header Chunk indicated size " << chunk.dataSize << "; Expected "
-                              << sizeof(TransactionHeader) << ".";
+                    LOG(ERROR) << "Header Chunk indicated size " << chunk.dataSize << "; Expected "
+                               << sizeof(TransactionHeader) << ".";
                     return std::nullopt;
                 }
-                if (!android::base::ReadFully(fd, &t.mHeader, chunk.dataSize)) {
-                    LOG(INFO) << "Failed to read transactionHeader from fd " << fd.get();
-                    return std::nullopt;
-                }
-                lseek(fd.get(), chunk.padding, SEEK_CUR);
+                t.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap);
                 break;
             }
             case DATA_PARCEL_CHUNK: {
-                std::vector<uint8_t> bytes;
-                bytes.resize(chunk.dataSize);
-                if (!android::base::ReadFully(fd, bytes.data(), chunk.dataSize)) {
-                    LOG(INFO) << "Failed to read sent parcel data from fd " << fd.get();
+                if (t.mSent.setData(reinterpret_cast<const unsigned char*>(payloadMap),
+                                    chunk.dataSize) != android::NO_ERROR) {
+                    LOG(ERROR) << "Failed to set sent parcel data.";
                     return std::nullopt;
                 }
-                if (t.mSent.setData(bytes.data(), chunk.dataSize) != android::NO_ERROR) {
-                    LOG(INFO) << "Failed to set sent parcel data.";
-                    return std::nullopt;
-                }
-                lseek(fd.get(), chunk.padding, SEEK_CUR);
                 break;
             }
             case REPLY_PARCEL_CHUNK: {
-                std::vector<uint8_t> bytes;
-                bytes.resize(chunk.dataSize);
-                if (!android::base::ReadFully(fd, bytes.data(), chunk.dataSize)) {
-                    LOG(INFO) << "Failed to read reply parcel data from fd " << fd.get();
+                if (t.mReply.setData(reinterpret_cast<const unsigned char*>(payloadMap),
+                                     chunk.dataSize) != android::NO_ERROR) {
+                    LOG(ERROR) << "Failed to set reply parcel data.";
                     return std::nullopt;
                 }
-                if (t.mReply.setData(bytes.data(), chunk.dataSize) != android::NO_ERROR) {
-                    LOG(INFO) << "Failed to set reply parcel data.";
-                    return std::nullopt;
-                }
-                lseek(fd.get(), chunk.padding, SEEK_CUR);
                 break;
             }
-            case INVALID_CHUNK:
-                LOG(INFO) << "Invalid chunk.";
-                return std::nullopt;
             case END_CHUNK:
-                LOG(INFO) << "Read end chunk";
-                FALLTHROUGH_INTENDED;
-            default:
-                // Unrecognized or skippable chunk
-                lseek(fd.get(), chunk.dataSize + chunk.padding, SEEK_CUR);
                 break;
+            default:
+                LOG(INFO) << "Unrecognized chunk.";
+                continue;
         }
     } while (chunk.chunkType != END_CHUNK);
 
@@ -230,36 +252,37 @@
 
 android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunkType,
                                                   size_t byteCount, const uint8_t* data) const {
-    // Write Chunk Descriptor
-    // - Chunk Type
-    if (!android::base::WriteFully(fd, &chunkType, sizeof(uint32_t))) {
-        LOG(INFO) << "Failed to write chunk header to fd " << fd.get();
-        return UNKNOWN_ERROR;
+    if (byteCount > kMaxChunkDataSize) {
+        LOG(ERROR) << "Chunk data exceeds maximum size";
+        return BAD_VALUE;
     }
-    // - Chunk Data Padding Size
-    uint32_t additionalPaddingCount = static_cast<uint32_t>(PADDING8(byteCount));
-    if (!android::base::WriteFully(fd, &additionalPaddingCount, sizeof(uint32_t))) {
-        LOG(INFO) << "Failed to write chunk padding size to fd " << fd.get();
-        return UNKNOWN_ERROR;
-    }
-    // - Chunk Data Size
-    uint64_t byteCountToWrite = (uint64_t)byteCount;
-    if (!android::base::WriteFully(fd, &byteCountToWrite, sizeof(uint64_t))) {
-        LOG(INFO) << "Failed to write chunk size to fd " << fd.get();
-        return UNKNOWN_ERROR;
-    }
-    if (byteCount == 0) {
-        return NO_ERROR;
+    ChunkDescriptor descriptor = {.chunkType = chunkType,
+                                  .dataSize = static_cast<uint32_t>(byteCount)};
+    // Prepare Chunk content as byte *
+    const std::byte* descriptorBytes = reinterpret_cast<const std::byte*>(&descriptor);
+    const std::byte* dataBytes = reinterpret_cast<const std::byte*>(data);
+
+    // Add Chunk to intermediate buffer, except checksum
+    std::vector<std::byte> buffer;
+    buffer.insert(buffer.end(), descriptorBytes, descriptorBytes + sizeof(ChunkDescriptor));
+    buffer.insert(buffer.end(), dataBytes, dataBytes + byteCount);
+    std::byte zero{0};
+    buffer.insert(buffer.end(), PADDING8(byteCount), zero);
+
+    // Calculate checksum from buffer
+    transaction_checksum_t* checksumData = reinterpret_cast<transaction_checksum_t*>(buffer.data());
+    transaction_checksum_t checksumValue = 0;
+    for (size_t idx = 0; idx < (buffer.size() / sizeof(transaction_checksum_t)); idx++) {
+        checksumValue ^= checksumData[idx];
     }
 
-    if (!android::base::WriteFully(fd, data, byteCount)) {
-        LOG(INFO) << "Failed to write chunk data to fd " << fd.get();
-        return UNKNOWN_ERROR;
-    }
+    // Write checksum to buffer
+    std::byte* checksumBytes = reinterpret_cast<std::byte*>(&checksumValue);
+    buffer.insert(buffer.end(), checksumBytes, checksumBytes + sizeof(transaction_checksum_t));
 
-    const uint8_t zeros[7] = {0};
-    if (!android::base::WriteFully(fd, zeros, additionalPaddingCount)) {
-        LOG(INFO) << "Failed to write chunk padding to fd " << fd.get();
+    // Write buffer to file
+    if (!android::base::WriteFully(fd, buffer.data(), buffer.size())) {
+        LOG(ERROR) << "Failed to write chunk fd " << fd.get();
         return UNKNOWN_ERROR;
     }
     return NO_ERROR;
@@ -269,19 +292,19 @@
     if (NO_ERROR !=
         writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader),
                    reinterpret_cast<const uint8_t*>(&mHeader))) {
-        LOG(INFO) << "Failed to write transactionHeader to fd " << fd.get();
+        LOG(ERROR) << "Failed to write transactionHeader to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
     if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataSize(), mSent.data())) {
-        LOG(INFO) << "Failed to write sent Parcel to fd " << fd.get();
+        LOG(ERROR) << "Failed to write sent Parcel to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
     if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataSize(), mReply.data())) {
-        LOG(INFO) << "Failed to write reply Parcel to fd " << fd.get();
+        LOG(ERROR) << "Failed to write reply Parcel to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
     if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, NULL)) {
-        LOG(INFO) << "Failed to write end chunk to fd " << fd.get();
+        LOG(ERROR) << "Failed to write end chunk to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
     return NO_ERROR;
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 6d64e1e..da58251 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -1221,6 +1221,10 @@
         return NO_ERROR;
     }
 
+    ALOGE_IF(mProcess->mDriverFD >= 0,
+             "Driver returned error (%s). This is a bug in either libbinder or the driver. This "
+             "thread's connection to %s will no longer work.",
+             statusToString(err).c_str(), mProcess->mDriverName.c_str());
     return err;
 }
 
diff --git a/libs/binder/include/binder/ParcelFileDescriptor.h b/libs/binder/include/binder/ParcelFileDescriptor.h
index 9896fd7..08d8e43 100644
--- a/libs/binder/include/binder/ParcelFileDescriptor.h
+++ b/libs/binder/include/binder/ParcelFileDescriptor.h
@@ -42,6 +42,7 @@
     android::status_t writeToParcel(android::Parcel* parcel) const override;
     android::status_t readFromParcel(const android::Parcel* parcel) override;
 
+    inline std::string toString() const { return "ParcelFileDescriptor:" + std::to_string(get()); }
     inline bool operator!=(const ParcelFileDescriptor& rhs) const {
         return mFd.get() != rhs.mFd.get();
     }
diff --git a/libs/binder/include/binder/ParcelableHolder.h b/libs/binder/include/binder/ParcelableHolder.h
index 88790a8..40fd30a 100644
--- a/libs/binder/include/binder/ParcelableHolder.h
+++ b/libs/binder/include/binder/ParcelableHolder.h
@@ -111,6 +111,11 @@
 
     Stability getStability() const override { return mStability; }
 
+    inline std::string toString() const {
+        return "ParcelableHolder:" +
+                (mParcelableName ? std::string(String8(mParcelableName.value()).c_str())
+                                 : "<parceled>");
+    }
     inline bool operator!=(const ParcelableHolder& rhs) const {
         return this != &rhs;
     }
diff --git a/libs/binder/ndk/include_cpp/android/binder_to_string.h b/libs/binder/ndk/include_cpp/android/binder_to_string.h
index e3ead11..9b0d222 100644
--- a/libs/binder/ndk/include_cpp/android/binder_to_string.h
+++ b/libs/binder/ndk/include_cpp/android/binder_to_string.h
@@ -58,8 +58,6 @@
 #if __has_include(<binder/RpcSession.h>)
 #include <binder/IBinder.h>
 #include <binder/IInterface.h>
-#include <binder/ParcelFileDescriptor.h>
-#include <binder/ParcelableHolder.h>
 #define HAS_CPP_INTERFACE
 #endif
 
@@ -152,9 +150,7 @@
 #endif  // HAS_NDK_INTERFACE
 #ifdef HAS_CPP_INTERFACE
                                     || std::is_base_of_v<IInterface, _U> ||
-                                    std::is_same_v<IBinder, _U> ||
-                                    std::is_same_v<os::ParcelFileDescriptor, _U> ||
-                                    std::is_same_v<os::ParcelableHolder, _U>
+                                    std::is_same_v<IBinder, _U>
 #endif
                             ,
                             std::true_type>
diff --git a/libs/binder/tests/binderRecordedTransactionTest.cpp b/libs/binder/tests/binderRecordedTransactionTest.cpp
index 2ece315..67553fc 100644
--- a/libs/binder/tests/binderRecordedTransactionTest.cpp
+++ b/libs/binder/tests/binderRecordedTransactionTest.cpp
@@ -16,6 +16,7 @@
 
 #include <binder/BinderRecordReplay.h>
 #include <gtest/gtest.h>
+#include <utils/Errors.h>
 
 using android::Parcel;
 using android::status_t;
@@ -54,3 +55,80 @@
     EXPECT_EQ(retrievedTransaction->getDataParcel().readInt64(), 2);
     EXPECT_EQ(retrievedTransaction->getReplyParcel().readInt32(), 99);
 }
+
+TEST(BinderRecordedTransaction, Checksum) {
+    Parcel d;
+    d.writeInt32(12);
+    d.writeInt64(2);
+    Parcel r;
+    r.writeInt32(99);
+    timespec ts = {1232456, 567890};
+    auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+
+    auto file = std::tmpfile();
+    auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
+
+    status_t status = transaction->dumpToFile(fd);
+    ASSERT_EQ(android::NO_ERROR, status);
+
+    lseek(fd.get(), 9, SEEK_SET);
+    uint32_t badData = 0xffffffff;
+    write(fd.get(), &badData, sizeof(uint32_t));
+    std::rewind(file);
+
+    auto retrievedTransaction = RecordedTransaction::fromFile(fd);
+
+    EXPECT_FALSE(retrievedTransaction.has_value());
+}
+
+TEST(BinderRecordedTransaction, PayloadsExceedPageBoundaries) {
+    // File contents are read with mmap.
+    // This test verifies that transactions are read from portions
+    // of files that cross page boundaries and don't start at a
+    // page boundary offset of the fd.
+    const size_t pageSize = sysconf(_SC_PAGE_SIZE);
+    const size_t largeDataSize = pageSize + 100;
+    std::vector<uint8_t> largePayload;
+    uint8_t filler = 0xaa;
+    largePayload.insert(largePayload.end(), largeDataSize, filler);
+    Parcel d;
+    d.writeInt32(12);
+    d.writeInt64(2);
+    d.writeByteVector(largePayload);
+    Parcel r;
+    r.writeInt32(99);
+    timespec ts = {1232456, 567890};
+    auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+
+    auto file = std::tmpfile();
+    auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
+
+    // Write to file twice
+    status_t status = transaction->dumpToFile(fd);
+    ASSERT_EQ(android::NO_ERROR, status);
+    status = transaction->dumpToFile(fd);
+    ASSERT_EQ(android::NO_ERROR, status);
+
+    std::rewind(file);
+
+    for (int i = 0; i < 2; i++) {
+        auto retrievedTransaction = RecordedTransaction::fromFile(fd);
+
+        EXPECT_EQ(retrievedTransaction->getCode(), 1);
+        EXPECT_EQ(retrievedTransaction->getFlags(), 42);
+        EXPECT_EQ(retrievedTransaction->getTimestamp().tv_sec, ts.tv_sec);
+        EXPECT_EQ(retrievedTransaction->getTimestamp().tv_nsec, ts.tv_nsec);
+        EXPECT_EQ(retrievedTransaction->getDataParcel().dataSize(), d.dataSize());
+        EXPECT_EQ(retrievedTransaction->getReplyParcel().dataSize(), 4);
+        EXPECT_EQ(retrievedTransaction->getReturnedStatus(), 0);
+        EXPECT_EQ(retrievedTransaction->getVersion(), 0);
+
+        EXPECT_EQ(retrievedTransaction->getDataParcel().readInt32(), 12);
+        EXPECT_EQ(retrievedTransaction->getDataParcel().readInt64(), 2);
+        std::optional<std::vector<uint8_t>> payloadOut;
+        EXPECT_EQ(retrievedTransaction->getDataParcel().readByteVector(&payloadOut), android::OK);
+        EXPECT_EQ(payloadOut.value(), largePayload);
+
+        EXPECT_EQ(retrievedTransaction->getReplyParcel().readInt32(), 99);
+    }
+}
diff --git a/libs/binder/tests/binderUtilsHostTest.cpp b/libs/binder/tests/binderUtilsHostTest.cpp
index 4330e3e..25e286c 100644
--- a/libs/binder/tests/binderUtilsHostTest.cpp
+++ b/libs/binder/tests/binderUtilsHostTest.cpp
@@ -37,17 +37,24 @@
     EXPECT_EQ(result->stdoutStr, "foo\n");
 }
 
+template <typename T>
+auto millisSince(std::chrono::time_point<T> now) {
+    auto elapsed = std::chrono::system_clock::now() - now;
+    return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+}
+
 TEST(UtilsHost, ExecuteLongRunning) {
-    auto now = std::chrono::system_clock::now();
+    auto start = std::chrono::system_clock::now();
 
     {
-        std::vector<std::string> args{"sh", "-c",
-                                      "sleep 0.5 && echo -n f && sleep 0.5 && echo oo && sleep 1"};
-        auto result = execute(std::move(args), [](const CommandResult& commandResult) {
+        std::vector<std::string>
+                args{"sh", "-c", "sleep 0.5 && echo -n f && sleep 0.5 && echo oo && sleep 100"};
+        auto result = execute(std::move(args), [&](const CommandResult& commandResult) {
+            std::cout << millisSince(start)
+                      << "ms: GOT PARTIAL COMMAND RESULT:" << commandResult.stdoutStr << std::endl;
             return android::base::EndsWith(commandResult.stdoutStr, "\n");
         });
-        auto elapsed = std::chrono::system_clock::now() - now;
-        auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+        auto elapsedMs = millisSince(start);
         EXPECT_GE(elapsedMs, 1000);
         EXPECT_LT(elapsedMs, 2000);
 
@@ -58,22 +65,21 @@
 
     // ~CommandResult() called, child process is killed.
     // Assert that the second sleep does not finish.
-    auto elapsed = std::chrono::system_clock::now() - now;
-    auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
-    EXPECT_LT(elapsedMs, 2000);
+    EXPECT_LT(millisSince(start), 2000);
 }
 
 TEST(UtilsHost, ExecuteLongRunning2) {
-    auto now = std::chrono::system_clock::now();
+    auto start = std::chrono::system_clock::now();
 
     {
         std::vector<std::string> args{"sh", "-c",
-                                      "sleep 2 && echo -n f && sleep 2 && echo oo && sleep 2"};
-        auto result = execute(std::move(args), [](const CommandResult& commandResult) {
+                                      "sleep 2 && echo -n f && sleep 2 && echo oo && sleep 100"};
+        auto result = execute(std::move(args), [&](const CommandResult& commandResult) {
+            std::cout << millisSince(start)
+                      << "ms: GOT PARTIAL COMMAND RESULT:" << commandResult.stdoutStr << std::endl;
             return android::base::EndsWith(commandResult.stdoutStr, "\n");
         });
-        auto elapsed = std::chrono::system_clock::now() - now;
-        auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+        auto elapsedMs = millisSince(start);
         EXPECT_GE(elapsedMs, 4000);
         EXPECT_LT(elapsedMs, 6000);
 
@@ -84,9 +90,7 @@
 
     // ~CommandResult() called, child process is killed.
     // Assert that the second sleep does not finish.
-    auto elapsed = std::chrono::system_clock::now() - now;
-    auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
-    EXPECT_LT(elapsedMs, 6000);
+    EXPECT_LT(millisSince(start), 6000);
 }
 
 TEST(UtilsHost, KillWithSigKill) {
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 24a5295..8ee9b14 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -480,20 +480,18 @@
     mSyncedFrameNumbers.erase(callbackId.framenumber);
 }
 
-void BLASTBufferQueue::acquireNextBufferLocked(
+status_t BLASTBufferQueue::acquireNextBufferLocked(
         const std::optional<SurfaceComposerClient::Transaction*> transaction) {
     // If the next transaction is set, we want to guarantee the our acquire will not fail, so don't
     // include the extra buffer when checking if we can acquire the next buffer.
-    const bool includeExtraAcquire = !transaction;
-    const bool maxAcquired = maxBuffersAcquired(includeExtraAcquire);
-    if (mNumFrameAvailable == 0 || maxAcquired) {
-        BQA_LOGV("Can't process next buffer maxBuffersAcquired=%s", boolToString(maxAcquired));
-        return;
+    if (mNumFrameAvailable == 0) {
+        BQA_LOGV("Can't process next buffer. No available frames");
+        return NOT_ENOUGH_DATA;
     }
 
     if (mSurfaceControl == nullptr) {
         BQA_LOGE("ERROR : surface control is null");
-        return;
+        return NAME_NOT_FOUND;
     }
 
     SurfaceComposerClient::Transaction localTransaction;
@@ -510,10 +508,10 @@
             mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false);
     if (status == BufferQueue::NO_BUFFER_AVAILABLE) {
         BQA_LOGV("Failed to acquire a buffer, err=NO_BUFFER_AVAILABLE");
-        return;
+        return status;
     } else if (status != OK) {
         BQA_LOGE("Failed to acquire a buffer, err=%s", statusToString(status).c_str());
-        return;
+        return status;
     }
 
     auto buffer = bufferItem.mGraphicBuffer;
@@ -523,7 +521,7 @@
     if (buffer == nullptr) {
         mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
         BQA_LOGE("Buffer was empty");
-        return;
+        return BAD_VALUE;
     }
 
     if (rejectBuffer(bufferItem)) {
@@ -532,8 +530,7 @@
                  mSize.width, mSize.height, mRequestedSize.width, mRequestedSize.height,
                  buffer->getWidth(), buffer->getHeight(), bufferItem.mTransform);
         mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
-        acquireNextBufferLocked(transaction);
-        return;
+        return acquireNextBufferLocked(transaction);
     }
 
     mNumAcquired++;
@@ -621,6 +618,7 @@
              bufferItem.mTimestamp, bufferItem.mIsAutoTimestamp ? "(auto)" : "",
              static_cast<uint32_t>(mPendingTransactions.size()), bufferItem.mGraphicBuffer->getId(),
              bufferItem.mAutoRefresh ? " mAutoRefresh" : "", bufferItem.mTransform);
+    return OK;
 }
 
 Rect BLASTBufferQueue::computeCrop(const BufferItem& item) {
@@ -643,31 +641,6 @@
     mBufferItemConsumer->releaseBuffer(bufferItem, bufferItem.mFence);
 }
 
-void BLASTBufferQueue::flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock) {
-    if (!mSyncedFrameNumbers.empty() && mNumFrameAvailable > 0) {
-        // We are waiting on a previous sync's transaction callback so allow another sync
-        // transaction to proceed.
-        //
-        // We need to first flush out the transactions that were in between the two syncs.
-        // We do this by merging them into mSyncTransaction so any buffer merging will get
-        // a release callback invoked. The release callback will be async so we need to wait
-        // on max acquired to make sure we have the capacity to acquire another buffer.
-        if (maxBuffersAcquired(false /* includeExtraAcquire */)) {
-            BQA_LOGD("waiting to flush shadow queue...");
-            mCallbackCV.wait(lock);
-        }
-        while (mNumFrameAvailable > 0) {
-            // flush out the shadow queue
-            acquireAndReleaseBuffer();
-        }
-    }
-
-    while (maxBuffersAcquired(false /* includeExtraAcquire */)) {
-        BQA_LOGD("waiting for free buffer.");
-        mCallbackCV.wait(lock);
-    }
-}
-
 void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
     std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
     SurfaceComposerClient::Transaction* prevTransaction = nullptr;
@@ -680,7 +653,6 @@
         BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet));
 
         if (syncTransactionSet) {
-            bool mayNeedToWaitForBuffer = true;
             // If we are going to re-use the same mSyncTransaction, release the buffer that may
             // already be set in the Transaction. This is to allow us a free slot early to continue
             // processing a new buffer.
@@ -691,14 +663,20 @@
                              bufferData->frameNumber);
                     releaseBuffer(bufferData->generateReleaseCallbackId(),
                                   bufferData->acquireFence);
-                    // Because we just released a buffer, we know there's no need to wait for a free
-                    // buffer.
-                    mayNeedToWaitForBuffer = false;
                 }
             }
 
-            if (mayNeedToWaitForBuffer) {
-                flushAndWaitForFreeBuffer(_lock);
+            if (waitForTransactionCallback) {
+                // We are waiting on a previous sync's transaction callback so allow another sync
+                // transaction to proceed.
+                //
+                // We need to first flush out the transactions that were in between the two syncs.
+                // We do this by merging them into mSyncTransaction so any buffer merging will get
+                // a release callback invoked.
+                while (mNumFrameAvailable > 0) {
+                    // flush out the shadow queue
+                    acquireAndReleaseBuffer();
+                }
             }
         }
 
@@ -714,7 +692,12 @@
                  item.mFrameNumber, boolToString(syncTransactionSet));
 
         if (syncTransactionSet) {
-            acquireNextBufferLocked(mSyncTransaction);
+            // If there's no available buffer and we're in a sync transaction, we need to wait
+            // instead of returning since we guarantee a buffer will be acquired for the sync.
+            while (acquireNextBufferLocked(mSyncTransaction) == BufferQueue::NO_BUFFER_AVAILABLE) {
+                BQA_LOGD("waiting for available buffer");
+                mCallbackCV.wait(_lock);
+            }
 
             // Only need a commit callback when syncing to ensure the buffer that's synced has been
             // sent to SF
@@ -824,15 +807,6 @@
     return mSize != bufferSize;
 }
 
-// Check if we have acquired the maximum number of buffers.
-// Consumer can acquire an additional buffer if that buffer is not droppable. Set
-// includeExtraAcquire is true to include this buffer to the count. Since this depends on the state
-// of the buffer, the next acquire may return with NO_BUFFER_AVAILABLE.
-bool BLASTBufferQueue::maxBuffersAcquired(bool includeExtraAcquire) const {
-    int maxAcquiredBuffers = mMaxAcquiredBuffers + (includeExtraAcquire ? 2 : 1);
-    return mNumAcquired >= maxAcquiredBuffers;
-}
-
 class BBQSurface : public Surface {
 private:
     std::mutex mMutex;
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 48226b9..f074bd3 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -136,12 +136,11 @@
     void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
                            sp<IGraphicBufferConsumer>* outConsumer);
 
-    void acquireNextBufferLocked(
+    status_t acquireNextBufferLocked(
             const std::optional<SurfaceComposerClient::Transaction*> transaction) REQUIRES(mMutex);
     Rect computeCrop(const BufferItem& item) REQUIRES(mMutex);
     // Return true if we need to reject the buffer based on the scaling mode and the buffer size.
     bool rejectBuffer(const BufferItem& item) REQUIRES(mMutex);
-    bool maxBuffersAcquired(bool includeExtraAcquire) const REQUIRES(mMutex);
     static PixelFormat convertBufferFormat(PixelFormat& format);
     void mergePendingTransactions(SurfaceComposerClient::Transaction* t, uint64_t frameNumber)
             REQUIRES(mMutex);
@@ -150,7 +149,6 @@
     void acquireAndReleaseBuffer() REQUIRES(mMutex);
     void releaseBuffer(const ReleaseCallbackId& callbackId, const sp<Fence>& releaseFence)
             REQUIRES(mMutex);
-    void flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock);
 
     std::string mName;
     // Represents the queued buffer count from buffer queue,
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index eb97a68..caf7101 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2196,8 +2196,31 @@
             // Update the temporary touch state.
             BitSet32 pointerIds;
             pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
-
             tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds);
+
+            // If this is the pointer going down and the touched window has a wallpaper
+            // then also add the touched wallpaper windows so they are locked in for the duration
+            // of the touch gesture.
+            // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
+            // engine only supports touch events.  We would need to add a mechanism similar
+            // to View.onGenericMotionEvent to enable wallpapers to handle these events.
+            if (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
+                maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+                if ((targetFlags & InputTarget::FLAG_FOREGROUND) &&
+                    windowHandle->getInfo()->inputConfig.test(
+                            gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
+                    sp<WindowInfoHandle> wallpaper = findWallpaperWindowBelow(windowHandle);
+                    if (wallpaper != nullptr) {
+                        int32_t wallpaperFlags = InputTarget::FLAG_WINDOW_IS_OBSCURED |
+                                InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED |
+                                InputTarget::FLAG_DISPATCH_AS_IS;
+                        if (isSplit) {
+                            wallpaperFlags |= InputTarget::FLAG_SPLIT;
+                        }
+                        tempTouchState.addOrUpdateWindow(wallpaper, wallpaperFlags, pointerIds);
+                    }
+                }
+            }
         }
     } else {
         /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
@@ -2274,6 +2297,10 @@
                 BitSet32 pointerIds;
                 pointerIds.markBit(entry.pointerProperties[0].id);
                 tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
+
+                // Check if the wallpaper window should deliver the corresponding event.
+                slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle,
+                                   tempTouchState, pointerIds);
             }
         }
 
@@ -2379,39 +2406,6 @@
         }
     }
 
-    // If this is the first pointer going down and the touched window has a wallpaper
-    // then also add the touched wallpaper windows so they are locked in for the duration
-    // of the touch gesture.
-    // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
-    // engine only supports touch events.  We would need to add a mechanism similar
-    // to View.onGenericMotionEvent to enable wallpapers to handle these events.
-    if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
-        sp<WindowInfoHandle> foregroundWindowHandle =
-                tempTouchState.getFirstForegroundWindowHandle();
-        if (foregroundWindowHandle &&
-            foregroundWindowHandle->getInfo()->inputConfig.test(
-                    WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
-            const std::vector<sp<WindowInfoHandle>>& windowHandles =
-                    getWindowHandlesLocked(displayId);
-            for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
-                const WindowInfo* info = windowHandle->getInfo();
-                if (info->displayId == displayId &&
-                    windowHandle->getInfo()->inputConfig.test(
-                            WindowInfo::InputConfig::IS_WALLPAPER)) {
-                    BitSet32 pointerIds;
-                    pointerIds.markBit(entry.pointerProperties[0].id);
-                    tempTouchState
-                            .addOrUpdateWindow(windowHandle,
-                                               InputTarget::FLAG_WINDOW_IS_OBSCURED |
-                                                       InputTarget::
-                                                               FLAG_WINDOW_IS_PARTIALLY_OBSCURED |
-                                                       InputTarget::FLAG_DISPATCH_AS_IS,
-                                               pointerIds);
-                }
-            }
-        }
-    }
-
     // Success!  Output targets.
     injectionResult = InputEventInjectionResult::SUCCEEDED;
 
@@ -3690,7 +3684,7 @@
 }
 
 void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
-        const sp<Connection>& connection) {
+        const sp<Connection>& connection, int32_t targetFlags) {
     if (connection->status == Connection::Status::BROKEN) {
         return;
     }
@@ -3718,7 +3712,7 @@
         target.globalScaleFactor = windowInfo->globalScaleFactor;
     }
     target.inputChannel = connection->inputChannel;
-    target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+    target.flags = targetFlags;
 
     const bool wasEmpty = connection->outboundQueue.empty();
 
@@ -3753,6 +3747,16 @@
     }
 }
 
+void InputDispatcher::synthesizeCancelationEventsForWindowLocked(
+        const sp<WindowInfoHandle>& windowHandle, const CancelationOptions& options) {
+    if (windowHandle != nullptr) {
+        sp<Connection> wallpaperConnection = getConnectionLocked(windowHandle->getToken());
+        if (wallpaperConnection != nullptr) {
+            synthesizeCancelationEventsForConnectionLocked(wallpaperConnection, options);
+        }
+    }
+}
+
 std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
         const MotionEntry& originalMotionEntry, BitSet32 pointerIds) {
     ALOG_ASSERT(pointerIds.value != 0);
@@ -4774,14 +4778,7 @@
                         touchedWindow.windowHandle->getInfo()->inputConfig.test(
                                 gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
                         sp<WindowInfoHandle> wallpaper = state.getWallpaperWindow();
-                        if (wallpaper != nullptr) {
-                            sp<Connection> wallpaperConnection =
-                                    getConnectionLocked(wallpaper->getToken());
-                            if (wallpaperConnection != nullptr) {
-                                synthesizeCancelationEventsForConnectionLocked(wallpaperConnection,
-                                                                               options);
-                            }
-                        }
+                        synthesizeCancelationEventsForWindowLocked(wallpaper, options);
                     }
                 }
                 state.windows.erase(state.windows.begin() + i);
@@ -5094,6 +5091,7 @@
         // Erase old window.
         int32_t oldTargetFlags = touchedWindow->targetFlags;
         BitSet32 pointerIds = touchedWindow->pointerIds;
+        sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
         state->removeWindowByToken(fromToken);
 
         // Add new window.
@@ -5125,7 +5123,10 @@
                     options(CancelationOptions::CANCEL_POINTER_EVENTS,
                             "transferring touch focus from this window to another window");
             synthesizeCancelationEventsForConnectionLocked(fromConnection, options);
-            synthesizePointerDownEventsForConnectionLocked(toConnection);
+            synthesizePointerDownEventsForConnectionLocked(toConnection, newTargetFlags);
+            // Check if the wallpaper window should deliver the corresponding event.
+            transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle,
+                                   *state, pointerIds);
         }
 
         if (DEBUG_FOCUS) {
@@ -6410,4 +6411,97 @@
     mMonitorDispatchingTimeout = timeout;
 }
 
+void InputDispatcher::slipWallpaperTouch(int32_t targetFlags,
+                                         const sp<WindowInfoHandle>& oldWindowHandle,
+                                         const sp<WindowInfoHandle>& newWindowHandle,
+                                         TouchState& state, const BitSet32& pointerIds) {
+    const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
+            gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+    const bool newHasWallpaper = (targetFlags & InputTarget::FLAG_FOREGROUND) &&
+            newWindowHandle->getInfo()->inputConfig.test(
+                    gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+    const sp<WindowInfoHandle> oldWallpaper =
+            oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+    const sp<WindowInfoHandle> newWallpaper =
+            newHasWallpaper ? findWallpaperWindowBelow(newWindowHandle) : nullptr;
+    if (oldWallpaper == newWallpaper) {
+        return;
+    }
+
+    if (oldWallpaper != nullptr) {
+        state.addOrUpdateWindow(oldWallpaper, InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT,
+                                BitSet32(0));
+    }
+
+    if (newWallpaper != nullptr) {
+        state.addOrUpdateWindow(newWallpaper,
+                                InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER |
+                                        InputTarget::FLAG_WINDOW_IS_OBSCURED |
+                                        InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+                                pointerIds);
+    }
+}
+
+void InputDispatcher::transferWallpaperTouch(int32_t oldTargetFlags, int32_t newTargetFlags,
+                                             const sp<WindowInfoHandle> fromWindowHandle,
+                                             const sp<WindowInfoHandle> toWindowHandle,
+                                             TouchState& state, const BitSet32& pointerIds) {
+    const bool oldHasWallpaper = (oldTargetFlags & InputTarget::FLAG_FOREGROUND) &&
+            fromWindowHandle->getInfo()->inputConfig.test(
+                    gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+    const bool newHasWallpaper = (newTargetFlags & InputTarget::FLAG_FOREGROUND) &&
+            toWindowHandle->getInfo()->inputConfig.test(
+                    gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+
+    const sp<WindowInfoHandle> oldWallpaper =
+            oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+    const sp<WindowInfoHandle> newWallpaper =
+            newHasWallpaper ? findWallpaperWindowBelow(toWindowHandle) : nullptr;
+    if (oldWallpaper == newWallpaper) {
+        return;
+    }
+
+    if (oldWallpaper != nullptr) {
+        CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+                                   "transferring touch focus to another window");
+        state.removeWindowByToken(oldWallpaper->getToken());
+        synthesizeCancelationEventsForWindowLocked(oldWallpaper, options);
+    }
+
+    if (newWallpaper != nullptr) {
+        int32_t wallpaperFlags =
+                oldTargetFlags & (InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS);
+        wallpaperFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED |
+                InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+        state.addOrUpdateWindow(newWallpaper, wallpaperFlags, pointerIds);
+        sp<Connection> wallpaperConnection = getConnectionLocked(newWallpaper->getToken());
+        if (wallpaperConnection != nullptr) {
+            sp<Connection> toConnection = getConnectionLocked(toWindowHandle->getToken());
+            toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState);
+            synthesizePointerDownEventsForConnectionLocked(wallpaperConnection, wallpaperFlags);
+        }
+    }
+}
+
+sp<WindowInfoHandle> InputDispatcher::findWallpaperWindowBelow(
+        const sp<WindowInfoHandle>& windowHandle) const {
+    const std::vector<sp<WindowInfoHandle>>& windowHandles =
+            getWindowHandlesLocked(windowHandle->getInfo()->displayId);
+    bool foundWindow = false;
+    for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
+        if (!foundWindow && otherHandle != windowHandle) {
+            continue;
+        }
+        if (windowHandle == otherHandle) {
+            foundWindow = true;
+            continue;
+        }
+
+        if (otherHandle->getInfo()->inputConfig.test(WindowInfo::InputConfig::IS_WALLPAPER)) {
+            return otherHandle;
+        }
+    }
+    return nullptr;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index ed89ed0..0df5894 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -621,8 +621,12 @@
                                                         const CancelationOptions& options)
             REQUIRES(mLock);
 
-    void synthesizePointerDownEventsForConnectionLocked(const sp<Connection>& connection)
-            REQUIRES(mLock);
+    void synthesizePointerDownEventsForConnectionLocked(const sp<Connection>& connection,
+                                                        int32_t targetFlags) REQUIRES(mLock);
+
+    void synthesizeCancelationEventsForWindowLocked(
+            const sp<android::gui::WindowInfoHandle>& windowHandle,
+            const CancelationOptions& options) REQUIRES(mLock);
 
     // Splitting motion events across windows.
     std::unique_ptr<MotionEntry> splitMotionEvent(const MotionEntry& originalMotionEntry,
@@ -684,6 +688,18 @@
     bool recentWindowsAreOwnedByLocked(int32_t pid, int32_t uid) REQUIRES(mLock);
 
     sp<InputReporterInterface> mReporter;
+
+    void slipWallpaperTouch(int32_t targetFlags,
+                            const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
+                            const sp<android::gui::WindowInfoHandle>& newWindowHandle,
+                            TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock);
+    void transferWallpaperTouch(int32_t oldTargetFlags, int32_t newTargetFlags,
+                                const sp<android::gui::WindowInfoHandle> fromWindowHandle,
+                                const sp<android::gui::WindowInfoHandle> toWindowHandle,
+                                TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock);
+
+    sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
+            const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index fce0f99..b2575fe 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -58,6 +58,8 @@
         AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 static constexpr int32_t POINTER_2_DOWN =
         AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_0_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 static constexpr int32_t POINTER_1_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
@@ -73,6 +75,9 @@
 
 static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms;
 
+static constexpr int expectedWallpaperFlags =
+        AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+
 struct PointF {
     float x;
     float y;
@@ -1670,8 +1675,6 @@
     sp<FakeWindowHandle> wallpaperWindow =
             new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
-    constexpr int expectedWallpaperFlags =
-            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1714,8 +1717,6 @@
     sp<FakeWindowHandle> wallpaperWindow =
             new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
-    constexpr int expectedWallpaperFlags =
-            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1745,24 +1746,27 @@
     foregroundWindow->consumeMotionCancel();
 }
 
+class ShouldSplitTouchFixture : public InputDispatcherTest,
+                                public ::testing::WithParamInterface<bool> {};
+INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture,
+                         ::testing::Values(true, false));
 /**
  * A single window that receives touch (on top), and a wallpaper window underneath it.
  * The top window gets a multitouch gesture.
  * Ensure that wallpaper gets the same gesture.
  */
-TEST_F(InputDispatcherTest, WallpaperWindow_ReceivesMultiTouch) {
+TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
-    window->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> foregroundWindow =
+            new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+    foregroundWindow->setDupTouchToWallpaper(true);
+    foregroundWindow->setPreventSplitting(GetParam());
 
     sp<FakeWindowHandle> wallpaperWindow =
             new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
-    constexpr int expectedWallpaperFlags =
-            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}});
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
 
     // Touch down on top window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1771,7 +1775,7 @@
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both top window and its wallpaper should receive the touch down
-    window->consumeMotionDown();
+    foregroundWindow->consumeMotionDown();
     wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     // Second finger down on the top window
@@ -1790,11 +1794,34 @@
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    window->consumeMotionPointerDown(1 /* pointerIndex */);
+    foregroundWindow->consumeMotionPointerDown(1 /* pointerIndex */);
     wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
                                               expectedWallpaperFlags);
-    window->assertNoEvents();
-    wallpaperWindow->assertNoEvents();
+
+    const MotionEvent secondFingerUpEvent =
+            MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(100)
+                                     .y(100))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(150)
+                                     .y(150))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    foregroundWindow->consumeMotionPointerUp(0);
+    wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                             {100, 100}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 }
 
 /**
@@ -1821,8 +1848,6 @@
             new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setFrame(Rect(0, 0, 400, 200));
     wallpaperWindow->setIsWallpaper(true);
-    constexpr int expectedWallpaperFlags =
-            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
     mDispatcher->setInputWindows(
             {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
@@ -1887,62 +1912,49 @@
     wallpaperWindow->assertNoEvents();
 }
 
-TEST_F(InputDispatcherTest, WallpaperWindowReceivesMultiTouch) {
+/**
+ * Two windows: a window on the left with dup touch to wallpaper and window on the right without it.
+ * The touch slips to the right window. so left window and wallpaper should receive ACTION_CANCEL
+ * The right window should receive ACTION_DOWN.
+ */
+TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
-    window->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> leftWindow =
+            new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+    leftWindow->setDupTouchToWallpaper(true);
+    leftWindow->setSlippery(true);
+
+    sp<FakeWindowHandle> rightWindow =
+            new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     sp<FakeWindowHandle> wallpaperWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
-    constexpr int expectedWallpaperFlags =
-            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
-    wallpaperWindow->setPreventSplitting(true);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}});
+    mDispatcher->setInputWindows(
+            {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
 
+    // Touch down on left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+                               {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    // Both foreground window and its wallpaper should receive the touch down
+    leftWindow->consumeMotionDown();
     wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
-    const MotionEvent secondFingerDownEvent =
-            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
-                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
-                    .build();
+    // Move to right window, the left window should receive cancel.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
-                                InputEventInjectionSync::WAIT_FOR_RESULT))
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {201, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    window->consumeMotionPointerDown(1);
-    wallpaperWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
-
-    const MotionEvent secondFingerUpEvent =
-            MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
-                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
-                    .build();
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
-                                InputEventInjectionSync::WAIT_FOR_RESULT))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionPointerUp(1);
-    wallpaperWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
-
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50}))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionUp(ADISPLAY_ID_DEFAULT);
-    wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    leftWindow->consumeMotionCancel();
+    rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 }
 
 /**
@@ -2620,20 +2632,26 @@
     // Create a couple of windows
     sp<FakeWindowHandle> firstWindow =
             new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+    firstWindow->setDupTouchToWallpaper(true);
+
     sp<FakeWindowHandle> secondWindow =
             new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
-
+    sp<FakeWindowHandle> wallpaper =
+            new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+    wallpaper->setIsWallpaper(true);
     // Add the windows to the dispatcher
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow, wallpaper}}});
 
     // Send down to the first window
     NotifyMotionArgs downMotionArgs =
             generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                                ADISPLAY_ID_DEFAULT);
     mDispatcher->notifyMotion(&downMotionArgs);
+
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
+    wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     // Transfer touch to the second window
     TransferFunction f = GetParam();
@@ -2642,6 +2660,7 @@
     // The first window gets cancel and the second gets down
     firstWindow->consumeMotionCancel();
     secondWindow->consumeMotionDown();
+    wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
 
     // Send up event to the second window
     NotifyMotionArgs upMotionArgs =
@@ -2651,6 +2670,7 @@
     // The first  window gets no events and the second gets up
     firstWindow->assertNoEvents();
     secondWindow->consumeMotionUp();
+    wallpaper->assertNoEvents();
 }
 
 /**
@@ -2772,6 +2792,65 @@
     secondWindow->consumeMotionUp();
 }
 
+TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    // Create a couple of windows
+    sp<FakeWindowHandle> firstWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+                                       ADISPLAY_ID_DEFAULT);
+    firstWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> secondWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+                                       ADISPLAY_ID_DEFAULT);
+    secondWindow->setDupTouchToWallpaper(true);
+
+    sp<FakeWindowHandle> wallpaper1 =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper1", ADISPLAY_ID_DEFAULT);
+    wallpaper1->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> wallpaper2 =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper2", ADISPLAY_ID_DEFAULT);
+    wallpaper2->setIsWallpaper(true);
+    // Add the windows to the dispatcher
+    mDispatcher->setInputWindows(
+            {{ADISPLAY_ID_DEFAULT, {firstWindow, wallpaper1, secondWindow, wallpaper2}}});
+
+    // Send down to the first window
+    NotifyMotionArgs downMotionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&downMotionArgs);
+
+    // Only the first window should get the down event
+    firstWindow->consumeMotionDown();
+    secondWindow->assertNoEvents();
+    wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper2->assertNoEvents();
+
+    // Transfer touch focus to the second window
+    TransferFunction f = GetParam();
+    bool success = f(mDispatcher, firstWindow->getToken(), secondWindow->getToken());
+    ASSERT_TRUE(success);
+
+    // The first window gets cancel and the second gets down
+    firstWindow->consumeMotionCancel();
+    secondWindow->consumeMotionDown();
+    wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    // Send up event to the second window
+    NotifyMotionArgs upMotionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&upMotionArgs);
+    // The first  window gets no events and the second gets up
+    firstWindow->assertNoEvents();
+    secondWindow->consumeMotionUp();
+    wallpaper1->assertNoEvents();
+    wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+}
+
 // For the cases of single pointer touch and two pointers non-split touch, the api's
 // 'transferTouch' and 'transferTouchFocus' are equivalent in behaviour. They only differ
 // for the case where there are multiple pointers split across several windows.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index d1275b0..c2f3bdd 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -423,6 +423,9 @@
     property_get("debug.sf.treat_170m_as_sRGB", value, "0");
     mTreat170mAsSrgb = atoi(value);
 
+    mIgnoreHwcPhysicalDisplayOrientation =
+            base::GetBoolProperty("debug.sf.ignore_hwc_physical_display_orientation"s, false);
+
     // We should be reading 'persist.sys.sf.color_saturation' here
     // but since /data may be encrypted, we need to wait until after vold
     // comes online to attempt to read the property. The property is
@@ -2410,7 +2413,8 @@
     if (!id) {
         return ui::ROTATION_0;
     }
-    if (getHwComposer().getComposer()->isSupported(
+    if (!mIgnoreHwcPhysicalDisplayOrientation &&
+        getHwComposer().getComposer()->isSupported(
                 Hwc2::Composer::OptionalFeature::PhysicalDisplayOrientation)) {
         switch (getHwComposer().getPhysicalDisplayOrientation(*id)) {
             case Hwc2::AidlTransform::ROT_90:
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index bb94908..87124d9 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -353,6 +353,11 @@
     // on this behavior to increase contrast for some media sources.
     bool mTreat170mAsSrgb = false;
 
+    // Allows to ignore physical orientation provided through hwc API in favour of
+    // 'ro.surface_flinger.primary_display_orientation'.
+    // TODO(b/246793311): Clean up a temporary property
+    bool mIgnoreHwcPhysicalDisplayOrientation = false;
+
 protected:
     // We're reference counted, never destroy SurfaceFlinger directly
     virtual ~SurfaceFlinger();